@shipfox/client-projects 22.0.3 → 23.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/client-projects",
3
3
  "license": "MIT",
4
- "version": "22.0.3",
4
+ "version": "23.0.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -28,14 +28,14 @@
28
28
  "@swc/helpers": "^0.5.17",
29
29
  "@tanstack/react-form": "^1.32.0",
30
30
  "@shipfox/api-common-dto": "12.0.0",
31
- "@shipfox/api-definitions-dto": "12.3.0",
32
- "@shipfox/api-integration-core-dto": "12.2.0",
33
- "@shipfox/api-projects-dto": "12.2.0",
34
- "@shipfox/client-agent": "22.0.3",
31
+ "@shipfox/api-definitions-dto": "14.0.0",
32
+ "@shipfox/api-integration-core-dto": "14.0.0",
33
+ "@shipfox/api-projects-dto": "14.0.0",
34
+ "@shipfox/client-agent": "23.0.0",
35
35
  "@shipfox/client-api": "6.0.1",
36
- "@shipfox/client-auth": "22.0.3",
37
- "@shipfox/client-integrations": "22.0.3",
38
- "@shipfox/client-shell": "22.0.3",
36
+ "@shipfox/client-auth": "23.0.0",
37
+ "@shipfox/client-integrations": "23.0.0",
38
+ "@shipfox/client-shell": "23.0.0",
39
39
  "@shipfox/client-ui": "22.0.3",
40
40
  "@shipfox/react-ui": "2.2.0"
41
41
  },
@@ -22,13 +22,15 @@ export interface DefinitionSyncSummary {
22
22
  finishedAt: string | null;
23
23
  lastErrorCode: string | null;
24
24
  lastErrorMessage: string | null;
25
- warnings: DefinitionSyncWarning[];
25
+ diagnostics: DefinitionSyncDiagnostic[];
26
26
  }
27
27
 
