@shipfox/client-projects 12.0.1 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +59 -0
  3. package/dist/chrome.d.ts +5 -1
  4. package/dist/chrome.d.ts.map +1 -1
  5. package/dist/chrome.js +18 -26
  6. package/dist/chrome.js.map +1 -1
  7. package/dist/components/model-provider-reminder-banner.d.ts.map +1 -1
  8. package/dist/components/model-provider-reminder-banner.js +4 -2
  9. package/dist/components/model-provider-reminder-banner.js.map +1 -1
  10. package/dist/components/project-crumb.d.ts +3 -1
  11. package/dist/components/project-crumb.d.ts.map +1 -1
  12. package/dist/components/project-crumb.js +7 -5
  13. package/dist/components/project-crumb.js.map +1 -1
  14. package/dist/components/project-switcher.d.ts +2 -1
  15. package/dist/components/project-switcher.d.ts.map +1 -1
  16. package/dist/components/project-switcher.js +10 -8
  17. package/dist/components/project-switcher.js.map +1 -1
  18. package/dist/core/definition.d.ts +6 -0
  19. package/dist/core/definition.d.ts.map +1 -1
  20. package/dist/core/definition.js.map +1 -1
  21. package/dist/core/project.d.ts +7 -0
  22. package/dist/core/project.d.ts.map +1 -1
  23. package/dist/core/project.js.map +1 -1
  24. package/dist/feature.d.ts +29 -5
  25. package/dist/feature.d.ts.map +1 -1
  26. package/dist/feature.js +26 -5
  27. package/dist/feature.js.map +1 -1
  28. package/dist/hooks/api/mappers.d.ts.map +1 -1
  29. package/dist/hooks/api/mappers.js +9 -1
  30. package/dist/hooks/api/mappers.js.map +1 -1
  31. package/dist/hooks/api/projects.d.ts +21 -2
  32. package/dist/hooks/api/projects.d.ts.map +1 -1
  33. package/dist/hooks/api/projects.js +175 -2
  34. package/dist/hooks/api/projects.js.map +1 -1
  35. package/dist/index.d.ts +3 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/pages/create-project-page.d.ts.map +1 -1
  40. package/dist/pages/create-project-page.js +97 -21
  41. package/dist/pages/create-project-page.js.map +1 -1
  42. package/dist/pages/create-project-page.stories.js +5 -3
  43. package/dist/pages/create-project-page.stories.js.map +1 -1
  44. package/dist/pages/project-settings-form-errors.d.ts +11 -0
  45. package/dist/pages/project-settings-form-errors.d.ts.map +1 -0
  46. package/dist/pages/project-settings-form-errors.js +16 -0
  47. package/dist/pages/project-settings-form-errors.js.map +1 -0
  48. package/dist/pages/project-settings-page.d.ts +2 -0
  49. package/dist/pages/project-settings-page.d.ts.map +1 -0
  50. package/dist/pages/project-settings-page.js +225 -0
  51. package/dist/pages/project-settings-page.js.map +1 -0
  52. package/dist/pages/project-settings-page.stories.d.ts +23 -0
  53. package/dist/pages/project-settings-page.stories.d.ts.map +1 -0
  54. package/dist/pages/project-settings-page.stories.js +206 -0
  55. package/dist/pages/project-settings-page.stories.js.map +1 -0
  56. package/dist/pages/projects-hub-page.js +11 -11
  57. package/dist/pages/projects-hub-page.js.map +1 -1
  58. package/dist/project-error.d.ts +1 -0
  59. package/dist/project-error.d.ts.map +1 -1
  60. package/dist/project-error.js +7 -0
  61. package/dist/project-error.js.map +1 -1
  62. package/dist/routes/inputs.d.ts +2 -2
  63. package/dist/routes/inputs.d.ts.map +1 -1
  64. package/dist/routes/inputs.js +5 -5
  65. package/dist/routes/inputs.js.map +1 -1
  66. package/dist/routes/project-index.d.ts +2 -2
  67. package/dist/routes/project-index.d.ts.map +1 -1
  68. package/dist/routes/project-index.js +3 -3
  69. package/dist/routes/project-index.js.map +1 -1
  70. package/dist/routes/project-settings-index.d.ts +10 -0
  71. package/dist/routes/project-settings-index.d.ts.map +1 -0
  72. package/dist/routes/project-settings-index.js +12 -0
  73. package/dist/routes/project-settings-index.js.map +1 -0
  74. package/dist/routes/project-settings.d.ts +6 -0
  75. package/dist/routes/project-settings.d.ts.map +1 -0
  76. package/dist/routes/project-settings.js +7 -0
  77. package/dist/routes/project-settings.js.map +1 -0
  78. package/dist/tsconfig.test.tsbuildinfo +1 -1
  79. package/package.json +11 -10
  80. package/src/chrome.tsx +27 -19
  81. package/src/components/model-provider-reminder-banner.tsx +3 -1
  82. package/src/components/project-crumb.tsx +18 -5
  83. package/src/components/project-switcher.test.tsx +7 -4
  84. package/src/components/project-switcher.tsx +16 -5
  85. package/src/core/definition.ts +7 -0
  86. package/src/core/project.ts +8 -0
  87. package/src/feature.ts +26 -4
  88. package/src/hooks/api/mappers.test.ts +19 -0
  89. package/src/hooks/api/mappers.ts +6 -0
  90. package/src/hooks/api/projects.test.ts +247 -1
  91. package/src/hooks/api/projects.ts +227 -2
  92. package/src/index.ts +9 -1
  93. package/src/pages/create-project-page.stories.tsx +5 -4
  94. package/src/pages/create-project-page.test.tsx +191 -12
  95. package/src/pages/create-project-page.tsx +102 -15
  96. package/src/pages/home-router.test.tsx +2 -2
  97. package/src/pages/project-settings-form-errors.ts +17 -0
  98. package/src/pages/project-settings-page.stories.tsx +216 -0
  99. package/src/pages/project-settings-page.test.tsx +74 -0
  100. package/src/pages/project-settings-page.tsx +220 -0
  101. package/src/pages/projects-hub-page.test.tsx +17 -11
  102. package/src/pages/projects-hub-page.tsx +9 -9
  103. package/src/project-error.test.ts +1 -0
  104. package/src/project-error.ts +8 -0
  105. package/src/routes/inputs.test.ts +8 -6
  106. package/src/routes/inputs.ts +7 -6
  107. package/src/routes/project-index.tsx +3 -3
  108. package/src/routes/project-settings-index.tsx +11 -0
  109. package/src/routes/project-settings.tsx +4 -0
  110. package/test/pages.tsx +26 -22
  111. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 22 files with swc (326.68ms)
