@shepai/cli 1.22.1 → 1.23.1
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/dist/packages/core/src/infrastructure/services/file-dialog.service.d.ts +28 -0
- package/dist/packages/core/src/infrastructure/services/file-dialog.service.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/file-dialog.service.js +63 -0
- package/dist/src/presentation/web/app/api/dialog/pick-files/route.d.ts +11 -0
- package/dist/src/presentation/web/app/api/dialog/pick-files/route.d.ts.map +1 -0
- package/dist/src/presentation/web/app/api/dialog/pick-files/route.js +16 -0
- package/dist/src/presentation/web/app/derive-state.d.ts +12 -0
- package/dist/src/presentation/web/app/derive-state.d.ts.map +1 -0
- package/dist/src/presentation/web/app/derive-state.js +21 -0
- package/dist/src/presentation/web/app/page.d.ts.map +1 -1
- package/dist/src/presentation/web/app/page.js +23 -12
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts +15 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js +102 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts +63 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.js +164 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/index.d.ts +2 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/index.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/index.js +1 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/pick-files.d.ts +7 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/pick-files.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/pick-files.js +13 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.d.ts +7 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.js +28 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.stories.d.ts +19 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.stories.js +167 -0
- package/dist/src/presentation/web/components/common/feature-drawer/index.d.ts +2 -0
- package/dist/src/presentation/web/components/common/feature-drawer/index.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-drawer/index.js +1 -0
- package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts +2 -5
- package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.js +4 -13
- package/dist/src/presentation/web/components/common/feature-node/feature-node.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/feature-node.js +7 -9
- package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts +0 -1
- package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.js +4 -23
- package/dist/src/presentation/web/components/common/feature-node/index.d.ts +1 -3
- package/dist/src/presentation/web/components/common/feature-node/index.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/index.js +1 -3
- package/dist/src/presentation/web/components/common/index.d.ts +2 -0
- package/dist/src/presentation/web/components/common/index.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/index.js +2 -0
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +11 -3
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +4 -0
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +20 -4
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts +1 -1
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts.map +1 -1
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +5 -1
- package/dist/src/presentation/web/components/ui/button.d.ts +1 -1
- package/dist/src/presentation/web/components/ui/drawer.d.ts +17 -0
- package/dist/src/presentation/web/components/ui/drawer.d.ts.map +1 -0
- package/dist/src/presentation/web/components/ui/drawer.js +45 -0
- package/dist/src/presentation/web/components/ui/drawer.stories.d.ts +17 -0
- package/dist/src/presentation/web/components/ui/drawer.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/ui/drawer.stories.js +29 -0
- package/dist/src/presentation/web/components/ui/textarea.d.ts +4 -0
- package/dist/src/presentation/web/components/ui/textarea.d.ts.map +1 -0
- package/dist/src/presentation/web/components/ui/textarea.js +6 -0
- package/dist/src/presentation/web/components/ui/textarea.stories.d.ts +10 -0
- package/dist/src/presentation/web/components/ui/textarea.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/ui/textarea.stories.js +28 -0
- package/dist/src/presentation/web/lib/features.d.ts +30 -0
- package/dist/src/presentation/web/lib/features.d.ts.map +1 -0
- package/dist/src/presentation/web/lib/features.js +50 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/app-path-routes-manifest.json +1 -0
- package/web/.next/build-manifest.json +5 -5
- package/web/.next/cache/.previewinfo +1 -1
- package/web/.next/cache/.rscinfo +1 -1
- package/web/.next/cache/.tsbuildinfo +1 -1
- package/web/.next/cache/config.json +3 -3
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- package/web/.next/routes-manifest.json +6 -0
- package/web/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/web/.next/server/app/_global-error/page.js +1 -1
- package/web/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/web/.next/server/app/_not-found/page.js +1 -1
- package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_not-found.html +2 -2
- package/web/.next/server/app/_not-found.rsc +9 -9
- package/web/.next/server/app/_not-found.segments/_full.segment.rsc +9 -9
- package/web/.next/server/app/_not-found.segments/_head.segment.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_index.segment.rsc +5 -5
- package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/api/dialog/pick-files/route/app-paths-manifest.json +3 -0
- package/web/.next/server/app/api/dialog/pick-files/route/build-manifest.json +11 -0
- package/web/.next/server/app/api/dialog/pick-files/route/server-reference-manifest.json +4 -0
- package/web/.next/server/app/api/dialog/pick-files/route.js +6 -0
- package/web/.next/server/app/api/dialog/pick-files/route.js.map +5 -0
- package/web/.next/server/app/api/dialog/pick-files/route.js.nft.json +1 -0
- package/web/.next/server/app/api/dialog/pick-files/route_client-reference-manifest.js +2 -0
- package/web/.next/server/app/api/dialog/pick-folder/route.js +2 -2
- package/web/.next/server/app/api/dialog/pick-folder/route.js.nft.json +1 -1
- package/web/.next/server/app/index.html +2 -2
- package/web/.next/server/app/index.rsc +4 -4
- package/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/page/build-manifest.json +3 -3
- package/web/.next/server/app/page.js +3 -3
- package/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page/build-manifest.json +3 -3
- package/web/.next/server/app/version/page.js +1 -1
- package/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version.html +2 -2
- package/web/.next/server/app/version.rsc +4 -4
- package/web/.next/server/app/version.segments/_full.segment.rsc +4 -4
- package/web/.next/server/app/version.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/version.segments/_index.segment.rsc +3 -3
- package/web/.next/server/app/version.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/version.segments/version/__PAGE__.segment.rsc +2 -2
- package/web/.next/server/app/version.segments/version.segment.rsc +1 -1
- package/web/.next/server/app-paths-manifest.json +1 -0
- package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_dialog_pick-files_route_actions_f9868286.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__00833626._.js +21 -0
- package/web/.next/server/chunks/[root-of-the-server]__00833626._.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__22cabd43._.js +4 -0
- package/web/.next/server/chunks/[root-of-the-server]__22cabd43._.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__95785ac7._.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__95785ac7._.js.map +1 -0
- package/web/.next/server/chunks/ssr/403f9_next_dist_esm_build_templates_app-page_dbf21e04.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__1eade43f._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__1eade43f._.js.map +1 -0
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__6b3c1f2b._.js → [root-of-the-server]__685eaa45._.js} +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__71515c64._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__757c7912._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__7c64f85c._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__7c64f85c._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__8507ac68._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__8507ac68._.js.map +1 -0
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__e55e9c7f._.js → [root-of-the-server]__dbd220d2._.js} +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__f12b60be._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__fa87595c._.js +1 -1
- package/web/.next/server/chunks/ssr/_09afa42a._.js +1 -1
- package/web/.next/server/chunks/ssr/_09afa42a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_1f9347c8._.js +7 -0
- package/web/.next/server/chunks/ssr/_1f9347c8._.js.map +1 -0
- package/web/.next/server/chunks/ssr/_884ddade._.js +1 -1
- package/web/.next/server/chunks/ssr/_937b37e2._.js +3 -0
- package/web/.next/server/chunks/ssr/_937b37e2._.js.map +1 -0
- package/web/.next/server/chunks/ssr/_a0c04017._.js +1 -5
- package/web/.next/server/chunks/ssr/_a0c04017._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_c5d377cc._.js +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_version_version-page-client_tsx_fe9db753._.js +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_version_version-page-client_tsx_fe9db753._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_ui_badge_tsx_964c2a3b._.js +3 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_components_ui_badge_tsx_964c2a3b._.js.map +1 -0
- package/web/.next/server/middleware-build-manifest.js +3 -3
- package/web/.next/server/pages/404.html +2 -2
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/static/chunks/4431cc2d3628b6f6.js +1 -0
- package/web/.next/static/chunks/49488cbe99543598.css +1 -0
- package/web/.next/static/chunks/70993ebedee7eab0.js +2 -0
- package/web/.next/static/chunks/8bc34062bc0f4ddc.js +1 -0
- package/web/.next/static/chunks/c95b4cf170b7f301.js +1 -0
- package/web/.next/static/chunks/df7d29673e0ac11d.js +1 -0
- package/web/.next/static/chunks/e78d1b265b8110c3.js +1 -0
- package/web/.next/static/chunks/f5099c5888ef9a74.js +5 -0
- package/web/.next/static/chunks/{b29c0572633eb9e4.js → f6b5484377f4e78e.js} +2 -2
- package/web/.next/static/chunks/{turbopack-e0a0178ee911c212.js → turbopack-a5a88a85e7ac8e8d.js} +1 -1
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- package/web/.next/types/link.d.ts +1 -0
- package/web/.next/types/routes.d.ts +2 -1
- package/web/.next/types/validator.ts +9 -0
- package/web/next.config.mjs +1 -1
- package/web/package.json +3 -3
- package/web/.next/server/chunks/[externals]_next_dist_a6d89067._.js +0 -3
- package/web/.next/server/chunks/[root-of-the-server]__ebcd3642._.js +0 -21
- package/web/.next/server/chunks/[root-of-the-server]__ebcd3642._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2127336f._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__44c4e191._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__44c4e191._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__492e833d._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__492e833d._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__80e5bed2._.js +0 -3
- package/web/.next/server/chunks/ssr/_3d152196._.js +0 -3
- package/web/.next/server/chunks/ssr/_3d152196._.js.map +0 -1
- package/web/.next/server/chunks/ssr/node_modules__pnpm_b11d5ad4._.js +0 -3
- package/web/.next/server/chunks/ssr/node_modules__pnpm_b11d5ad4._.js.map +0 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +0 -3
- package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +0 -1
- package/web/.next/static/chunks/3701b9d5749e6143.css +0 -1
- package/web/.next/static/chunks/a8030d19b4d73c95.js +0 -1
- package/web/.next/static/chunks/b0d13220be9ac29c.js +0 -1
- package/web/.next/static/chunks/be34161188b4e0a0.js +0 -2
- package/web/.next/static/chunks/c87445f8cbe36c74.js +0 -1
- package/web/.next/static/chunks/f220dad9d38bff95.js +0 -5
- package/web/.next/static/chunks/f74cb59a5a6c94f1.js +0 -1
- /package/web/.next/server/chunks/{[externals]_next_dist_a6d89067._.js.map → 744ca_web__next-internal_server_app_api_dialog_pick-files_route_actions_f9868286.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__6b3c1f2b._.js.map → [root-of-the-server]__685eaa45._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__80e5bed2._.js.map → [root-of-the-server]__71515c64._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__2127336f._.js.map → [root-of-the-server]__757c7912._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__e55e9c7f._.js.map → [root-of-the-server]__dbd220d2._.js.map} +0 -0
- /package/web/.next/static/{Ox87EvwGC4i-5d3Y6mR8W → GicjIYHwLgPQhRVKOwkGc}/_buildManifest.js +0 -0
- /package/web/.next/static/{Ox87EvwGC4i-5d3Y6mR8W → GicjIYHwLgPQhRVKOwkGc}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{Ox87EvwGC4i-5d3Y6mR8W → GicjIYHwLgPQhRVKOwkGc}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface FileAttachment {
|
|
2
|
+
path: string;
|
|
3
|
+
name: string;
|
|
4
|
+
size: number;
|
|
5
|
+
}
|
|
6
|
+
export interface FileDialogDeps {
|
|
7
|
+
platform: NodeJS.Platform;
|
|
8
|
+
exec: (cmd: string, opts?: {
|
|
9
|
+
encoding: BufferEncoding;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
}) => string;
|
|
12
|
+
stat: (filePath: string) => {
|
|
13
|
+
size: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare class FileDialogService {
|
|
17
|
+
private deps;
|
|
18
|
+
constructor(deps?: Partial<FileDialogDeps>);
|
|
19
|
+
/** Returns the OS-specific command string, or null if the platform is unsupported. */
|
|
20
|
+
getCommand(): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Opens a native file picker dialog and returns metadata for each selected file.
|
|
23
|
+
* Returns `null` if the user cancels the dialog.
|
|
24
|
+
* Throws if the platform is unsupported or the command fails unexpectedly.
|
|
25
|
+
*/
|
|
26
|
+
pickFiles(): FileAttachment[] | null;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=file-dialog.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-dialog.service.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/src/infrastructure/services/file-dialog.service.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,EAAE,cAAc,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;IACrF,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAkBD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,IAAI,CAAiB;gBAEjB,IAAI,GAAE,OAAO,CAAC,cAAc,CAAM;IAI9C,sFAAsF;IACtF,UAAU,IAAI,MAAM,GAAG,IAAI;IAI3B;;;;OAIG;IACH,SAAS,IAAI,cAAc,EAAE,GAAG,IAAI;CA8BrC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { statSync } from 'node:fs';
|
|
3
|
+
const defaultDeps = {
|
|
4
|
+
platform: process.platform,
|
|
5
|
+
exec: (cmd, opts) => execSync(cmd, opts),
|
|
6
|
+
stat: (filePath) => statSync(filePath),
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Platform-specific commands that open a native OS file picker dialog.
|
|
10
|
+
* Each returns newline-separated absolute file paths on stdout, or exits non-zero on cancel.
|
|
11
|
+
*/
|
|
12
|
+
const PLATFORM_COMMANDS = {
|
|
13
|
+
darwin: `osascript -e 'set f to (choose file with prompt "Select files" with multiple selections allowed)' -e 'set paths to {}' -e 'repeat with i in f' -e 'copy POSIX path of i to end of paths' -e 'end repeat' -e 'set text item delimiters to "\n"' -e 'paths as text'`,
|
|
14
|
+
linux: `zenity --file-selection --multiple --separator="\\n" --title="Select files" 2>/dev/null`,
|
|
15
|
+
win32: `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; $d = New-Object System.Windows.Forms.OpenFileDialog; $d.Multiselect = $true; $d.Title = 'Select files'; if ($d.ShowDialog() -eq 'OK') { $d.FileNames -join [char]10 } else { exit 1 }"`,
|
|
16
|
+
};
|
|
17
|
+
export class FileDialogService {
|
|
18
|
+
deps;
|
|
19
|
+
constructor(deps = {}) {
|
|
20
|
+
this.deps = { ...defaultDeps, ...deps };
|
|
21
|
+
}
|
|
22
|
+
/** Returns the OS-specific command string, or null if the platform is unsupported. */
|
|
23
|
+
getCommand() {
|
|
24
|
+
return PLATFORM_COMMANDS[this.deps.platform] ?? null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Opens a native file picker dialog and returns metadata for each selected file.
|
|
28
|
+
* Returns `null` if the user cancels the dialog.
|
|
29
|
+
* Throws if the platform is unsupported or the command fails unexpectedly.
|
|
30
|
+
*/
|
|
31
|
+
pickFiles() {
|
|
32
|
+
const command = this.getCommand();
|
|
33
|
+
if (!command) {
|
|
34
|
+
throw new Error(`Unsupported platform: ${this.deps.platform}`);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const result = this.deps.exec(command, { encoding: 'utf-8', timeout: 60_000 });
|
|
38
|
+
const paths = result
|
|
39
|
+
.split('\n')
|
|
40
|
+
.map((line) => line.trim())
|
|
41
|
+
.filter((line) => line.length > 0);
|
|
42
|
+
if (paths.length === 0)
|
|
43
|
+
return null;
|
|
44
|
+
return paths.map((filePath) => {
|
|
45
|
+
const stats = this.deps.stat(filePath);
|
|
46
|
+
return {
|
|
47
|
+
path: filePath,
|
|
48
|
+
name: filePath.split(/[/\\]/).pop(),
|
|
49
|
+
size: stats.size,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (isExecError(error) && error.status === 1) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function isExecError(error) {
|
|
62
|
+
return typeof error === 'object' && error !== null && 'status' in error;
|
|
63
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
export declare function POST(): Promise<NextResponse<{
|
|
3
|
+
files: null;
|
|
4
|
+
cancelled: boolean;
|
|
5
|
+
}> | NextResponse<{
|
|
6
|
+
files: import("../../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js").FileAttachment[];
|
|
7
|
+
cancelled: boolean;
|
|
8
|
+
}> | NextResponse<{
|
|
9
|
+
error: string;
|
|
10
|
+
}>>;
|
|
11
|
+
//# sourceMappingURL=route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/dialog/pick-files/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,wBAAsB,IAAI;;;;;;;;IAezB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { FileDialogService } from '../../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
|
|
3
|
+
export async function POST() {
|
|
4
|
+
const service = new FileDialogService();
|
|
5
|
+
try {
|
|
6
|
+
const files = service.pickFiles();
|
|
7
|
+
if (files === null) {
|
|
8
|
+
return NextResponse.json({ files: null, cancelled: true });
|
|
9
|
+
}
|
|
10
|
+
return NextResponse.json({ files, cancelled: false });
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
const message = error instanceof Error ? error.message : 'Failed to open file dialog';
|
|
14
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FeatureNodeState } from '../components/common/feature-node/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Derives the UI feature node state from lifecycle phase and agent run status.
|
|
4
|
+
* - 'maintain' lifecycle always returns done (completed feature)
|
|
5
|
+
* - Agent status maps: completed->done, failed->error, waiting_approval->action-required
|
|
6
|
+
* - Default: running with 0 progress
|
|
7
|
+
*/
|
|
8
|
+
export declare function deriveState(lifecycle: string, agentStatus: string | undefined): {
|
|
9
|
+
state: FeatureNodeState;
|
|
10
|
+
progress: number;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=derive-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derive-state.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/derive-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEzE;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAe/C"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives the UI feature node state from lifecycle phase and agent run status.
|
|
3
|
+
* - 'maintain' lifecycle always returns done (completed feature)
|
|
4
|
+
* - Agent status maps: completed->done, failed->error, waiting_approval->action-required
|
|
5
|
+
* - Default: running with 0 progress
|
|
6
|
+
*/
|
|
7
|
+
export function deriveState(lifecycle, agentStatus) {
|
|
8
|
+
if (lifecycle === 'maintain') {
|
|
9
|
+
return { state: 'done', progress: 100 };
|
|
10
|
+
}
|
|
11
|
+
switch (agentStatus) {
|
|
12
|
+
case 'completed':
|
|
13
|
+
return { state: 'done', progress: 100 };
|
|
14
|
+
case 'failed':
|
|
15
|
+
return { state: 'error', progress: 0 };
|
|
16
|
+
case 'waiting_approval':
|
|
17
|
+
return { state: 'action-required', progress: 0 };
|
|
18
|
+
default:
|
|
19
|
+
return { state: 'running', progress: 0 };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"AA2BA,wBAA8B,QAAQ,qDA2ErC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { ControlCenter } from '../components/features/control-center/index.js';
|
|
3
|
-
import { getFeatures
|
|
3
|
+
import { getFeatures } from '../lib/features.js';
|
|
4
|
+
import { deriveState } from './derive-state.js';
|
|
4
5
|
import { layoutWithDagre } from '../lib/layout-with-dagre.js';
|
|
5
|
-
import { deriveNodeState, deriveProgress, } from '../components/common/feature-node/derive-feature-state.js';
|
|
6
6
|
/** Map domain SdlcLifecycle enum values to UI FeatureLifecyclePhase (1:1). */
|
|
7
7
|
const lifecycleMap = {
|
|
8
8
|
Requirements: 'requirements',
|
|
@@ -12,18 +12,24 @@ const lifecycleMap = {
|
|
|
12
12
|
'Deploy & QA': 'deploy',
|
|
13
13
|
Maintain: 'maintain',
|
|
14
14
|
};
|
|
15
|
+
/** Map agent graph node names (from agent_run.result) to UI lifecycle phases. */
|
|
16
|
+
const nodeToLifecyclePhase = {
|
|
17
|
+
analyze: 'requirements',
|
|
18
|
+
requirements: 'requirements',
|
|
19
|
+
research: 'research',
|
|
20
|
+
plan: 'implementation',
|
|
21
|
+
implement: 'implementation',
|
|
22
|
+
};
|
|
15
23
|
export default async function HomePage() {
|
|
16
24
|
const features = await getFeatures();
|
|
17
|
-
// Load agent runs for all features in parallel (mirrors CLI feat ls)
|
|
18
|
-
const agentRuns = await Promise.all(features.map((f) => (f.agentRunId ? getAgentRun(f.agentRunId) : Promise.resolve(null))));
|
|
19
25
|
// Group features by repository path
|
|
20
26
|
const featuresByRepo = {};
|
|
21
|
-
features.forEach((f
|
|
27
|
+
features.forEach((f) => {
|
|
22
28
|
const repoKey = f.repositoryPath;
|
|
23
29
|
if (!featuresByRepo[repoKey]) {
|
|
24
30
|
featuresByRepo[repoKey] = [];
|
|
25
31
|
}
|
|
26
|
-
featuresByRepo[repoKey].push(
|
|
32
|
+
featuresByRepo[repoKey].push(f);
|
|
27
33
|
});
|
|
28
34
|
const nodes = [];
|
|
29
35
|
const edges = [];
|
|
@@ -36,17 +42,22 @@ export default async function HomePage() {
|
|
|
36
42
|
position: { x: 0, y: 0 },
|
|
37
43
|
data: { name: repoName },
|
|
38
44
|
});
|
|
39
|
-
repoFeatures.forEach((
|
|
40
|
-
const
|
|
41
|
-
|
|
45
|
+
repoFeatures.forEach((feature) => {
|
|
46
|
+
const agentNode = feature.agentResult?.startsWith('node:')
|
|
47
|
+
? feature.agentResult.slice(5)
|
|
48
|
+
: undefined;
|
|
49
|
+
const lifecycle = feature.agentStatus === 'completed'
|
|
50
|
+
? 'maintain'
|
|
51
|
+
: ((agentNode ? nodeToLifecyclePhase[agentNode] : undefined) ??
|
|
52
|
+
lifecycleMap[feature.lifecycle] ??
|
|
53
|
+
'requirements');
|
|
42
54
|
const nodeData = {
|
|
43
55
|
name: feature.name,
|
|
44
56
|
description: feature.description ?? feature.slug,
|
|
45
57
|
featureId: feature.id,
|
|
46
58
|
lifecycle,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
...(run?.agentType && { agentType: run.agentType }),
|
|
59
|
+
...deriveState(lifecycle, feature.agentStatus),
|
|
60
|
+
...(feature.agentError && { errorMessage: feature.agentError }),
|
|
50
61
|
};
|
|
51
62
|
const featureNodeId = `feat-${feature.id}`;
|
|
52
63
|
nodes.push({
|
package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FileAttachment } from '../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
|
|
2
|
+
export type { FileAttachment } from '../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
|
|
3
|
+
export interface CreateFeatureFormData {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
attachments: FileAttachment[];
|
|
7
|
+
}
|
|
8
|
+
export interface FeatureCreateDrawerProps {
|
|
9
|
+
open: boolean;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
onSubmit: (data: CreateFeatureFormData) => void;
|
|
12
|
+
isSubmitting?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function FeatureCreateDrawer({ open, onClose, onSubmit, isSubmitting, }: FeatureCreateDrawerProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=feature-create-drawer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-create-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAG/F,YAAY,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAE/F,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAChD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,YAAoB,GACrB,EAAE,wBAAwB,2CAkK1B"}
|
package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from 'react';
|
|
4
|
+
import { XIcon, PlusIcon, FileIcon, FileTextIcon, ImageIcon, CodeIcon, Trash2Icon, } from 'lucide-react';
|
|
5
|
+
import { cn } from '../../../lib/utils.js';
|
|
6
|
+
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, } from '../../ui/drawer.js';
|
|
7
|
+
import { Button } from '../../ui/button.js';
|
|
8
|
+
import { Input } from '../../ui/input.js';
|
|
9
|
+
import { Textarea } from '../../ui/textarea.js';
|
|
10
|
+
import { Label } from '../../ui/label.js';
|
|
11
|
+
import { Separator } from '../../ui/separator.js';
|
|
12
|
+
import { Badge } from '../../ui/badge.js';
|
|
13
|
+
import { pickFiles } from './pick-files.js';
|
|
14
|
+
export function FeatureCreateDrawer({ open, onClose, onSubmit, isSubmitting = false, }) {
|
|
15
|
+
const [name, setName] = useState('');
|
|
16
|
+
const [description, setDescription] = useState('');
|
|
17
|
+
const [attachments, setAttachments] = useState([]);
|
|
18
|
+
const resetForm = useCallback(() => {
|
|
19
|
+
setName('');
|
|
20
|
+
setDescription('');
|
|
21
|
+
setAttachments([]);
|
|
22
|
+
}, []);
|
|
23
|
+
const handleOpenChange = useCallback((nextOpen) => {
|
|
24
|
+
if (!nextOpen) {
|
|
25
|
+
resetForm();
|
|
26
|
+
onClose();
|
|
27
|
+
}
|
|
28
|
+
}, [onClose, resetForm]);
|
|
29
|
+
const handleSubmit = useCallback((e) => {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
if (!name.trim())
|
|
32
|
+
return;
|
|
33
|
+
onSubmit({ name: name.trim(), description: description.trim(), attachments });
|
|
34
|
+
}, [name, description, attachments, onSubmit]);
|
|
35
|
+
const handleAddFiles = useCallback(async () => {
|
|
36
|
+
try {
|
|
37
|
+
const files = await pickFiles();
|
|
38
|
+
if (files) {
|
|
39
|
+
setAttachments((prev) => [...prev, ...files]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Native dialog failed — silently ignore (user can retry)
|
|
44
|
+
}
|
|
45
|
+
}, []);
|
|
46
|
+
const handleRemoveFile = useCallback((path) => {
|
|
47
|
+
setAttachments((prev) => prev.filter((f) => f.path !== path));
|
|
48
|
+
}, []);
|
|
49
|
+
return (_jsx(Drawer, { direction: "right", modal: false, open: open, onOpenChange: handleOpenChange, children: _jsxs(DrawerContent, { direction: "right", className: "w-96", showCloseButton: false, children: [_jsx("button", { type: "button", "aria-label": "Close", onClick: onClose, className: "ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden", children: _jsx(XIcon, { className: "size-4" }) }), _jsxs(DrawerHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 shrink-0 rounded-full bg-blue-500" }), _jsx(DrawerTitle, { children: "NEW FEATURE" })] }), _jsx(DrawerDescription, { asChild: true, children: _jsx("div", { children: _jsx(Badge, { variant: "secondary", children: "Creating..." }) }) })] }), _jsx(Separator, {}), _jsx("div", { className: "flex-1 overflow-y-auto p-4", children: _jsxs("form", { id: "create-feature-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-name", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "FEATURE NAME" }), _jsx(Input, { id: "feature-name", placeholder: "e.g. GitHub OAuth Login", value: name, onChange: (e) => setName(e.target.value), required: true, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-description", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "DESCRIPTION" }), _jsx(Textarea, { id: "feature-description", placeholder: "Describe what this feature does...", value: description, onChange: (e) => setDescription(e.target.value), rows: 4, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: ["ATTACHMENTS", attachments.length > 0 && (_jsxs("span", { className: "text-muted-foreground/60 ml-1.5", children: ["(", attachments.length, ")"] }))] }), _jsxs(Button, { type: "button", variant: "outline", size: "xs", onClick: handleAddFiles, disabled: isSubmitting, children: [_jsx(PlusIcon, { className: "size-3" }), "Add Files"] })] }), attachments.length > 0 && (_jsx("div", { className: "flex flex-col gap-2", children: attachments.map((file) => (_jsx(AttachmentCard, { file: file, onRemove: () => handleRemoveFile(file.path), disabled: isSubmitting }, file.path))) }))] })] }) }), _jsx(Separator, {}), _jsxs(DrawerFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: onClose, disabled: isSubmitting, children: "Cancel" }), _jsx(Button, { type: "submit", form: "create-feature-form", disabled: !name.trim() || isSubmitting, children: isSubmitting ? 'Creating...' : '+ Create Feature' })] })] }) }));
|
|
50
|
+
}
|
|
51
|
+
/* ---------------------------------------------------------------------------
|
|
52
|
+
* Private sub-components & utilities
|
|
53
|
+
* ------------------------------------------------------------------------- */
|
|
54
|
+
function AttachmentCard({ file, onRemove, disabled, }) {
|
|
55
|
+
const ext = getExtension(file.name);
|
|
56
|
+
const Icon = getFileIcon(ext);
|
|
57
|
+
const iconColorClass = getFileIconColor(ext);
|
|
58
|
+
return (_jsxs("div", { className: "flex items-center gap-3 rounded-md border p-2", children: [_jsx("div", { className: cn('flex h-8 w-8 shrink-0 items-center justify-center rounded', iconColorClass), children: _jsx(Icon, { className: "h-4 w-4" }) }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "truncate text-sm font-medium", children: file.name }), _jsx("span", { className: "text-muted-foreground truncate text-xs", children: file.path }), _jsx("span", { className: "text-muted-foreground text-xs", children: formatFileSize(file.size) })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon-xs", onClick: onRemove, disabled: disabled, "aria-label": `Remove ${file.name}`, children: _jsx(Trash2Icon, { className: "h-3 w-3" }) })] }));
|
|
59
|
+
}
|
|
60
|
+
function getExtension(filename) {
|
|
61
|
+
const dot = filename.lastIndexOf('.');
|
|
62
|
+
return dot >= 0 ? filename.slice(dot).toLowerCase() : '';
|
|
63
|
+
}
|
|
64
|
+
const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.bmp']);
|
|
65
|
+
const CODE_EXTS = new Set([
|
|
66
|
+
'.ts',
|
|
67
|
+
'.tsx',
|
|
68
|
+
'.js',
|
|
69
|
+
'.jsx',
|
|
70
|
+
'.json',
|
|
71
|
+
'.yaml',
|
|
72
|
+
'.yml',
|
|
73
|
+
'.xml',
|
|
74
|
+
'.html',
|
|
75
|
+
'.css',
|
|
76
|
+
'.md',
|
|
77
|
+
]);
|
|
78
|
+
function getFileIcon(ext) {
|
|
79
|
+
if (IMAGE_EXTS.has(ext))
|
|
80
|
+
return ImageIcon;
|
|
81
|
+
if (ext === '.pdf')
|
|
82
|
+
return FileTextIcon;
|
|
83
|
+
if (CODE_EXTS.has(ext))
|
|
84
|
+
return CodeIcon;
|
|
85
|
+
return FileIcon;
|
|
86
|
+
}
|
|
87
|
+
function getFileIconColor(ext) {
|
|
88
|
+
if (ext === '.pdf')
|
|
89
|
+
return 'bg-red-50 text-red-600';
|
|
90
|
+
if (IMAGE_EXTS.has(ext))
|
|
91
|
+
return 'bg-blue-50 text-blue-600';
|
|
92
|
+
if (CODE_EXTS.has(ext))
|
|
93
|
+
return 'bg-emerald-50 text-emerald-600';
|
|
94
|
+
return 'bg-gray-50 text-gray-600';
|
|
95
|
+
}
|
|
96
|
+
function formatFileSize(bytes) {
|
|
97
|
+
if (bytes === 0)
|
|
98
|
+
return '0 B';
|
|
99
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
100
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
101
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
102
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { FeatureCreateDrawer } from './feature-create-drawer.js';
|
|
3
|
+
/**
|
|
4
|
+
* **FeatureCreateDrawer** is a right-side sliding drawer containing a form to
|
|
5
|
+
* create a new feature in the Control Center.
|
|
6
|
+
*
|
|
7
|
+
* ### Form sections
|
|
8
|
+
* - **Feature Name** (required) — text input, submit is disabled when empty
|
|
9
|
+
* - **Description** (optional) — multi-line textarea
|
|
10
|
+
* - **Attachments** (optional) — native OS file picker with extension-based icon system:
|
|
11
|
+
* image → blue, PDF → red, code/text → emerald, generic → gray.
|
|
12
|
+
* Each attachment card shows the **full absolute file path**.
|
|
13
|
+
*
|
|
14
|
+
* ### Props
|
|
15
|
+
* | Prop | Type | Description |
|
|
16
|
+
* |------|------|-------------|
|
|
17
|
+
* | `open` | `boolean` | Controls drawer visibility |
|
|
18
|
+
* | `onClose` | `() => void` | Called on dismiss (close button, cancel, or backdrop) |
|
|
19
|
+
* | `onSubmit` | `(data: CreateFeatureFormData) => void` | Called with `{ name, description, attachments }` |
|
|
20
|
+
* | `isSubmitting` | `boolean` | Disables all fields and shows "Creating..." on submit button |
|
|
21
|
+
*
|
|
22
|
+
* ### Behavior
|
|
23
|
+
* - Form resets (name, description, attachments cleared) when the drawer closes
|
|
24
|
+
* - Submit button is disabled when name is empty OR `isSubmitting` is true
|
|
25
|
+
* - Non-modal (`modal={false}`) — canvas stays interactive behind the drawer
|
|
26
|
+
* - Fixed width: 384px (`w-96`)
|
|
27
|
+
* - Attachments use native OS file picker via `pickFiles()` — returns `FileAttachment[]`
|
|
28
|
+
* with full absolute paths, filenames, and sizes
|
|
29
|
+
*/
|
|
30
|
+
declare const meta: Meta<typeof FeatureCreateDrawer>;
|
|
31
|
+
export default meta;
|
|
32
|
+
type Story = StoryObj<typeof FeatureCreateDrawer>;
|
|
33
|
+
/** Default empty form — click the trigger button to open the create feature drawer. */
|
|
34
|
+
export declare const Default: Story;
|
|
35
|
+
/**
|
|
36
|
+
* Pre-filled form — click to open, then name and description are typed in.
|
|
37
|
+
* Demonstrates the form in a "ready to submit" state.
|
|
38
|
+
*/
|
|
39
|
+
export declare const PreFilled: Story;
|
|
40
|
+
/**
|
|
41
|
+
* Submitting/loading state — all form fields are disabled, submit button shows "Creating...".
|
|
42
|
+
* Click the trigger to open and observe the disabled form.
|
|
43
|
+
*/
|
|
44
|
+
export declare const Submitting: Story;
|
|
45
|
+
/**
|
|
46
|
+
* Validation state — open the drawer with an empty name field.
|
|
47
|
+
* The "+ Create Feature" button is disabled because name validation fails.
|
|
48
|
+
*/
|
|
49
|
+
export declare const ValidationDisabled: Story;
|
|
50
|
+
/** Click to open the drawer for quick visual inspection. */
|
|
51
|
+
export declare const PreOpened: Story;
|
|
52
|
+
/**
|
|
53
|
+
* Fully interactive story — open the drawer, fill the form, and click
|
|
54
|
+
* "Add Files" to attach files via the native OS file picker.
|
|
55
|
+
*
|
|
56
|
+
* **Submitted data** is displayed in a styled panel showing `FileAttachment[]`
|
|
57
|
+
* with **full absolute file paths**, filenames, and sizes.
|
|
58
|
+
*
|
|
59
|
+
* In Storybook, the native picker won't work (no backend), but submitted data
|
|
60
|
+
* would show the paths if files were attached programmatically.
|
|
61
|
+
*/
|
|
62
|
+
export declare const Interactive: Story;
|
|
63
|
+
//# sourceMappingURL=feature-create-drawer.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-create-drawer.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAQ9D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,mBAAmB,CA0B1C,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAgDlD,uFAAuF;AACvF,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAiBvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAExB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAMhC,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,KAiDzB,CAAC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { within, userEvent, fn } from '@storybook/test';
|
|
4
|
+
import { FeatureCreateDrawer } from './feature-create-drawer.js';
|
|
5
|
+
import { Button } from '../../ui/button.js';
|
|
6
|
+
/* ---------------------------------------------------------------------------
|
|
7
|
+
* Meta — component-level docs + interactive controls
|
|
8
|
+
* ------------------------------------------------------------------------- */
|
|
9
|
+
/**
|
|
10
|
+
* **FeatureCreateDrawer** is a right-side sliding drawer containing a form to
|
|
11
|
+
* create a new feature in the Control Center.
|
|
12
|
+
*
|
|
13
|
+
* ### Form sections
|
|
14
|
+
* - **Feature Name** (required) — text input, submit is disabled when empty
|
|
15
|
+
* - **Description** (optional) — multi-line textarea
|
|
16
|
+
* - **Attachments** (optional) — native OS file picker with extension-based icon system:
|
|
17
|
+
* image → blue, PDF → red, code/text → emerald, generic → gray.
|
|
18
|
+
* Each attachment card shows the **full absolute file path**.
|
|
19
|
+
*
|
|
20
|
+
* ### Props
|
|
21
|
+
* | Prop | Type | Description |
|
|
22
|
+
* |------|------|-------------|
|
|
23
|
+
* | `open` | `boolean` | Controls drawer visibility |
|
|
24
|
+
* | `onClose` | `() => void` | Called on dismiss (close button, cancel, or backdrop) |
|
|
25
|
+
* | `onSubmit` | `(data: CreateFeatureFormData) => void` | Called with `{ name, description, attachments }` |
|
|
26
|
+
* | `isSubmitting` | `boolean` | Disables all fields and shows "Creating..." on submit button |
|
|
27
|
+
*
|
|
28
|
+
* ### Behavior
|
|
29
|
+
* - Form resets (name, description, attachments cleared) when the drawer closes
|
|
30
|
+
* - Submit button is disabled when name is empty OR `isSubmitting` is true
|
|
31
|
+
* - Non-modal (`modal={false}`) — canvas stays interactive behind the drawer
|
|
32
|
+
* - Fixed width: 384px (`w-96`)
|
|
33
|
+
* - Attachments use native OS file picker via `pickFiles()` — returns `FileAttachment[]`
|
|
34
|
+
* with full absolute paths, filenames, and sizes
|
|
35
|
+
*/
|
|
36
|
+
const meta = {
|
|
37
|
+
title: 'Composed/FeatureCreateDrawer',
|
|
38
|
+
component: FeatureCreateDrawer,
|
|
39
|
+
tags: ['autodocs'],
|
|
40
|
+
parameters: {
|
|
41
|
+
layout: 'fullscreen',
|
|
42
|
+
},
|
|
43
|
+
argTypes: {
|
|
44
|
+
open: {
|
|
45
|
+
control: 'boolean',
|
|
46
|
+
description: 'Whether the drawer is visible.',
|
|
47
|
+
},
|
|
48
|
+
onClose: {
|
|
49
|
+
description: 'Callback fired when the drawer is dismissed (close button, cancel, or backdrop click).',
|
|
50
|
+
},
|
|
51
|
+
onSubmit: {
|
|
52
|
+
description: 'Callback fired with `CreateFeatureFormData` when the form is submitted. Receives `{ name, description, attachments }`.',
|
|
53
|
+
},
|
|
54
|
+
isSubmitting: {
|
|
55
|
+
control: 'boolean',
|
|
56
|
+
description: 'When true, all form fields and buttons are disabled, and the submit button shows "Creating...".',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
export default meta;
|
|
61
|
+
/* ---------------------------------------------------------------------------
|
|
62
|
+
* Shared action loggers
|
|
63
|
+
* ------------------------------------------------------------------------- */
|
|
64
|
+
const logSubmit = fn().mockName('onSubmit');
|
|
65
|
+
const logClose = fn().mockName('onClose');
|
|
66
|
+
/* ---------------------------------------------------------------------------
|
|
67
|
+
* Trigger wrapper — every story uses this so nothing auto-opens on Docs page
|
|
68
|
+
* ------------------------------------------------------------------------- */
|
|
69
|
+
/** Starts closed — click button to open. Actions are logged. */
|
|
70
|
+
function CreateDrawerTrigger({ isSubmitting = false, label = 'Open Create Feature', }) {
|
|
71
|
+
const [open, setOpen] = useState(false);
|
|
72
|
+
return (_jsxs("div", { className: "flex h-screen items-start p-4", children: [_jsx(Button, { variant: "outline", onClick: () => setOpen(true), children: label }), _jsx(FeatureCreateDrawer, { open: open, onClose: () => {
|
|
73
|
+
setOpen(false);
|
|
74
|
+
logClose();
|
|
75
|
+
}, onSubmit: (data) => {
|
|
76
|
+
logSubmit(data);
|
|
77
|
+
setOpen(false);
|
|
78
|
+
}, isSubmitting: isSubmitting })] }));
|
|
79
|
+
}
|
|
80
|
+
/* ---------------------------------------------------------------------------
|
|
81
|
+
* Per-state stories
|
|
82
|
+
* ------------------------------------------------------------------------- */
|
|
83
|
+
/** Default empty form — click the trigger button to open the create feature drawer. */
|
|
84
|
+
export const Default = {
|
|
85
|
+
render: () => _jsx(CreateDrawerTrigger, {}),
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Pre-filled form — click to open, then name and description are typed in.
|
|
89
|
+
* Demonstrates the form in a "ready to submit" state.
|
|
90
|
+
*/
|
|
91
|
+
export const PreFilled = {
|
|
92
|
+
render: () => _jsx(CreateDrawerTrigger, { label: "Open Pre-Filled" }),
|
|
93
|
+
play: async ({ canvasElement }) => {
|
|
94
|
+
const canvas = within(canvasElement);
|
|
95
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Open Pre-Filled' }));
|
|
96
|
+
// Drawer renders in a portal — query from document body
|
|
97
|
+
const body = within(canvasElement.ownerDocument.body);
|
|
98
|
+
const nameInput = await body.findByPlaceholderText('e.g. GitHub OAuth Login');
|
|
99
|
+
const descInput = body.getByPlaceholderText('Describe what this feature does...');
|
|
100
|
+
await userEvent.type(nameInput, 'GitHub OAuth Login');
|
|
101
|
+
await userEvent.type(descInput, 'Implement OAuth2 authentication with GitHub as the identity provider. Includes login, callback handling, and token refresh.');
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Submitting/loading state — all form fields are disabled, submit button shows "Creating...".
|
|
106
|
+
* Click the trigger to open and observe the disabled form.
|
|
107
|
+
*/
|
|
108
|
+
export const Submitting = {
|
|
109
|
+
render: () => _jsx(CreateDrawerTrigger, { isSubmitting: true, label: "Open (Submitting)" }),
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Validation state — open the drawer with an empty name field.
|
|
113
|
+
* The "+ Create Feature" button is disabled because name validation fails.
|
|
114
|
+
*/
|
|
115
|
+
export const ValidationDisabled = {
|
|
116
|
+
render: () => _jsx(CreateDrawerTrigger, { label: "Open Validation Demo" }),
|
|
117
|
+
play: async ({ canvasElement }) => {
|
|
118
|
+
const canvas = within(canvasElement);
|
|
119
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Open Validation Demo' }));
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
/** Click to open the drawer for quick visual inspection. */
|
|
123
|
+
export const PreOpened = {
|
|
124
|
+
render: () => _jsx(CreateDrawerTrigger, { label: "Open Drawer" }),
|
|
125
|
+
play: async ({ canvasElement }) => {
|
|
126
|
+
const canvas = within(canvasElement);
|
|
127
|
+
await userEvent.click(canvas.getByRole('button', { name: 'Open Drawer' }));
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
/* ---------------------------------------------------------------------------
|
|
131
|
+
* Interactive story — full paths shown in submitted data panel
|
|
132
|
+
* ------------------------------------------------------------------------- */
|
|
133
|
+
/**
|
|
134
|
+
* Fully interactive story — open the drawer, fill the form, and click
|
|
135
|
+
* "Add Files" to attach files via the native OS file picker.
|
|
136
|
+
*
|
|
137
|
+
* **Submitted data** is displayed in a styled panel showing `FileAttachment[]`
|
|
138
|
+
* with **full absolute file paths**, filenames, and sizes.
|
|
139
|
+
*
|
|
140
|
+
* In Storybook, the native picker won't work (no backend), but submitted data
|
|
141
|
+
* would show the paths if files were attached programmatically.
|
|
142
|
+
*/
|
|
143
|
+
export const Interactive = {
|
|
144
|
+
render: function InteractiveRender() {
|
|
145
|
+
const [open, setOpen] = useState(false);
|
|
146
|
+
const [submitted, setSubmitted] = useState(null);
|
|
147
|
+
return (_jsxs("div", { className: "flex h-screen items-start gap-4 p-4", children: [_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(Button, { variant: "outline", onClick: () => setOpen(true), children: "Open Create Feature" }), submitted ? (_jsxs("div", { className: "bg-muted/50 max-w-sm rounded-md border p-3", children: [_jsx("span", { className: "text-muted-foreground mb-1 text-xs font-semibold tracking-wider uppercase", children: "Last submitted" }), _jsx("pre", { className: "mt-1 text-xs whitespace-pre-wrap", children: JSON.stringify({
|
|
148
|
+
name: submitted.name,
|
|
149
|
+
description: submitted.description,
|
|
150
|
+
attachments: submitted.attachments.map((f) => ({
|
|
151
|
+
path: f.path,
|
|
152
|
+
name: f.name,
|
|
153
|
+
size: f.size,
|
|
154
|
+
})),
|
|
155
|
+
}, null, 2) })] })) : null] }), _jsx(FeatureCreateDrawer, { open: open, onClose: () => {
|
|
156
|
+
setOpen(false);
|
|
157
|
+
logClose();
|
|
158
|
+
}, onSubmit: (data) => {
|
|
159
|
+
logSubmit(data);
|
|
160
|
+
setSubmitted(data);
|
|
161
|
+
setOpen(false);
|
|
162
|
+
} })] }));
|
|
163
|
+
},
|
|
164
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,cAAc,GACpB,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FeatureCreateDrawer, } from './feature-create-drawer.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FileAttachment } from '../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
|
|
2
|
+
/**
|
|
3
|
+
* Calls the backend API to open a native OS file picker dialog.
|
|
4
|
+
* Returns the selected files with metadata, or null if the user cancelled.
|
|
5
|
+
*/
|
|
6
|
+
export declare function pickFiles(): Promise<FileAttachment[] | null>;
|
|
7
|
+
//# sourceMappingURL=pick-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pick-files.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/pick-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAE/F;;;GAGG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,cAAc,EAAE,GAAG,IAAI,CAAC,CAUlE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calls the backend API to open a native OS file picker dialog.
|
|
3
|
+
* Returns the selected files with metadata, or null if the user cancelled.
|
|
4
|
+
*/
|
|
5
|
+
export async function pickFiles() {
|
|
6
|
+
const response = await fetch('/api/dialog/pick-files', { method: 'POST' });
|
|
7
|
+
if (!response.ok) {
|
|
8
|
+
const body = await response.json().catch(() => null);
|
|
9
|
+
throw new Error(body?.error ?? 'Failed to open file dialog');
|
|
10
|
+
}
|
|
11
|
+
const body = await response.json();
|
|
12
|
+
return body.files;
|
|
13
|
+
}
|