28
- export interface DefinitionSyncWarning {
28
+ export interface DefinitionSyncDiagnostic {
29
29
  code: string;
30
30
  message: string;
31
31
  path?: string | undefined;
32
+ filePath?: string | undefined;
33
+ severity: 'error' | 'warning';
32
34
  }
33
35
 
34
36
  export interface DefinitionList {
@@ -1,7 +1,7 @@
1
1
  import {toDefinitionSyncSummary} from './mappers.js';
2
2
 
3
3
  describe('toDefinitionSyncSummary', () => {
4
- it('preserves warnings without manufacturing a missing path', () => {
4
+ it('maps diagnostics with severity and file path while preserving an absent path', () => {
5
5
  const summary = toDefinitionSyncSummary({
6
6
  ref: 'main',
7
7
  status: 'succeeded',
@@ -10,10 +10,28 @@ describe('toDefinitionSyncSummary', () => {
10
10
  finished_at: '2026-05-07T01:00:00.000Z',
11
11
  last_error_code: null,
12
12
  last_error_message: null,
13
- warnings: [{code: 'warning-code', message: 'Warning without a path'}],
13
+ diagnostics: [
14
+ {code: 'warning-code', message: 'Warning without a path', severity: 'warning'},
15
+ {
16
+ code: 'error-code',
17
+ message: 'Trigger error with a path',
18
+ path: 'triggers.on_demand',
19
+ file_path: '.shipfox/workflows/deploy.yml',
20
+ severity: 'error',
21
+ },
22
+ ],
14
23
  });
15
24
 
16
- expect(summary.warnings).toEqual([{code: 'warning-code', message: 'Warning without a path'}]);
17
- expect(summary.warnings[0]).not.toHaveProperty('path');
25
+ expect(summary.diagnostics).toEqual([
26
+ {code: 'warning-code', message: 'Warning without a path', severity: 'warning'},
27
+ {
28
+ code: 'error-code',
29
+ message: 'Trigger error with a path',
30
+ path: 'triggers.on_demand',
31
+ filePath: '.shipfox/workflows/deploy.yml',
32
+ severity: 'error',
33
+ },
34
+ ]);
35
+ expect(summary.diagnostics[0]).not.toHaveProperty('path');
18
36
  });
19
37
  });
@@ -52,10 +52,12 @@ export function toDefinitionSyncSummary(dto: DefinitionSyncSummaryDto): Definiti
52
52
  finishedAt: dto.finished_at,
53
53
  lastErrorCode: dto.last_error_code,
54
54
  lastErrorMessage: dto.last_error_message,
55
- warnings: dto.warnings.map((warning) => ({
56
- code: warning.code,
57
- message: warning.message,
58
- ...(warning.path === undefined ? {} : {path: warning.path}),
55
+ diagnostics: dto.diagnostics.map((diagnostic) => ({
56
+ code: diagnostic.code,
57
+ message: diagnostic.message,
58
+ severity: diagnostic.severity,
59
+ ...(diagnostic.path === undefined ? {} : {path: diagnostic.path}),
60
+ ...(diagnostic.file_path === undefined ? {} : {filePath: diagnostic.file_path}),
59
61
  })),
60
62
  };
61
63
  }
package/src/index.ts CHANGED
@@ -1,4 +1,9 @@
1
- export type {Definition, DefinitionList, DefinitionSyncSummary} from '#core/definition.js';
1
+ export type {
2
+ Definition,
3
+ DefinitionList,
4
+ DefinitionSyncDiagnostic,
5
+ DefinitionSyncSummary,
6
+ } from '#core/definition.js';
2
7
  export {
3
8
  type CreateProjectCommand,
4
9
  type Project,
@@ -1,5 +1,6 @@
1
1
  import {configureApiClient} from '@shipfox/client-api';
2
2
  import {type AuthState, authStateAtom} from '@shipfox/client-auth';
3
+ import {ChromeProvider} from '@shipfox/client-shell/runtime';
3
4
  import {Toaster} from '@shipfox/react-ui/toast';
4
5
  import type {Meta, StoryObj} from '@storybook/react';
5
6
  import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
@@ -50,16 +51,23 @@ function ProjectsHubPageStory({scenario}: {scenario: Scenario}) {
50
51
  const router = useMemo(() => createStoryRouter(), []);
51
52
 
52
53
  return (
53
- <QueryClientProvider client={queryClient}>
54
- <JotaiProvider store={store}>
55
- <div className="min-h-screen bg-background-subtle-base px-24 py-32">
56
- <div className="mx-auto w-full max-w-[1120px]">
57
- <RouterProvider router={router} />
54
+ <ChromeProvider
55
+ chrome={{
56
+ ProjectBreadcrumb: () => null,
57
+ projectSlugResolver: async () => undefined,
58
+ }}
59
+ >
60
+ <QueryClientProvider client={queryClient}>
61
+ <JotaiProvider store={store}>
62
+ <div className="min-h-screen bg-background-subtle-base px-24 py-32">
63
+ <div className="mx-auto w-full max-w-[1120px]">
64
+ <RouterProvider router={router} />
65
+ </div>
58
66
  </div>
59
- </div>
60
- <Toaster />
61
- </JotaiProvider>
62
- </QueryClientProvider>
67
+ <Toaster />
68
+ </JotaiProvider>
69
+ </QueryClientProvider>
70
+ </ChromeProvider>
63
71
  );
64
72
  }
65
73
 
@@ -12,11 +12,36 @@ const NEW_PROJECT_REGEX = /New project/i;
12
12
  const WORKSPACE_PROJECTS_NEW_HREF = `/w/${PROJECT_TEST_WSLUG}/projects/new`;
13
13
  const CONNECTION_ID = '33333333-3333-4333-8333-333333333333';
14
14
 
15
+ function StubWorkspaceSetupChecklist() {
16
+ return <section aria-label="Workspace setup checklist">Workspace setup checklist</section>;
17
+ }
18
+
15
19
  describe('ProjectsHubPage', () => {
16
20
  beforeEach(() => {
17
21
  window.sessionStorage.clear();
18
22
  });
19
23
 
24
+ test('renders the workspace setup checklist before the projects panel', async () => {
25
+ configureApiClient({
26
+ fetchImpl: createHubFetch({
27
+ projects: jsonResponse({projects: [], next_cursor: null}),
28
+ modelProviders: jsonResponse(modelProviderConfigsDto()),
29
+ }),
30
+ });
31
+
32
+ renderProjectPage(`/w/${PROJECT_TEST_WSLUG}`, <ProjectsHubPage />, undefined, {
33
+ WorkspaceSetupChecklist: StubWorkspaceSetupChecklist,
34
+ });
35
+
36
+ expect(await screen.findByText('Create your first project')).toBeInTheDocument();
37
+ const checklist = screen.getByRole('region', {name: 'Workspace setup checklist'});
38
+ const projects = screen.getByRole('region', {name: 'Projects'});
39
+
40
+ expect(checklist.compareDocumentPosition(projects) & Node.DOCUMENT_POSITION_FOLLOWING).toBe(
41
+ Node.DOCUMENT_POSITION_FOLLOWING,
42
+ );
43
+ });
44
+
20
45
  test('renders the Projects panel controls and empty state with create CTA', async () => {
21
46
  configureApiClient({
22
47
  fetchImpl: createHubFetch({
@@ -5,6 +5,7 @@ import {
5
5
  IntegrationIcon,
6
6
  useIntegrationConnectionsQuery,
7
7
  } from '@shipfox/client-integrations';
8
+ import {useChrome} from '@shipfox/client-shell/runtime';
8
9
  import {QueryLoadError} from '@shipfox/client-ui';
9
10
  import {Button} from '@shipfox/react-ui/button';
10
11
  import {Callout} from '@shipfox/react-ui/callout';
@@ -29,6 +30,7 @@ import {useProjectsInfiniteQuery} from '#hooks/api/projects.js';
29
30
 
30
31
  export function ProjectsHubPage({search = ''}: {search?: string}) {
31
32
  const workspace = useActiveWorkspace();
33
+ const {WorkspaceSetupChecklist} = useChrome();
32
34
  const navigate = useNavigate();
33
35
  const query = useProjectsInfiniteQuery(workspace.id, search || undefined);
34
36
  const projects = query.data?.pages.flatMap((page) => page.projects) ?? [];
@@ -49,6 +51,7 @@ export function ProjectsHubPage({search = ''}: {search?: string}) {
49
51
 
50
52
  return (
51
53
  <div className="flex w-full flex-col gap-section">
54
+ {WorkspaceSetupChecklist ? <WorkspaceSetupChecklist /> : null}
52
55
  <ModelProviderReminderBanner workspaceId={workspace.id} />
53
56
 
54
57
  <section aria-label="Projects">
package/test/pages.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import {configureApiClient} from '@shipfox/client-api';
2
2
  import {type AuthState, authStateAtom} from '@shipfox/client-auth';
3
+ import {ChromeProvider, type ChromeSlots} from '@shipfox/client-shell/runtime';
3
4
  import {Toaster} from '@shipfox/react-ui/toast';
4
5
  import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
5
6
  import {
@@ -122,19 +123,27 @@ export function renderProjectPage(
122
123
  path: string,
123
124
  element: ReactElement,
124
125
  queryClient = new QueryClient({defaultOptions: {queries: {retry: false}}}),
126
+ chromeOverrides: Partial<ChromeSlots> = {},
125
127
  ): RenderResult {
126
128
  const router = createTestRouter(path, element);
127
129
  const store = createStore();
128
130
  store.set(authStateAtom, authState);
131
+ const chrome: ChromeSlots = {
132
+ ProjectBreadcrumb: () => null,
133
+ projectSlugResolver: async () => undefined,
134
+ ...chromeOverrides,
135
+ };
129
136
 
130
137
  configureApiClient({baseUrl: 'https://api.example.test'});
131
138
 
132
139
  return render(
133
- <QueryClientProvider client={queryClient}>
134
- <JotaiProvider store={store}>
135
- <RouterProvider router={router} />
136
- <Toaster />
137
- </JotaiProvider>
138
- </QueryClientProvider>,
140
+ <ChromeProvider chrome={chrome}>
141
+ <QueryClientProvider client={queryClient}>
142
+ <JotaiProvider store={store}>
143
+ <RouterProvider router={router} />
144
+ <Toaster />
145
+ </JotaiProvider>
146
+ </QueryClientProvider>
147
+ </ChromeProvider>,
139
148
  );
140
149
  }