2
+ Successfully compiled: 27 files with swc (93.49ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,64 @@
1
1
  # @shipfox/client-projects
2
2
 
3
+ ## 13.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - e405e92: Move client routes to slug-based `/w/$workspaceSlug` and `/p/$projectSlug` URLs, enforce the new composition contract, and support bounded project-slug resolution.
8
+ - 4eb18b8: Add required, workspace-scoped project slugs across the API and project creation form.
9
+
10
+ ### Minor Changes
11
+
12
+ - 3c73365: Add project settings, shared slug rename warnings, and live slug availability checks.
13
+
14
+ ### Patch Changes
15
+
16
+ - 9fdd5e4: Persist definition validation warnings from repository syncs and surface them on the workflow page without changing sync success or run creation behavior.
17
+ - Updated dependencies [ee2ce67]
18
+ - Updated dependencies [5d2c9cf]
19
+ - Updated dependencies [e405e92]
20
+ - Updated dependencies [89f2c18]
21
+ - Updated dependencies [f78740d]
22
+ - Updated dependencies [9e1d599]
23
+ - Updated dependencies [adf07e7]
24
+ - Updated dependencies [3f781ee]
25
+ - Updated dependencies [9969937]
26
+ - Updated dependencies [9fdd5e4]
27
+ - Updated dependencies [3c73365]
28
+ - Updated dependencies [4eb18b8]
29
+ - Updated dependencies [f13e8bb]
30
+ - Updated dependencies [869a792]
31
+ - Updated dependencies [54c820e]
32
+ - Updated dependencies [8cc5a36]
33
+ - Updated dependencies [35a42bd]
34
+ - Updated dependencies [6adc228]
35
+ - Updated dependencies [34a5639]
36
+ - Updated dependencies [032d316]
37
+ - Updated dependencies [c2a8e54]
38
+ - Updated dependencies [54c820e]
39
+ - Updated dependencies [cb0abfa]
40
+ - Updated dependencies [e1efaee]
41
+ - @shipfox/api-definitions-dto@12.0.0
42
+ - @shipfox/client-agent@13.0.0
43
+ - @shipfox/client-auth@13.0.0
44
+ - @shipfox/client-integrations@13.0.0
45
+ - @shipfox/client-shell@13.0.0
46
+ - @shipfox/api-common-dto@12.0.0
47
+ - @shipfox/client-ui@13.0.0
48
+ - @shipfox/react-ui@0.4.0
49
+ - @shipfox/api-projects-dto@12.0.0
50
+ - @shipfox/api-integration-core-dto@12.0.0
51
+
52
+ ## 12.0.2
53
+
54
+ ### Patch Changes
55
+
56
+ - @shipfox/api-definitions-dto@11.0.0
57
+ - @shipfox/client-auth@12.0.2
58
+ - @shipfox/client-shell@12.0.2
59
+ - @shipfox/client-integrations@12.0.2
60
+ - @shipfox/client-agent@12.0.2
61
+
3
62
  ## 12.0.1
4
63
 
5
64
  ### Patch Changes
package/dist/chrome.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ import { resolveProjectSlug } from '#hooks/api/projects.js';
2
+ export { resolveProjectSlug };
3
+ export declare function useMaybeActiveProjectQuery(): import("@tanstack/react-query").UseQueryResult<NoInfer<import(".").Project | null>, Error>;
4
+ export declare function useMaybeActiveProject(): NoInfer<import(".").Project | null> | undefined;
5
+ export declare function useActiveProject(): NonNullable<NoInfer<import(".").Project | null>>;
1
6
  export declare function ProjectBreadcrumb(): import("react").JSX.Element;
2
- export declare function ProjectLayoutGuard(): import("react").JSX.Element | null;
3
7
  //# sourceMappingURL=chrome.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chrome.d.ts","sourceRoot":"","sources":["../src/chrome.tsx"],"names":[],"mappings":"AAWA,wBAAgB,iBAAiB,gCAOhC;AAED,wBAAgB,kBAAkB,uCAUjC"}
1
+ {"version":3,"file":"chrome.d.ts","sourceRoot":"","sources":["../src/chrome.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAC,kBAAkB,EAAsB,MAAM,wBAAwB,CAAC;AAE/E,OAAO,EAAC,kBAAkB,EAAC,CAAC;AAE5B,wBAAgB,0BAA0B,+FAIzC;AAED,wBAAgB,qBAAqB,oDAEpC;AAED,wBAAgB,gBAAgB,qDAI/B;AAED,wBAAgB,iBAAiB,gCAYhC"}
package/dist/chrome.js CHANGED
@@ -1,39 +1,31 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { parseWorkspaceProjectParams, useActiveWorkspace, useRouteParams } from '@shipfox/client-shell/runtime';
3
- import { Outlet, useNavigate } from '@tanstack/react-router';
4
- import { useEffect } from 'react';
5
3
  import { ProjectCrumb } from '#components/project-crumb.js';
6
- import { useProjectQuery } from '#hooks/api/projects.js';
7
- import { projectRouteParams } from './routes/inputs.js';
4
+ import { resolveProjectSlug, useProjectSlugQuery } from '#hooks/api/projects.js';
5
+ export { resolveProjectSlug };
6
+ export function useMaybeActiveProjectQuery() {
7
+ const workspace = useActiveWorkspace();
8
+ const { projectSlug } = useRouteParams(parseWorkspaceProjectParams);
9
+ return useProjectSlugQuery(workspace.id, projectSlug);
10
+ }
11
+ export function useMaybeActiveProject() {
12
+ return useMaybeActiveProjectQuery().data;
13
+ }
14
+ export function useActiveProject() {
15
+ const project = useMaybeActiveProject();
16
+ if (!project) throw new Error('No active project is available for this route.');
17
+ return project;
18
+ }
8
19
  export function ProjectBreadcrumb() {
9
20
  const workspace = useActiveWorkspace();
10
- const { pid } = useRouteParams(parseWorkspaceProjectParams);
11
- const project = useProjectQuery(pid).data;
21
+ const project = useMaybeActiveProject();
12
22
  return /*#__PURE__*/ _jsx(ProjectCrumb, {
13
23
  workspaceId: workspace.id,
24
+ workspaceSlug: workspace.slug,
14
25
  projectId: project?.id,
26
+ projectSlug: project?.slug,
15
27
  projectName: project?.name
16
28
  });
17
29
  }
18
- export function ProjectLayoutGuard() {
19
- const { wid, pid } = useRouteParams(projectRouteParams);
20
- const navigate = useNavigate();
21
- const project = useProjectQuery(pid).data;
22
- useEffect(()=>{
23
- if (project && project.workspaceId !== wid) void navigate({
24
- to: '/workspaces/$wid',
25
- params: {
26
- wid
27
- },
28
- replace: true
29
- });
30
- }, [
31
- navigate,
32
- project,
33
- wid
34
- ]);
35
- if (project && project.workspaceId !== wid) return null;
36
- return /*#__PURE__*/ _jsx(Outlet, {});
37
- }
38
30
 
39
31
  //# sourceMappingURL=chrome.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/chrome.tsx"],"sourcesContent":["import {\n parseWorkspaceProjectParams,\n useActiveWorkspace,\n useRouteParams,\n} from '@shipfox/client-shell/runtime';\nimport {Outlet, useNavigate} from '@tanstack/react-router';\nimport {useEffect} from 'react';\nimport {ProjectCrumb} from '#components/project-crumb.js';\nimport {useProjectQuery} from '#hooks/api/projects.js';\nimport {projectRouteParams} from './routes/inputs.js';\n\nexport function ProjectBreadcrumb() {\n const workspace = useActiveWorkspace();\n const {pid} = useRouteParams(parseWorkspaceProjectParams);\n const project = useProjectQuery(pid).data;\n return (\n <ProjectCrumb workspaceId={workspace.id} projectId={project?.id} projectName={project?.name} />\n );\n}\n\nexport function ProjectLayoutGuard() {\n const {wid, pid} = useRouteParams(projectRouteParams);\n const navigate = useNavigate();\n const project = useProjectQuery(pid).data;\n useEffect(() => {\n if (project && project.workspaceId !== wid)\n void navigate({to: '/workspaces/$wid', params: {wid}, replace: true});\n }, [navigate, project, wid]);\n if (project && project.workspaceId !== wid) return null;\n return <Outlet />;\n}\n"],"names":["parseWorkspaceProjectParams","useActiveWorkspace","useRouteParams","Outlet","useNavigate","useEffect","ProjectCrumb","useProjectQuery","projectRouteParams","ProjectBreadcrumb","workspace","pid","project","data","workspaceId","id","projectId","projectName","name","ProjectLayoutGuard","wid","navigate","to","params","replace"],"mappings":";AAAA,SACEA,2BAA2B,EAC3BC,kBAAkB,EAClBC,cAAc,QACT,gCAAgC;AACvC,SAAQC,MAAM,EAAEC,WAAW,QAAO,yBAAyB;AAC3D,SAAQC,SAAS,QAAO,QAAQ;AAChC,SAAQC,YAAY,QAAO,+BAA+B;AAC1D,SAAQC,eAAe,QAAO,yBAAyB;AACvD,SAAQC,kBAAkB,QAAO,qBAAqB;AAEtD,OAAO,SAASC;IACd,MAAMC,YAAYT;IAClB,MAAM,EAACU,GAAG,EAAC,GAAGT,eAAeF;IAC7B,MAAMY,UAAUL,gBAAgBI,KAAKE,IAAI;IACzC,qBACE,KAACP;QAAaQ,aAAaJ,UAAUK,EAAE;QAAEC,WAAWJ,SAASG;QAAIE,aAAaL,SAASM;;AAE3F;AAEA,OAAO,SAASC;IACd,MAAM,EAACC,GAAG,EAAET,GAAG,EAAC,GAAGT,eAAeM;IAClC,MAAMa,WAAWjB;IACjB,MAAMQ,UAAUL,gBAAgBI,KAAKE,IAAI;IACzCR,UAAU;QACR,IAAIO,WAAWA,QAAQE,WAAW,KAAKM,KACrC,KAAKC,SAAS;YAACC,IAAI;YAAoBC,QAAQ;gBAACH;YAAG;YAAGI,SAAS;QAAI;IACvE,GAAG;QAACH;QAAUT;QAASQ;KAAI;IAC3B,IAAIR,WAAWA,QAAQE,WAAW,KAAKM,KAAK,OAAO;IACnD,qBAAO,KAACjB;AACV"}
1
+ {"version":3,"sources":["../src/chrome.tsx"],"sourcesContent":["import {\n parseWorkspaceProjectParams,\n useActiveWorkspace,\n useRouteParams,\n} from '@shipfox/client-shell/runtime';\nimport {ProjectCrumb} from '#components/project-crumb.js';\nimport {resolveProjectSlug, useProjectSlugQuery} from '#hooks/api/projects.js';\n\nexport {resolveProjectSlug};\n\nexport function useMaybeActiveProjectQuery() {\n const workspace = useActiveWorkspace();\n const {projectSlug} = useRouteParams(parseWorkspaceProjectParams);\n return useProjectSlugQuery(workspace.id, projectSlug);\n}\n\nexport function useMaybeActiveProject() {\n return useMaybeActiveProjectQuery().data;\n}\n\nexport function useActiveProject() {\n const project = useMaybeActiveProject();\n if (!project) throw new Error('No active project is available for this route.');\n return project;\n}\n\nexport function ProjectBreadcrumb() {\n const workspace = useActiveWorkspace();\n const project = useMaybeActiveProject();\n return (\n <ProjectCrumb\n workspaceId={workspace.id}\n workspaceSlug={workspace.slug}\n projectId={project?.id}\n projectSlug={project?.slug}\n projectName={project?.name}\n />\n );\n}\n"],"names":["parseWorkspaceProjectParams","useActiveWorkspace","useRouteParams","ProjectCrumb","resolveProjectSlug","useProjectSlugQuery","useMaybeActiveProjectQuery","workspace","projectSlug","id","useMaybeActiveProject","data","useActiveProject","project","Error","ProjectBreadcrumb","workspaceId","workspaceSlug","slug","projectId","projectName","name"],"mappings":";AAAA,SACEA,2BAA2B,EAC3BC,kBAAkB,EAClBC,cAAc,QACT,gCAAgC;AACvC,SAAQC,YAAY,QAAO,+BAA+B;AAC1D,SAAQC,kBAAkB,EAAEC,mBAAmB,QAAO,yBAAyB;AAE/E,SAAQD,kBAAkB,GAAE;AAE5B,OAAO,SAASE;IACd,MAAMC,YAAYN;IAClB,MAAM,EAACO,WAAW,EAAC,GAAGN,eAAeF;IACrC,OAAOK,oBAAoBE,UAAUE,EAAE,EAAED;AAC3C;AAEA,OAAO,SAASE;IACd,OAAOJ,6BAA6BK,IAAI;AAC1C;AAEA,OAAO,SAASC;IACd,MAAMC,UAAUH;IAChB,IAAI,CAACG,SAAS,MAAM,IAAIC,MAAM;IAC9B,OAAOD;AACT;AAEA,OAAO,SAASE;IACd,MAAMR,YAAYN;IAClB,MAAMY,UAAUH;IAChB,qBACE,KAACP;QACCa,aAAaT,UAAUE,EAAE;QACzBQ,eAAeV,UAAUW,IAAI;QAC7BC,WAAWN,SAASJ;QACpBD,aAAaK,SAASK;QACtBE,aAAaP,SAASQ;;AAG5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"model-provider-reminder-banner.d.ts","sourceRoot":"","sources":["../../src/components/model-provider-reminder-banner.tsx"],"names":[],"mappings":"AAcA,wBAAgB,2BAA2B,CAAC,EAAC,WAAW,EAAC,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,sCAoC/E"}
1
+ {"version":3,"file":"model-provider-reminder-banner.d.ts","sourceRoot":"","sources":["../../src/components/model-provider-reminder-banner.tsx"],"names":[],"mappings":"AAeA,wBAAgB,2BAA2B,CAAC,EAAC,WAAW,EAAC,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,sCAqC/E"}
@@ -1,11 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useModelProviderConfigsQuery } from '@shipfox/client-agent';
3
+ import { useActiveWorkspace } from '@shipfox/client-auth';
3
4
  import { createTypedBrowserStorage, sessionStorageOrUndefined } from '@shipfox/client-ui';
4
5
  import { Alert, AlertActions, AlertClose, AlertContent, AlertDescription, AlertTitle } from '@shipfox/react-ui/alert';
5
6
  import { Button } from '@shipfox/react-ui/button';
6
7
  import { Link } from '@tanstack/react-router';
7
8
  import { useState } from 'react';
8
9
  export function ModelProviderReminderBanner({ workspaceId }) {
10
+ const workspace = useActiveWorkspace();
9
11
  const configsQuery = useModelProviderConfigsQuery(workspaceId);
10
12
  const [dismissed, setDismissed] = useState(()=>isReminderDismissed(workspaceId));
11
13
  const configs = configsQuery.data?.configs;
@@ -36,9 +38,9 @@ export function ModelProviderReminderBanner({ workspaceId }) {
36
38
  size: "sm",
37
39
  variant: "secondary",
38
40
  children: /*#__PURE__*/ _jsx(Link, {
39
- to: "/workspaces/$wid/settings/agents",
41
+ to: "/w/$workspaceSlug/settings/agents",
40
42
  params: {
41
- wid: workspaceId
43
+ workspaceSlug: workspace.slug
42
44
  },
43
45
  children: "Agents"
44
46
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/model-provider-reminder-banner.tsx"],"sourcesContent":["import {useModelProviderConfigsQuery} from '@shipfox/client-agent';\nimport {createTypedBrowserStorage, sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {\n Alert,\n AlertActions,\n AlertClose,\n AlertContent,\n AlertDescription,\n AlertTitle,\n} from '@shipfox/react-ui/alert';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Link} from '@tanstack/react-router';\nimport {useState} from 'react';\n\nexport function ModelProviderReminderBanner({workspaceId}: {workspaceId: string}) {\n const configsQuery = useModelProviderConfigsQuery(workspaceId);\n const [dismissed, setDismissed] = useState(() => isReminderDismissed(workspaceId));\n const configs = configsQuery.data?.configs;\n const unconfigured =\n Array.isArray(configs) && configs.length === 0 && configsQuery.data?.defaultProviderId === null;\n\n if (!unconfigured || dismissed) return null;\n\n return (\n <Alert\n variant=\"info\"\n animated={false}\n onOpenChange={(open) => {\n if (!open) {\n dismissReminder(workspaceId);\n setDismissed(true);\n }\n }}\n >\n <AlertContent className=\"pr-28\">\n <AlertTitle>Finish setting up a model provider</AlertTitle>\n <AlertDescription>\n Add workspace credentials, or keep using the instance default.\n </AlertDescription>\n <AlertActions>\n <Button asChild size=\"sm\" variant=\"secondary\">\n <Link to=\"/workspaces/$wid/settings/agents\" params={{wid: workspaceId}}>\n Agents\n </Link>\n </Button>\n </AlertActions>\n </AlertContent>\n <AlertClose />\n </Alert>\n );\n}\n\nfunction isReminderDismissed(workspaceId: string): boolean {\n return reminderStorage(workspaceId).read() === true;\n}\n\nfunction dismissReminder(workspaceId: string): void {\n reminderStorage(workspaceId).write(true);\n}\n\nfunction reminderStorage(workspaceId: string) {\n return createTypedBrowserStorage(\n sessionStorageOrUndefined,\n {\n key: 'shipfox.modelProviderReminder.dismissed',\n lifetime: 'session' as const,\n principalScope: 'workspace' as const,\n serialize: (dismissed: boolean) => JSON.stringify(dismissed),\n parse: (raw: string) => raw === 'true',\n },\n {workspaceId},\n );\n}\n"],"names":["useModelProviderConfigsQuery","createTypedBrowserStorage","sessionStorageOrUndefined","Alert","AlertActions","AlertClose","AlertContent","AlertDescription","AlertTitle","Button","Link","useState","ModelProviderReminderBanner","workspaceId","configsQuery","dismissed","setDismissed","isReminderDismissed","configs","data","unconfigured","Array","isArray","length","defaultProviderId","variant","animated","onOpenChange","open","dismissReminder","className","asChild","size","to","params","wid","reminderStorage","read","write","key","lifetime","principalScope","serialize","JSON","stringify","parse","raw"],"mappings":";AAAA,SAAQA,4BAA4B,QAAO,wBAAwB;AACnE,SAAQC,yBAAyB,EAAEC,yBAAyB,QAAO,qBAAqB;AACxF,SACEC,KAAK,EACLC,YAAY,EACZC,UAAU,EACVC,YAAY,EACZC,gBAAgB,EAChBC,UAAU,QACL,0BAA0B;AACjC,SAAQC,MAAM,QAAO,2BAA2B;AAChD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,QAAQ,QAAO,QAAQ;AAE/B,OAAO,SAASC,4BAA4B,EAACC,WAAW,EAAwB;IAC9E,MAAMC,eAAed,6BAA6Ba;IAClD,MAAM,CAACE,WAAWC,aAAa,GAAGL,SAAS,IAAMM,oBAAoBJ;IACrE,MAAMK,UAAUJ,aAAaK,IAAI,EAAED;IACnC,MAAME,eACJC,MAAMC,OAAO,CAACJ,YAAYA,QAAQK,MAAM,KAAK,KAAKT,aAAaK,IAAI,EAAEK,sBAAsB;IAE7F,IAAI,CAACJ,gBAAgBL,WAAW,OAAO;IAEvC,qBACE,MAACZ;QACCsB,SAAQ;QACRC,UAAU;QACVC,cAAc,CAACC;YACb,IAAI,CAACA,MAAM;gBACTC,gBAAgBhB;gBAChBG,aAAa;YACf;QACF;;0BAEA,MAACV;gBAAawB,WAAU;;kCACtB,KAACtB;kCAAW;;kCACZ,KAACD;kCAAiB;;kCAGlB,KAACH;kCACC,cAAA,KAACK;4BAAOsB,OAAO;4BAACC,MAAK;4BAAKP,SAAQ;sCAChC,cAAA,KAACf;gCAAKuB,IAAG;gCAAmCC,QAAQ;oCAACC,KAAKtB;gCAAW;0CAAG;;;;;;0BAM9E,KAACR;;;AAGP;AAEA,SAASY,oBAAoBJ,WAAmB;IAC9C,OAAOuB,gBAAgBvB,aAAawB,IAAI,OAAO;AACjD;AAEA,SAASR,gBAAgBhB,WAAmB;IAC1CuB,gBAAgBvB,aAAayB,KAAK,CAAC;AACrC;AAEA,SAASF,gBAAgBvB,WAAmB;IAC1C,OAAOZ,0BACLC,2BACA;QACEqC,KAAK;QACLC,UAAU;QACVC,gBAAgB;QAChBC,WAAW,CAAC3B,YAAuB4B,KAAKC,SAAS,CAAC7B;QAClD8B,OAAO,CAACC,MAAgBA,QAAQ;IAClC,GACA;QAACjC;IAAW;AAEhB"}
1
+ {"version":3,"sources":["../../src/components/model-provider-reminder-banner.tsx"],"sourcesContent":["import {useModelProviderConfigsQuery} from '@shipfox/client-agent';\nimport {useActiveWorkspace} from '@shipfox/client-auth';\nimport {createTypedBrowserStorage, sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {\n Alert,\n AlertActions,\n AlertClose,\n AlertContent,\n AlertDescription,\n AlertTitle,\n} from '@shipfox/react-ui/alert';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Link} from '@tanstack/react-router';\nimport {useState} from 'react';\n\nexport function ModelProviderReminderBanner({workspaceId}: {workspaceId: string}) {\n const workspace = useActiveWorkspace();\n const configsQuery = useModelProviderConfigsQuery(workspaceId);\n const [dismissed, setDismissed] = useState(() => isReminderDismissed(workspaceId));\n const configs = configsQuery.data?.configs;\n const unconfigured =\n Array.isArray(configs) && configs.length === 0 && configsQuery.data?.defaultProviderId === null;\n\n if (!unconfigured || dismissed) return null;\n\n return (\n <Alert\n variant=\"info\"\n animated={false}\n onOpenChange={(open) => {\n if (!open) {\n dismissReminder(workspaceId);\n setDismissed(true);\n }\n }}\n >\n <AlertContent className=\"pr-28\">\n <AlertTitle>Finish setting up a model provider</AlertTitle>\n <AlertDescription>\n Add workspace credentials, or keep using the instance default.\n </AlertDescription>\n <AlertActions>\n <Button asChild size=\"sm\" variant=\"secondary\">\n <Link to=\"/w/$workspaceSlug/settings/agents\" params={{workspaceSlug: workspace.slug}}>\n Agents\n </Link>\n </Button>\n </AlertActions>\n </AlertContent>\n <AlertClose />\n </Alert>\n );\n}\n\nfunction isReminderDismissed(workspaceId: string): boolean {\n return reminderStorage(workspaceId).read() === true;\n}\n\nfunction dismissReminder(workspaceId: string): void {\n reminderStorage(workspaceId).write(true);\n}\n\nfunction reminderStorage(workspaceId: string) {\n return createTypedBrowserStorage(\n sessionStorageOrUndefined,\n {\n key: 'shipfox.modelProviderReminder.dismissed',\n lifetime: 'session' as const,\n principalScope: 'workspace' as const,\n serialize: (dismissed: boolean) => JSON.stringify(dismissed),\n parse: (raw: string) => raw === 'true',\n },\n {workspaceId},\n );\n}\n"],"names":["useModelProviderConfigsQuery","useActiveWorkspace","createTypedBrowserStorage","sessionStorageOrUndefined","Alert","AlertActions","AlertClose","AlertContent","AlertDescription","AlertTitle","Button","Link","useState","ModelProviderReminderBanner","workspaceId","workspace","configsQuery","dismissed","setDismissed","isReminderDismissed","configs","data","unconfigured","Array","isArray","length","defaultProviderId","variant","animated","onOpenChange","open","dismissReminder","className","asChild","size","to","params","workspaceSlug","slug","reminderStorage","read","write","key","lifetime","principalScope","serialize","JSON","stringify","parse","raw"],"mappings":";AAAA,SAAQA,4BAA4B,QAAO,wBAAwB;AACnE,SAAQC,kBAAkB,QAAO,uBAAuB;AACxD,SAAQC,yBAAyB,EAAEC,yBAAyB,QAAO,qBAAqB;AACxF,SACEC,KAAK,EACLC,YAAY,EACZC,UAAU,EACVC,YAAY,EACZC,gBAAgB,EAChBC,UAAU,QACL,0BAA0B;AACjC,SAAQC,MAAM,QAAO,2BAA2B;AAChD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,QAAQ,QAAO,QAAQ;AAE/B,OAAO,SAASC,4BAA4B,EAACC,WAAW,EAAwB;IAC9E,MAAMC,YAAYd;IAClB,MAAMe,eAAehB,6BAA6Bc;IAClD,MAAM,CAACG,WAAWC,aAAa,GAAGN,SAAS,IAAMO,oBAAoBL;IACrE,MAAMM,UAAUJ,aAAaK,IAAI,EAAED;IACnC,MAAME,eACJC,MAAMC,OAAO,CAACJ,YAAYA,QAAQK,MAAM,KAAK,KAAKT,aAAaK,IAAI,EAAEK,sBAAsB;IAE7F,IAAI,CAACJ,gBAAgBL,WAAW,OAAO;IAEvC,qBACE,MAACb;QACCuB,SAAQ;QACRC,UAAU;QACVC,cAAc,CAACC;YACb,IAAI,CAACA,MAAM;gBACTC,gBAAgBjB;gBAChBI,aAAa;YACf;QACF;;0BAEA,MAACX;gBAAayB,WAAU;;kCACtB,KAACvB;kCAAW;;kCACZ,KAACD;kCAAiB;;kCAGlB,KAACH;kCACC,cAAA,KAACK;4BAAOuB,OAAO;4BAACC,MAAK;4BAAKP,SAAQ;sCAChC,cAAA,KAAChB;gCAAKwB,IAAG;gCAAoCC,QAAQ;oCAACC,eAAetB,UAAUuB,IAAI;gCAAA;0CAAG;;;;;;0BAM5F,KAAChC;;;AAGP;AAEA,SAASa,oBAAoBL,WAAmB;IAC9C,OAAOyB,gBAAgBzB,aAAa0B,IAAI,OAAO;AACjD;AAEA,SAAST,gBAAgBjB,WAAmB;IAC1CyB,gBAAgBzB,aAAa2B,KAAK,CAAC;AACrC;AAEA,SAASF,gBAAgBzB,WAAmB;IAC1C,OAAOZ,0BACLC,2BACA;QACEuC,KAAK;QACLC,UAAU;QACVC,gBAAgB;QAChBC,WAAW,CAAC5B,YAAuB6B,KAAKC,SAAS,CAAC9B;QAClD+B,OAAO,CAACC,MAAgBA,QAAQ;IAClC,GACA;QAACnC;IAAW;AAEhB"}
@@ -1,7 +1,9 @@
1
1
  export interface ProjectCrumbProps {
2
2
  workspaceId: string;
3
+ workspaceSlug: string;
3
4
  projectId?: string | undefined;
5
+ projectSlug?: string | undefined;
4
6
  projectName?: string | undefined;
5
7
  }
6
- export declare function ProjectCrumb({ workspaceId, projectId, projectName }: ProjectCrumbProps): import("react").JSX.Element;
8
+ export declare function ProjectCrumb({ workspaceId, workspaceSlug, projectId, projectSlug, projectName, }: ProjectCrumbProps): import("react").JSX.Element;
7
9
  //# sourceMappingURL=project-crumb.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-crumb.d.ts","sourceRoot":"","sources":["../../src/components/project-crumb.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,wBAAgB,YAAY,CAAC,EAAC,WAAW,EAAE,SAAS,EAAE,WAAW,EAAC,EAAE,iBAAiB,+BAyDpF"}
1
+ {"version":3,"file":"project-crumb.d.ts","sourceRoot":"","sources":["../../src/components/project-crumb.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,aAAa,EACb,SAAS,EACT,WAAW,EACX,WAAW,GACZ,EAAE,iBAAiB,+BA8DnB"}
@@ -4,17 +4,17 @@ import { Popover, PopoverContent, PopoverTrigger } from '@shipfox/react-ui/popov
4
4
  import { Link } from '@tanstack/react-router';
5
5
  import { useState } from 'react';
6
6
  import { ProjectSwitcher } from './project-switcher.js';
7
- export function ProjectCrumb({ workspaceId, projectId, projectName }) {
7
+ export function ProjectCrumb({ workspaceId, workspaceSlug, projectId, projectSlug, projectName }) {
8
8
  const [open, setOpen] = useState(false);
9
- if (projectId && projectName) {
9
+ if (projectSlug && projectName) {
10
10
  return /*#__PURE__*/ _jsxs("div", {
11
11
  className: "flex items-center",
12
12
  children: [
13
13
  /*#__PURE__*/ _jsx(Link, {
14
- to: "/workspaces/$wid/projects/$pid",
14
+ to: "/w/$workspaceSlug/p/$projectSlug",
15
15
  params: {
16
- wid: workspaceId,
17
- pid: projectId
16
+ workspaceSlug,
17
+ projectSlug
18
18
  },
19
19
  "aria-current": "page",
20
20
  className: "text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors max-w-[240px] truncate",
@@ -44,6 +44,7 @@ export function ProjectCrumb({ workspaceId, projectId, projectName }) {
44
44
  sideOffset: 8,
45
45
  children: /*#__PURE__*/ _jsx(ProjectSwitcher, {
46
46
  workspaceId: workspaceId,
47
+ workspaceSlug: workspaceSlug,
47
48
  activeProjectId: projectId,
48
49
  onSelect: ()=>setOpen(false)
49
50
  })
@@ -83,6 +84,7 @@ export function ProjectCrumb({ workspaceId, projectId, projectName }) {
83
84
  sideOffset: 8,
84
85
  children: /*#__PURE__*/ _jsx(ProjectSwitcher, {
85
86
  workspaceId: workspaceId,
87
+ workspaceSlug: workspaceSlug,
86
88
  onSelect: ()=>setOpen(false)
87
89
  })
88
90
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/project-crumb.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Popover, PopoverContent, PopoverTrigger} from '@shipfox/react-ui/popover';\nimport {Link} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {ProjectSwitcher} from './project-switcher.js';\n\nexport interface ProjectCrumbProps {\n workspaceId: string;\n projectId?: string | undefined;\n projectName?: string | undefined;\n}\n\nexport function ProjectCrumb({workspaceId, projectId, projectName}: ProjectCrumbProps) {\n const [open, setOpen] = useState(false);\n\n if (projectId && projectName) {\n return (\n <div className=\"flex items-center\">\n <Link\n to=\"/workspaces/$wid/projects/$pid\"\n params={{wid: workspaceId, pid: projectId}}\n aria-current=\"page\"\n className=\"text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors max-w-[240px] truncate\"\n >\n {projectName}\n </Link>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Switch project\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className=\"ml-2 grid place-items-center size-24 rounded-4 text-foreground-neutral-muted hover:bg-background-components-hover hover:text-foreground-neutral-base transition-colors\"\n >\n <Icon name=\"arrowDownSLine\" className=\"size-16\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-[320px] p-0\" align=\"start\" sideOffset={8}>\n <ProjectSwitcher\n workspaceId={workspaceId}\n activeProjectId={projectId}\n onSelect={() => setOpen(false)}\n />\n </PopoverContent>\n </Popover>\n </div>\n );\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Switch project\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className=\"flex items-center gap-2 text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors\"\n >\n <span className=\"max-w-[240px] truncate\">All projects</span>\n <Icon name=\"arrowDownSLine\" className=\"size-16 text-foreground-neutral-muted\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-[320px] p-0\" align=\"start\" sideOffset={8}>\n <ProjectSwitcher workspaceId={workspaceId} onSelect={() => setOpen(false)} />\n </PopoverContent>\n </Popover>\n );\n}\n"],"names":["Icon","Popover","PopoverContent","PopoverTrigger","Link","useState","ProjectSwitcher","ProjectCrumb","workspaceId","projectId","projectName","open","setOpen","div","className","to","params","wid","pid","aria-current","onOpenChange","asChild","button","type","aria-label","aria-haspopup","aria-expanded","name","align","sideOffset","activeProjectId","onSelect","span"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,QAAQ,QAAO,QAAQ;AAC/B,SAAQC,eAAe,QAAO,wBAAwB;AAQtD,OAAO,SAASC,aAAa,EAACC,WAAW,EAAEC,SAAS,EAAEC,WAAW,EAAoB;IACnF,MAAM,CAACC,MAAMC,QAAQ,GAAGP,SAAS;IAEjC,IAAII,aAAaC,aAAa;QAC5B,qBACE,MAACG;YAAIC,WAAU;;8BACb,KAACV;oBACCW,IAAG;oBACHC,QAAQ;wBAACC,KAAKT;wBAAaU,KAAKT;oBAAS;oBACzCU,gBAAa;oBACbL,WAAU;8BAETJ;;8BAEH,MAACT;oBAAQU,MAAMA;oBAAMS,cAAcR;;sCACjC,KAACT;4BAAekB,OAAO;sCACrB,cAAA,KAACC;gCACCC,MAAK;gCACLC,cAAW;gCACXC,iBAAc;gCACdC,iBAAef;gCACfG,WAAU;0CAEV,cAAA,KAACd;oCAAK2B,MAAK;oCAAiBb,WAAU;;;;sCAG1C,KAACZ;4BAAeY,WAAU;4BAAgBc,OAAM;4BAAQC,YAAY;sCAClE,cAAA,KAACvB;gCACCE,aAAaA;gCACbsB,iBAAiBrB;gCACjBsB,UAAU,IAAMnB,QAAQ;;;;;;;IAMpC;IAEA,qBACE,MAACX;QAAQU,MAAMA;QAAMS,cAAcR;;0BACjC,KAACT;gBAAekB,OAAO;0BACrB,cAAA,MAACC;oBACCC,MAAK;oBACLC,cAAW;oBACXC,iBAAc;oBACdC,iBAAef;oBACfG,WAAU;;sCAEV,KAACkB;4BAAKlB,WAAU;sCAAyB;;sCACzC,KAACd;4BAAK2B,MAAK;4BAAiBb,WAAU;;;;;0BAG1C,KAACZ;gBAAeY,WAAU;gBAAgBc,OAAM;gBAAQC,YAAY;0BAClE,cAAA,KAACvB;oBAAgBE,aAAaA;oBAAauB,UAAU,IAAMnB,QAAQ;;;;;AAI3E"}
1
+ {"version":3,"sources":["../../src/components/project-crumb.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Popover, PopoverContent, PopoverTrigger} from '@shipfox/react-ui/popover';\nimport {Link} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {ProjectSwitcher} from './project-switcher.js';\n\nexport interface ProjectCrumbProps {\n workspaceId: string;\n workspaceSlug: string;\n projectId?: string | undefined;\n projectSlug?: string | undefined;\n projectName?: string | undefined;\n}\n\nexport function ProjectCrumb({\n workspaceId,\n workspaceSlug,\n projectId,\n projectSlug,\n projectName,\n}: ProjectCrumbProps) {\n const [open, setOpen] = useState(false);\n\n if (projectSlug && projectName) {\n return (\n <div className=\"flex items-center\">\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug\"\n params={{workspaceSlug, projectSlug}}\n aria-current=\"page\"\n className=\"text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors max-w-[240px] truncate\"\n >\n {projectName}\n </Link>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Switch project\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className=\"ml-2 grid place-items-center size-24 rounded-4 text-foreground-neutral-muted hover:bg-background-components-hover hover:text-foreground-neutral-base transition-colors\"\n >\n <Icon name=\"arrowDownSLine\" className=\"size-16\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-[320px] p-0\" align=\"start\" sideOffset={8}>\n <ProjectSwitcher\n workspaceId={workspaceId}\n workspaceSlug={workspaceSlug}\n activeProjectId={projectId}\n onSelect={() => setOpen(false)}\n />\n </PopoverContent>\n </Popover>\n </div>\n );\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Switch project\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className=\"flex items-center gap-2 text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors\"\n >\n <span className=\"max-w-[240px] truncate\">All projects</span>\n <Icon name=\"arrowDownSLine\" className=\"size-16 text-foreground-neutral-muted\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-[320px] p-0\" align=\"start\" sideOffset={8}>\n <ProjectSwitcher\n workspaceId={workspaceId}\n workspaceSlug={workspaceSlug}\n onSelect={() => setOpen(false)}\n />\n </PopoverContent>\n </Popover>\n );\n}\n"],"names":["Icon","Popover","PopoverContent","PopoverTrigger","Link","useState","ProjectSwitcher","ProjectCrumb","workspaceId","workspaceSlug","projectId","projectSlug","projectName","open","setOpen","div","className","to","params","aria-current","onOpenChange","asChild","button","type","aria-label","aria-haspopup","aria-expanded","name","align","sideOffset","activeProjectId","onSelect","span"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,QAAQ,QAAO,QAAQ;AAC/B,SAAQC,eAAe,QAAO,wBAAwB;AAUtD,OAAO,SAASC,aAAa,EAC3BC,WAAW,EACXC,aAAa,EACbC,SAAS,EACTC,WAAW,EACXC,WAAW,EACO;IAClB,MAAM,CAACC,MAAMC,QAAQ,GAAGT,SAAS;IAEjC,IAAIM,eAAeC,aAAa;QAC9B,qBACE,MAACG;YAAIC,WAAU;;8BACb,KAACZ;oBACCa,IAAG;oBACHC,QAAQ;wBAACT;wBAAeE;oBAAW;oBACnCQ,gBAAa;oBACbH,WAAU;8BAETJ;;8BAEH,MAACX;oBAAQY,MAAMA;oBAAMO,cAAcN;;sCACjC,KAACX;4BAAekB,OAAO;sCACrB,cAAA,KAACC;gCACCC,MAAK;gCACLC,cAAW;gCACXC,iBAAc;gCACdC,iBAAeb;gCACfG,WAAU;0CAEV,cAAA,KAAChB;oCAAK2B,MAAK;oCAAiBX,WAAU;;;;sCAG1C,KAACd;4BAAec,WAAU;4BAAgBY,OAAM;4BAAQC,YAAY;sCAClE,cAAA,KAACvB;gCACCE,aAAaA;gCACbC,eAAeA;gCACfqB,iBAAiBpB;gCACjBqB,UAAU,IAAMjB,QAAQ;;;;;;;IAMpC;IAEA,qBACE,MAACb;QAAQY,MAAMA;QAAMO,cAAcN;;0BACjC,KAACX;gBAAekB,OAAO;0BACrB,cAAA,MAACC;oBACCC,MAAK;oBACLC,cAAW;oBACXC,iBAAc;oBACdC,iBAAeb;oBACfG,WAAU;;sCAEV,KAACgB;4BAAKhB,WAAU;sCAAyB;;sCACzC,KAAChB;4BAAK2B,MAAK;4BAAiBX,WAAU;;;;;0BAG1C,KAACd;gBAAec,WAAU;gBAAgBY,OAAM;gBAAQC,YAAY;0BAClE,cAAA,KAACvB;oBACCE,aAAaA;oBACbC,eAAeA;oBACfsB,UAAU,IAAMjB,QAAQ;;;;;AAKlC"}
@@ -1,7 +1,8 @@
1
1
  export interface ProjectSwitcherProps {
2
2
  workspaceId: string;
3
+ workspaceSlug: string;
3
4
  activeProjectId?: string | undefined;
4
5
  onSelect?: () => void;
5
6
  }
6
- export declare function ProjectSwitcher({ workspaceId, activeProjectId, onSelect }: ProjectSwitcherProps): import("react").JSX.Element;
7
+ export declare function ProjectSwitcher({ workspaceId, workspaceSlug, activeProjectId, onSelect, }: ProjectSwitcherProps): import("react").JSX.Element;
7
8
  //# sourceMappingURL=project-switcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-switcher.d.ts","sourceRoot":"","sources":["../../src/components/project-switcher.tsx"],"names":[],"mappings":"AAcA,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,eAAe,CAAC,EAAC,WAAW,EAAE,eAAe,EAAE,QAAQ,EAAC,EAAE,oBAAoB,+BA0G7F"}
1
+ {"version":3,"file":"project-switcher.d.ts","sourceRoot":"","sources":["../../src/components/project-switcher.tsx"],"names":[],"mappings":"AAcA,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,eAAe,CAAC,EAC9B,WAAW,EACX,aAAa,EACb,eAAe,EACf,QAAQ,GACT,EAAE,oBAAoB,+BA+GtB"}
@@ -4,7 +4,7 @@ import { Icon } from '@shipfox/react-ui/icon';
4
4
  import { useNavigate } from '@tanstack/react-router';
5
5
  import { useEffect, useState } from 'react';
6
6
  import { useProjectsInfiniteQuery } from '#hooks/api/projects.js';
7
- export function ProjectSwitcher({ workspaceId, activeProjectId, onSelect }) {
7
+ export function ProjectSwitcher({ workspaceId, workspaceSlug, activeProjectId, onSelect }) {
8
8
  const navigate = useNavigate();
9
9
  const [searchValue, setSearchValue] = useState('');
10
10
  const query = useProjectsInfiniteQuery(workspaceId);
@@ -21,29 +21,31 @@ export function ProjectSwitcher({ workspaceId, activeProjectId, onSelect }) {
21
21
  query.fetchNextPage
22
22
  ]);
23
23
  const handleSelect = (projectId)=>{
24
+ const project = projects.find((candidate)=>candidate.id === projectId);
25
+ if (!project) return;
24
26
  navigate({
25
- to: '/workspaces/$wid/projects/$pid',
27
+ to: '/w/$workspaceSlug/p/$projectSlug',
26
28
  params: {
27
- wid: workspaceId,
28
- pid: projectId
29
+ workspaceSlug,
30
+ projectSlug: project.slug
29
31
  }
30
32
  });
31
33
  onSelect?.();
32
34
  };
33
35
  const handleSelectAll = ()=>{
34
36
  navigate({
35
- to: '/workspaces/$wid',
37
+ to: '/w/$workspaceSlug',
36
38
  params: {
37
- wid: workspaceId
39
+ workspaceSlug
38
40
  }
39
41
  });
40
42
  onSelect?.();
41
43
  };
42
44
  const handleCreate = ()=>{
43
45
  navigate({
44
- to: '/workspaces/$wid/projects/new',
46
+ to: '/w/$workspaceSlug/projects/new',
45
47
  params: {
46
- wid: workspaceId
48
+ workspaceSlug
47
49
  }
48
50
  });
49
51
  onSelect?.();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/project-switcher.tsx"],"sourcesContent":["import {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from '@shipfox/react-ui/command';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {useNavigate} from '@tanstack/react-router';\nimport {type KeyboardEvent, useEffect, useState} from 'react';\nimport {useProjectsInfiniteQuery} from '#hooks/api/projects.js';\n\nexport interface ProjectSwitcherProps {\n workspaceId: string;\n activeProjectId?: string | undefined;\n onSelect?: () => void;\n}\n\nexport function ProjectSwitcher({workspaceId, activeProjectId, onSelect}: ProjectSwitcherProps) {\n const navigate = useNavigate();\n const [searchValue, setSearchValue] = useState('');\n const query = useProjectsInfiniteQuery(workspaceId);\n const projects = query.data?.pages.flatMap((page) => page.projects) ?? [];\n\n // Eagerly fetch additional pages once the popover opens; the switcher should\n // show the full list, not just the first page.\n useEffect(() => {\n if (query.hasNextPage && !query.isFetchingNextPage) {\n query.fetchNextPage();\n }\n }, [query.hasNextPage, query.isFetchingNextPage, query.fetchNextPage]);\n\n const handleSelect = (projectId: string) => {\n navigate({\n to: '/workspaces/$wid/projects/$pid',\n params: {wid: workspaceId, pid: projectId},\n });\n onSelect?.();\n };\n\n const handleSelectAll = () => {\n navigate({to: '/workspaces/$wid', params: {wid: workspaceId}});\n onSelect?.();\n };\n\n const handleCreate = () => {\n navigate({to: '/workspaces/$wid/projects/new', params: {wid: workspaceId}});\n onSelect?.();\n };\n\n const normalizedSearch = searchValue.trim().toLowerCase();\n const hasSearchMatch =\n normalizedSearch.length === 0 ||\n 'all projects'.includes(normalizedSearch) ||\n projects.some((project) =>\n [project.id, project.name].some((value) => value.toLowerCase().includes(normalizedSearch)),\n );\n const shouldCreateFromEmptySearch = !query.isLoading && !query.isError && !hasSearchMatch;\n\n const handleCommandKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Enter' || !shouldCreateFromEmptySearch) {\n return;\n }\n\n event.preventDefault();\n handleCreate();\n };\n\n return (\n <Command onKeyDown={handleCommandKeyDown}>\n <CommandInput\n placeholder=\"Search projects...\"\n value={searchValue}\n onValueChange={setSearchValue}\n />\n <CommandList>\n {query.isError ? (\n <CommandEmpty>Couldn&apos;t load projects.</CommandEmpty>\n ) : query.isLoading ? (\n <CommandEmpty>Loading projects...</CommandEmpty>\n ) : projects.length === 0 ? (\n <CommandEmpty>No projects yet.</CommandEmpty>\n ) : (\n <CommandEmpty>No projects found.</CommandEmpty>\n )}\n <CommandGroup>\n <CommandItem value=\"__all\" keywords={['all projects']} onSelect={handleSelectAll}>\n <Icon\n name=\"check\"\n className={`size-16 mr-8 ${activeProjectId ? 'opacity-0' : 'opacity-100'}`}\n />\n All projects\n </CommandItem>\n </CommandGroup>\n {projects.length > 0 ? (\n <CommandGroup heading=\"Projects\">\n {projects.map((project) => (\n <CommandItem\n key={project.id}\n value={project.id}\n keywords={[project.name]}\n onSelect={() => handleSelect(project.id)}\n >\n <Icon\n name=\"check\"\n className={`size-16 mr-8 ${\n activeProjectId === project.id ? 'opacity-100' : 'opacity-0'\n }`}\n />\n {project.name}\n </CommandItem>\n ))}\n </CommandGroup>\n ) : null}\n </CommandList>\n <CommandSeparator />\n <CommandGroup forceMount>\n <CommandItem value=\"__create\" onSelect={handleCreate} forceMount>\n <Icon name=\"addLine\" className=\"size-16\" />\n Create project\n </CommandItem>\n </CommandGroup>\n </Command>\n );\n}\n"],"names":["Command","CommandEmpty","CommandGroup","CommandInput","CommandItem","CommandList","CommandSeparator","Icon","useNavigate","useEffect","useState","useProjectsInfiniteQuery","ProjectSwitcher","workspaceId","activeProjectId","onSelect","navigate","searchValue","setSearchValue","query","projects","data","pages","flatMap","page","hasNextPage","isFetchingNextPage","fetchNextPage","handleSelect","projectId","to","params","wid","pid","handleSelectAll","handleCreate","normalizedSearch","trim","toLowerCase","hasSearchMatch","length","includes","some","project","id","name","value","shouldCreateFromEmptySearch","isLoading","isError","handleCommandKeyDown","event","key","preventDefault","onKeyDown","placeholder","onValueChange","keywords","className","heading","map","forceMount"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/components/project-switcher.tsx"],"sourcesContent":["import {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from '@shipfox/react-ui/command';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {useNavigate} from '@tanstack/react-router';\nimport {type KeyboardEvent, useEffect, useState} from 'react';\nimport {useProjectsInfiniteQuery} from '#hooks/api/projects.js';\n\nexport interface ProjectSwitcherProps {\n workspaceId: string;\n workspaceSlug: string;\n activeProjectId?: string | undefined;\n onSelect?: () => void;\n}\n\nexport function ProjectSwitcher({\n workspaceId,\n workspaceSlug,\n activeProjectId,\n onSelect,\n}: ProjectSwitcherProps) {\n const navigate = useNavigate();\n const [searchValue, setSearchValue] = useState('');\n const query = useProjectsInfiniteQuery(workspaceId);\n const projects = query.data?.pages.flatMap((page) => page.projects) ?? [];\n\n // Eagerly fetch additional pages once the popover opens; the switcher should\n // show the full list, not just the first page.\n useEffect(() => {\n if (query.hasNextPage && !query.isFetchingNextPage) {\n query.fetchNextPage();\n }\n }, [query.hasNextPage, query.isFetchingNextPage, query.fetchNextPage]);\n\n const handleSelect = (projectId: string) => {\n const project = projects.find((candidate) => candidate.id === projectId);\n if (!project) return;\n navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {\n workspaceSlug,\n projectSlug: project.slug,\n },\n });\n onSelect?.();\n };\n\n const handleSelectAll = () => {\n navigate({to: '/w/$workspaceSlug', params: {workspaceSlug}});\n onSelect?.();\n };\n\n const handleCreate = () => {\n navigate({to: '/w/$workspaceSlug/projects/new', params: {workspaceSlug}});\n onSelect?.();\n };\n\n const normalizedSearch = searchValue.trim().toLowerCase();\n const hasSearchMatch =\n normalizedSearch.length === 0 ||\n 'all projects'.includes(normalizedSearch) ||\n projects.some((project) =>\n [project.id, project.name].some((value) => value.toLowerCase().includes(normalizedSearch)),\n );\n const shouldCreateFromEmptySearch = !query.isLoading && !query.isError && !hasSearchMatch;\n\n const handleCommandKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Enter' || !shouldCreateFromEmptySearch) {\n return;\n }\n\n event.preventDefault();\n handleCreate();\n };\n\n return (\n <Command onKeyDown={handleCommandKeyDown}>\n <CommandInput\n placeholder=\"Search projects...\"\n value={searchValue}\n onValueChange={setSearchValue}\n />\n <CommandList>\n {query.isError ? (\n <CommandEmpty>Couldn&apos;t load projects.</CommandEmpty>\n ) : query.isLoading ? (\n <CommandEmpty>Loading projects...</CommandEmpty>\n ) : projects.length === 0 ? (\n <CommandEmpty>No projects yet.</CommandEmpty>\n ) : (\n <CommandEmpty>No projects found.</CommandEmpty>\n )}\n <CommandGroup>\n <CommandItem value=\"__all\" keywords={['all projects']} onSelect={handleSelectAll}>\n <Icon\n name=\"check\"\n className={`size-16 mr-8 ${activeProjectId ? 'opacity-0' : 'opacity-100'}`}\n />\n All projects\n </CommandItem>\n </CommandGroup>\n {projects.length > 0 ? (\n <CommandGroup heading=\"Projects\">\n {projects.map((project) => (\n <CommandItem\n key={project.id}\n value={project.id}\n keywords={[project.name]}\n onSelect={() => handleSelect(project.id)}\n >\n <Icon\n name=\"check\"\n className={`size-16 mr-8 ${\n activeProjectId === project.id ? 'opacity-100' : 'opacity-0'\n }`}\n />\n {project.name}\n </CommandItem>\n ))}\n </CommandGroup>\n ) : null}\n </CommandList>\n <CommandSeparator />\n <CommandGroup forceMount>\n <CommandItem value=\"__create\" onSelect={handleCreate} forceMount>\n <Icon name=\"addLine\" className=\"size-16\" />\n Create project\n </CommandItem>\n </CommandGroup>\n </Command>\n );\n}\n"],"names":["Command","CommandEmpty","CommandGroup","CommandInput","CommandItem","CommandList","CommandSeparator","Icon","useNavigate","useEffect","useState","useProjectsInfiniteQuery","ProjectSwitcher","workspaceId","workspaceSlug","activeProjectId","onSelect","navigate","searchValue","setSearchValue","query","projects","data","pages","flatMap","page","hasNextPage","isFetchingNextPage","fetchNextPage","handleSelect","projectId","project","find","candidate","id","to","params","projectSlug","slug","handleSelectAll","handleCreate","normalizedSearch","trim","toLowerCase","hasSearchMatch","length","includes","some","name","value","shouldCreateFromEmptySearch","isLoading","isError","handleCommandKeyDown","event","key","preventDefault","onKeyDown","placeholder","onValueChange","keywords","className","heading","map","forceMount"],"mappings":"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"}
@@ -23,6 +23,12 @@ export interface DefinitionSyncSummary {
23
23
  finishedAt: string | null;
24
24
  lastErrorCode: string | null;
25
25
  lastErrorMessage: string | null;
26
+ warnings: DefinitionSyncWarning[];
27
+ }
28
+ export interface DefinitionSyncWarning {
29
+ code: string;
30
+ message: string;
31
+ path?: string | undefined;
26
32
  }
27
33
  export interface DefinitionList {
28
34
  definitions: Definition[];
@@ -1 +1 @@
1
- {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/core/definition.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,QAAQ,GAAG,KAAK,CAAC;IACzB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,GAAG,IAAI,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,IAAI,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACnC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B"}
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/core/definition.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,QAAQ,GAAG,KAAK,CAAC;IACzB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,GAAG,IAAI,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,qBAAqB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,IAAI,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACnC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/definition.ts"],"sourcesContent":["export interface Definition {\n id: string;\n projectId: string;\n configPath: string | null;\n source: 'manual' | 'vcs';\n sha: string | null;\n ref: string | null;\n name: string;\n workflowDocument: unknown;\n workflowModel: unknown;\n manualTrigger: {name: string} | null;\n fetchedAt: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface DefinitionSyncSummary {\n ref: string | null;\n status: 'pending' | 'syncing' | 'succeeded' | 'failed';\n lastSyncAt: string;\n startedAt: string | null;\n finishedAt: string | null;\n lastErrorCode: string | null;\n lastErrorMessage: string | null;\n}\n\nexport interface DefinitionList {\n definitions: Definition[];\n sync: DefinitionSyncSummary | null;\n nextCursor: string | null;\n}\n"],"names":[],"mappings":"AA0BA,WAIC"}
1
+ {"version":3,"sources":["../../src/core/definition.ts"],"sourcesContent":["export interface Definition {\n id: string;\n projectId: string;\n configPath: string | null;\n source: 'manual' | 'vcs';\n sha: string | null;\n ref: string | null;\n name: string;\n workflowDocument: unknown;\n workflowModel: unknown;\n manualTrigger: {name: string} | null;\n fetchedAt: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface DefinitionSyncSummary {\n ref: string | null;\n status: 'pending' | 'syncing' | 'succeeded' | 'failed';\n lastSyncAt: string;\n startedAt: string | null;\n finishedAt: string | null;\n lastErrorCode: string | null;\n lastErrorMessage: string | null;\n warnings: DefinitionSyncWarning[];\n}\n\nexport interface DefinitionSyncWarning {\n code: string;\n message: string;\n path?: string | undefined;\n}\n\nexport interface DefinitionList {\n definitions: Definition[];\n sync: DefinitionSyncSummary | null;\n nextCursor: string | null;\n}\n"],"names":[],"mappings":"AAiCA,WAIC"}
@@ -6,6 +6,7 @@ export interface Project {
6
6
  id: string;
7
7
  workspaceId: string;
8
8
  name: string;
9
+ slug: string;
9
10
  source: ProjectSource;
10
11
  createdAt: string;
11
12
  updatedAt: string;
@@ -17,8 +18,14 @@ export interface ProjectList {
17
18
  export interface CreateProjectCommand {
18
19
  workspaceId: string;
19
20
  name: string;
21
+ slug: string;
20
22
  source: ProjectSource;
21
23
  }
24
+ export interface UpdateProjectCommand {
25
+ projectId: string;
26
+ name?: string;
27
+ slug?: string;
28
+ }
22
29
  export declare function projectNameFromRepository(repositoryId: string): string;
23
30
  export declare function selectProjectSource<T extends {
24
31
  externalRepositoryId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/core/project.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;CACvB;AAID,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAOtE;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS;IAAC,oBAAoB,EAAE,MAAM,CAAA;CAAC,EAC1E,YAAY,EAAE,CAAC,EAAE,EACjB,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC,CAAC,GAAG,SAAS,CAOf"}
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/core/project.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAID,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAOtE;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS;IAAC,oBAAoB,EAAE,MAAM,CAAA;CAAC,EAC1E,YAAY,EAAE,CAAC,EAAE,EACjB,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC,CAAC,GAAG,SAAS,CAOf"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/project.ts"],"sourcesContent":["export interface ProjectSource {\n connectionId: string;\n externalRepositoryId: string;\n}\n\nexport interface Project {\n id: string;\n workspaceId: string;\n name: string;\n source: ProjectSource;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ProjectList {\n projects: Project[];\n nextCursor: string | null;\n}\n\nexport interface CreateProjectCommand {\n workspaceId: string;\n name: string;\n source: ProjectSource;\n}\n\nconst REPOSITORY_NAME_SPLIT_RE = /[/-]/;\n\nexport function projectNameFromRepository(repositoryId: string): string {\n return repositoryId\n .trim()\n .split(REPOSITORY_NAME_SPLIT_RE)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ');\n}\n\nexport function selectProjectSource<T extends {externalRepositoryId: string}>(\n repositories: T[],\n selectedRepositoryId: string | undefined,\n): T | undefined {\n if (selectedRepositoryId) {\n return repositories.find(\n (repository) => repository.externalRepositoryId === selectedRepositoryId,\n );\n }\n return repositories[0];\n}\n"],"names":["REPOSITORY_NAME_SPLIT_RE","projectNameFromRepository","repositoryId","trim","split","filter","Boolean","map","part","charAt","toUpperCase","slice","join","selectProjectSource","repositories","selectedRepositoryId","find","repository","externalRepositoryId"],"mappings":"AAyBA,MAAMA,2BAA2B;AAEjC,OAAO,SAASC,0BAA0BC,YAAoB;IAC5D,OAAOA,aACJC,IAAI,GACJC,KAAK,CAACJ,0BACNK,MAAM,CAACC,SACPC,GAAG,CAAC,CAACC,OAASA,KAAKC,MAAM,CAAC,GAAGC,WAAW,KAAKF,KAAKG,KAAK,CAAC,IACxDC,IAAI,CAAC;AACV;AAEA,OAAO,SAASC,oBACdC,YAAiB,EACjBC,oBAAwC;IAExC,IAAIA,sBAAsB;QACxB,OAAOD,aAAaE,IAAI,CACtB,CAACC,aAAeA,WAAWC,oBAAoB,KAAKH;IAExD;IACA,OAAOD,YAAY,CAAC,EAAE;AACxB"}
1
+ {"version":3,"sources":["../../src/core/project.ts"],"sourcesContent":["export interface ProjectSource {\n connectionId: string;\n externalRepositoryId: string;\n}\n\nexport interface Project {\n id: string;\n workspaceId: string;\n name: string;\n slug: string;\n source: ProjectSource;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ProjectList {\n projects: Project[];\n nextCursor: string | null;\n}\n\nexport interface CreateProjectCommand {\n workspaceId: string;\n name: string;\n slug: string;\n source: ProjectSource;\n}\n\nexport interface UpdateProjectCommand {\n projectId: string;\n name?: string;\n slug?: string;\n}\n\nconst REPOSITORY_NAME_SPLIT_RE = /[/-]/;\n\nexport function projectNameFromRepository(repositoryId: string): string {\n return repositoryId\n .trim()\n .split(REPOSITORY_NAME_SPLIT_RE)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ');\n}\n\nexport function selectProjectSource<T extends {externalRepositoryId: string}>(\n repositories: T[],\n selectedRepositoryId: string | undefined,\n): T | undefined {\n if (selectedRepositoryId) {\n return repositories.find(\n (repository) => repository.externalRepositoryId === selectedRepositoryId,\n );\n }\n return repositories[0];\n}\n"],"names":["REPOSITORY_NAME_SPLIT_RE","projectNameFromRepository","repositoryId","trim","split","filter","Boolean","map","part","charAt","toUpperCase","slice","join","selectProjectSource","repositories","selectedRepositoryId","find","repository","externalRepositoryId"],"mappings":"AAiCA,MAAMA,2BAA2B;AAEjC,OAAO,SAASC,0BAA0BC,YAAoB;IAC5D,OAAOA,aACJC,IAAI,GACJC,KAAK,CAACJ,0BACNK,MAAM,CAACC,SACPC,GAAG,CAAC,CAACC,OAASA,KAAKC,MAAM,CAAC,GAAGC,WAAW,KAAKF,KAAKG,KAAK,CAAC,IACxDC,IAAI,CAAC;AACV;AAEA,OAAO,SAASC,oBACdC,YAAiB,EACjBC,oBAAwC;IAExC,IAAIA,sBAAsB;QACxB,OAAOD,aAAaE,IAAI,CACtB,CAACC,aAAeA,WAAWC,oBAAoB,KAAKH;IAExD;IACA,OAAOD,YAAY,CAAC,EAAE;AACxB"}
package/dist/feature.d.ts CHANGED
@@ -2,32 +2,56 @@ export declare const projectsNavigation: readonly [{
2
2
  readonly id: "nav.projects";
3
3
  readonly scope: "workspace";
4
4
  readonly label: "Projects";
5
- readonly to: "/workspaces/$wid";
5
+ readonly to: "/w/$workspaceSlug";
6
6
  readonly exact: true;
7
7
  readonly order: 100;
8
8
  }];
9
+ export declare const projectSettingsSections: readonly [{
10
+ readonly id: "settings.project-general";
11
+ readonly scope: "project";
12
+ readonly pathSegment: "general";
13
+ readonly label: "General";
14
+ readonly icon: "settings3Line";
15
+ readonly order: 50;
16
+ }];
9
17
  export declare const projectsFeature: {
10
18
  readonly id: "shipfox.projects";
11
19
  readonly routes: readonly [{
12
- readonly path: "/workspaces/$wid";
20
+ readonly path: "/w/$workspaceSlug";
13
21
  readonly parent: "workspaceLayout";
14
22
  readonly impl: "@shipfox/client-projects/routes/home";
15
23
  }, {
16
- readonly path: "/workspaces/$wid/projects/new";
24
+ readonly path: "/w/$workspaceSlug/projects/new";
17
25
  readonly parent: "workspaceLayout";
18
26
  readonly impl: "@shipfox/client-projects/routes/create-project";
19
27
  }, {
20
- readonly path: "/workspaces/$wid/projects/$pid";
28
+ readonly path: "/w/$workspaceSlug/p/$projectSlug";
21
29
  readonly parent: "projectLayout";
22
30
  readonly impl: "@shipfox/client-projects/routes/project-index";
31
+ }, {
32
+ readonly path: "/w/$workspaceSlug/p/$projectSlug/settings";
33
+ readonly parent: "projectSettings";
34
+ readonly impl: "@shipfox/client-projects/routes/project-settings-index";
35
+ }, {
36
+ readonly path: "/w/$workspaceSlug/p/$projectSlug/settings/general";
37
+ readonly parent: "projectSettings";
38
+ readonly impl: "@shipfox/client-projects/routes/project-settings";
23
39
  }];
24
40
  readonly navigation: readonly [{
25
41
  readonly id: "nav.projects";
26
42
  readonly scope: "workspace";
27
43
  readonly label: "Projects";
28
- readonly to: "/workspaces/$wid";
44
+ readonly to: "/w/$workspaceSlug";
29
45
  readonly exact: true;
30
46
  readonly order: 100;
31
47
  }];
48
+ readonly settingsSections: readonly [{
49
+ readonly id: "settings.project-general";
50
+ readonly scope: "project";
51
+ readonly pathSegment: "general";
52
+ readonly label: "General";
53
+ readonly icon: "settings3Line";
54
+ readonly order: 50;
55
+ }];
32
56
  };
33
57
  //# sourceMappingURL=feature.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../src/feature.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kBAAkB;;;;;;;EASY,CAAC;AAE5C,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;CAoB1B,CAAC"}
1
+ {"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../src/feature.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kBAAkB;;;;;;;EASY,CAAC;AAE5C,eAAO,MAAM,uBAAuB;;;;;;;EAS1B,CAAC;AAEX,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+B1B,CAAC"}