@happyvertical/smrt-projects 0.40.65 → 0.40.67

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 (43) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +489 -420
  4. package/dist/index.js.map +1 -1
  5. package/dist/managed-client.d.ts +9 -0
  6. package/dist/managed-client.d.ts.map +1 -1
  7. package/dist/manifest.json +1 -1
  8. package/dist/playground.js +48 -3
  9. package/dist/playground.js.map +1 -1
  10. package/dist/project-board-types.d.ts +12 -0
  11. package/dist/project-board-types.d.ts.map +1 -0
  12. package/dist/project-board-types.js +0 -0
  13. package/dist/services/delivery-service.d.ts +1 -1
  14. package/dist/services/delivery-service.d.ts.map +1 -1
  15. package/dist/services/index.d.ts +1 -0
  16. package/dist/services/index.d.ts.map +1 -1
  17. package/dist/services/project-board-service.d.ts +32 -0
  18. package/dist/services/project-board-service.d.ts.map +1 -0
  19. package/dist/smrt-knowledge.json +1916 -91
  20. package/dist/svelte/DevelopmentBoard.svelte +54 -74
  21. package/dist/svelte/DevelopmentBoard.svelte.d.ts.map +1 -1
  22. package/dist/svelte/ProjectBoard.svelte +157 -0
  23. package/dist/svelte/ProjectBoard.svelte.d.ts +32 -0
  24. package/dist/svelte/ProjectBoard.svelte.d.ts.map +1 -0
  25. package/dist/svelte/components/__tests__/ProjectBoard.test.js +156 -0
  26. package/dist/svelte/i18n.d.ts +2 -0
  27. package/dist/svelte/i18n.d.ts.map +1 -1
  28. package/dist/svelte/i18n.js +2 -0
  29. package/dist/svelte/index.d.ts +4 -1
  30. package/dist/svelte/index.d.ts.map +1 -1
  31. package/dist/svelte/index.js +3 -1
  32. package/dist/svelte/playground.d.ts +99 -0
  33. package/dist/svelte/playground.d.ts.map +1 -1
  34. package/dist/svelte/playground.js +49 -3
  35. package/dist/svelte/project-board-types.d.ts +3 -0
  36. package/dist/svelte/project-board-types.d.ts.map +1 -0
  37. package/dist/svelte/project-board-types.js +1 -0
  38. package/dist/types.d.ts +1 -1
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/ui.d.ts.map +1 -1
  41. package/dist/ui.js +10 -1
  42. package/dist/ui.js.map +1 -1
  43. package/package.json +17 -16
@@ -1,8 +1,18 @@
1
1
  <script lang="ts">
2
+ import {
3
+ Board,
4
+ type BoardCard,
5
+ type BoardColumn,
6
+ } from '@happyvertical/smrt-svelte/board';
2
7
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
8
  import type { DevelopmentRequestView } from './delivery-types.js';
4
9
  import { M } from './i18n.js';
5
10
 
11
+ interface DevelopmentBoardCard extends BoardCard {
12
+ request: DevelopmentRequestView;
13
+ columnId: string;
14
+ }
15
+
6
16
  export interface Props {
7
17
  requests?: DevelopmentRequestView[];
8
18
  columns?: string[];
@@ -15,89 +25,59 @@ let {
15
25
  onselect,
16
26
  }: Props = $props();
17
27
  const { t } = useI18n();
18
- const inColumn = (column: string) =>
19
- requests.filter(
20
- (request) =>
21
- (request.deliveryStatus || request.status).toLowerCase() ===
22
- column.toLowerCase(),
23
- );
28
+ const boardColumns = $derived<BoardColumn[]>(
29
+ columns.map((column) => ({
30
+ id: column.toLowerCase(),
31
+ label: column.replaceAll('_', ' '),
32
+ })),
33
+ );
34
+ const cards = $derived<DevelopmentBoardCard[]>(
35
+ requests.map((request) => ({
36
+ id: request.id,
37
+ request,
38
+ columnId: (request.deliveryStatus || request.status).toLowerCase(),
39
+ })),
40
+ );
41
+ const cardLabel = (card: DevelopmentBoardCard) =>
42
+ card.request.title ?? card.request.description;
24
43
  </script>
25
44
 
26
- <section class="board" aria-label={t(M['projects.development_board.aria'])}>
27
- {#if requests.length === 0}
28
- <p class="empty">{t(M['projects.development_board.empty'])}</p>
29
- {:else}
30
- {#each columns as column (column)}
31
- <div class="lane">
32
- <header>
33
- <h3>{column.replaceAll('_', ' ')}</h3>
34
- <span>{inColumn(column).length}</span>
35
- </header>
36
- <div class="lane__items">
37
- {#each inColumn(column) as request (request.id)}
38
- <!-- raw-primitive-allow: each board row is a semantic selection control -->
39
- <button type="button" onclick={() => onselect?.(request)}>
40
- <strong>{request.title ?? request.description}</strong>
41
- <small>
42
- {request.type}
43
- {request.requesterLabel ? ` · ${request.requesterLabel}` : ''}
44
- </small>
45
- </button>
46
- {/each}
47
- </div>
48
- </div>
49
- {/each}
50
- {/if}
51
- </section>
45
+ {#if requests.length === 0}
46
+ <section
47
+ class="empty"
48
+ aria-label={t(M['projects.development_board.aria'])}
49
+ >
50
+ <p>{t(M['projects.development_board.empty'])}</p>
51
+ </section>
52
+ {:else}
53
+ <Board
54
+ columns={boardColumns}
55
+ {cards}
56
+ label={t(M['projects.development_board.aria'])}
57
+ getCardColumnId={(card) => card.columnId}
58
+ setCardColumnId={(card, columnId) => ({ ...card, columnId })}
59
+ getCardLabel={cardLabel}
60
+ onselect={(card) => onselect?.(card.request)}
61
+ >
62
+ {#snippet card({ card })}
63
+ <strong>{card.request.title ?? card.request.description}</strong>
64
+ <small>
65
+ {card.request.type}
66
+ {card.request.requesterLabel
67
+ ? ` · ${card.request.requesterLabel}`
68
+ : ''}
69
+ </small>
70
+ {/snippet}
71
+ </Board>
72
+ {/if}
52
73
 
53
74
  <style>
54
- .board {
55
- display: grid;
56
- gap: var(--smrt-spacing-5);
57
- grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
58
- overflow-x: auto;
59
- }
60
- .lane {
61
- min-width: 0;
62
- }
63
75
  .empty {
64
76
  color: var(--smrt-color-on-surface-variant);
65
- grid-column: 1 / -1;
66
77
  margin: 0;
67
78
  padding: var(--smrt-spacing-5) var(--smrt-spacing-1);
68
79
  }
69
- .lane header {
70
- align-items: center;
71
- border-bottom: 2px solid var(--smrt-color-primary);
72
- display: flex;
73
- justify-content: space-between;
74
- padding: var(--smrt-spacing-2) var(--smrt-spacing-1);
75
- }
76
- .lane h3 {
77
- font-size: var(--smrt-typography-label-medium-size);
78
- margin: 0;
79
- text-transform: uppercase;
80
- }
81
- .lane__items {
82
- display: grid;
83
- }
84
- .lane__items button {
85
- background: transparent;
86
- border: 0;
87
- border-bottom: 1px solid var(--smrt-color-outline-variant);
88
- color: inherit;
89
- cursor: pointer;
90
- display: grid;
91
- gap: var(--smrt-spacing-1);
92
- padding: var(--smrt-spacing-4) var(--smrt-spacing-1);
93
- text-align: left;
94
- transition: transform 120ms ease;
95
- }
96
- .lane__items button:hover,
97
- .lane__items button:focus-visible {
98
- transform: translateX(var(--smrt-spacing-1));
99
- }
100
- .lane small {
80
+ small {
101
81
  color: var(--smrt-color-on-surface-variant);
102
82
  }
103
83
  </style>
@@ -1 +1 @@
1
- {"version":3,"file":"DevelopmentBoard.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/DevelopmentBoard.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAIlE,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACtD;AAoDD,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"DevelopmentBoard.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/DevelopmentBoard.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AASlE,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACtD;AAsDD,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,157 @@
1
+ <script lang="ts">
2
+ import {
3
+ Board,
4
+ type BoardCard,
5
+ type BoardColumn,
6
+ type BoardMoveIntent,
7
+ } from '@happyvertical/smrt-svelte/board';
8
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
9
+ import type { ProjectItem, ProjectStatus } from '../types.js';
10
+ import { M } from './i18n.js';
11
+ import type { ProjectBoardMoveIntent } from './project-board-types.js';
12
+
13
+ interface ProjectBoardColumn extends BoardColumn {
14
+ status?: string;
15
+ }
16
+
17
+ interface ProjectBoardCard extends BoardCard {
18
+ item: ProjectItem;
19
+ columnId: string;
20
+ }
21
+
22
+ interface ProjectBoardBaseProps {
23
+ /** Provider project identity, forwarded only to the injected move action. */
24
+ projectId: string;
25
+ /** Canonical, ordered provider statuses. Their supplied order is preserved. */
26
+ statuses: readonly ProjectStatus[];
27
+ /** Authoritative provider items; this adapter never reorders them locally. */
28
+ items: readonly ProjectItem[];
29
+ onselect?: (item: ProjectItem) => void;
30
+ label?: string;
31
+ }
32
+
33
+ interface ReadOnlyProjectBoardProps {
34
+ /** Omit both callbacks to expose a read-only controlled board. */
35
+ onmove?: never;
36
+ onrefresh?: never;
37
+ }
38
+
39
+ interface MovableProjectBoardProps {
40
+ /**
41
+ * Browser-safe mutation boundary. Its consumer attaches authorization in a
42
+ * server action before it calls ProjectBoardService.
43
+ */
44
+ onmove: (intent: ProjectBoardMoveIntent) => void | Promise<void>;
45
+ /** Required reconciliation of controlled cards after every move attempt. */
46
+ onrefresh: () => void | Promise<void>;
47
+ }
48
+
49
+ /** A controlled project board is movable only with an authoritative refresh. */
50
+ export type Props = ProjectBoardBaseProps &
51
+ (ReadOnlyProjectBoardProps | MovableProjectBoardProps);
52
+
53
+ let { projectId, statuses, items, onmove, onrefresh, onselect, label }: Props =
54
+ $props();
55
+
56
+ const { t } = useI18n();
57
+ const unmatchedColumnId = '__smrt_projects_unmatched__';
58
+ const statusByName = $derived(
59
+ new Map(statuses.map((status) => [status.name, status])),
60
+ );
61
+ const hasUnmatchedItems = $derived(
62
+ items.some((item) => !statusByName.has(item.status ?? '')),
63
+ );
64
+ const movementEnabled = $derived(
65
+ onmove !== undefined && onrefresh !== undefined,
66
+ );
67
+ const columns = $derived<ProjectBoardColumn[]>([
68
+ ...statuses.map((status) => ({
69
+ id: status.id,
70
+ label: status.name,
71
+ status: status.name,
72
+ })),
73
+ ...(hasUnmatchedItems
74
+ ? [
75
+ {
76
+ id: unmatchedColumnId,
77
+ label: t(M['projects.project_board.unassigned']),
78
+ disabled: true,
79
+ },
80
+ ]
81
+ : []),
82
+ ]);
83
+ const cards = $derived<ProjectBoardCard[]>(
84
+ items.map((item) => ({
85
+ id: item.id,
86
+ item,
87
+ columnId: statusByName.has(item.status ?? '')
88
+ ? (statusByName.get(item.status ?? '')?.id ?? unmatchedColumnId)
89
+ : unmatchedColumnId,
90
+ })),
91
+ );
92
+
93
+ async function refresh(): Promise<void> {
94
+ if (!onrefresh) throw new Error('Project board refresh is required.');
95
+ await onrefresh();
96
+ }
97
+
98
+ async function move(
99
+ intent: BoardMoveIntent<ProjectBoardCard, ProjectBoardColumn>,
100
+ ): Promise<void> {
101
+ const target = columns.find((column) => column.id === intent.target.columnId);
102
+ if (!target?.status || target.disabled || !onmove || !onrefresh) {
103
+ throw new Error('Project board move failed.');
104
+ }
105
+
106
+ try {
107
+ await onmove({
108
+ projectId,
109
+ itemId: intent.card.item.id,
110
+ status: target.status,
111
+ });
112
+ await refresh();
113
+ } catch {
114
+ // Controlled cards never become local truth. Re-fetch even when the
115
+ // mutation fails so a provider-side partial success is corrected.
116
+ try {
117
+ await refresh();
118
+ } catch {
119
+ // The original operation's failure is intentionally not exposed to the
120
+ // browser; it may contain provider details.
121
+ }
122
+ // Board owns the single live-region announcement and focus restoration.
123
+ throw new Error('Project board move failed.');
124
+ }
125
+ }
126
+
127
+ function cardLabel(card: ProjectBoardCard): string {
128
+ return card.item.title ?? card.item.id;
129
+ }
130
+ </script>
131
+
132
+ <Board
133
+ {columns}
134
+ {cards}
135
+ label={label ?? t(M['projects.project_board.aria'])}
136
+ getCardColumnId={(card) => card.columnId}
137
+ setCardColumnId={(card, columnId) => ({ ...card, columnId })}
138
+ getCardLabel={cardLabel}
139
+ allowSameColumnReorder={false}
140
+ onmove={movementEnabled ? move : undefined}
141
+ onselect={(card) => onselect?.(card.item)}
142
+ >
143
+ {#snippet card({ card })}
144
+ <!-- raw-primitive-allow: each board card is a semantic project selection control -->
145
+ <strong>{card.item.title ?? card.item.id}</strong>
146
+ <small>{card.item.type}</small>
147
+ {#if card.item.assignees?.length}
148
+ <small>{card.item.assignees.join(', ')}</small>
149
+ {/if}
150
+ {/snippet}
151
+ </Board>
152
+
153
+ <style>
154
+ small {
155
+ color: var(--smrt-color-on-surface-variant);
156
+ }
157
+ </style>
@@ -0,0 +1,32 @@
1
+ import type { ProjectItem, ProjectStatus } from '../types.js';
2
+ import type { ProjectBoardMoveIntent } from './project-board-types.js';
3
+ interface ProjectBoardBaseProps {
4
+ /** Provider project identity, forwarded only to the injected move action. */
5
+ projectId: string;
6
+ /** Canonical, ordered provider statuses. Their supplied order is preserved. */
7
+ statuses: readonly ProjectStatus[];
8
+ /** Authoritative provider items; this adapter never reorders them locally. */
9
+ items: readonly ProjectItem[];
10
+ onselect?: (item: ProjectItem) => void;
11
+ label?: string;
12
+ }
13
+ interface ReadOnlyProjectBoardProps {
14
+ /** Omit both callbacks to expose a read-only controlled board. */
15
+ onmove?: never;
16
+ onrefresh?: never;
17
+ }
18
+ interface MovableProjectBoardProps {
19
+ /**
20
+ * Browser-safe mutation boundary. Its consumer attaches authorization in a
21
+ * server action before it calls ProjectBoardService.
22
+ */
23
+ onmove: (intent: ProjectBoardMoveIntent) => void | Promise<void>;
24
+ /** Required reconciliation of controlled cards after every move attempt. */
25
+ onrefresh: () => void | Promise<void>;
26
+ }
27
+ /** A controlled project board is movable only with an authoritative refresh. */
28
+ export type Props = ProjectBoardBaseProps & (ReadOnlyProjectBoardProps | MovableProjectBoardProps);
29
+ declare const ProjectBoard: import("svelte").Component<Props, {}, "">;
30
+ type ProjectBoard = ReturnType<typeof ProjectBoard>;
31
+ export default ProjectBoard;
32
+ //# sourceMappingURL=ProjectBoard.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectBoard.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/ProjectBoard.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAYvE,UAAU,qBAAqB;IAC7B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,8EAA8E;IAC9E,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,yBAAyB;IACjC,kEAAkE;IAClE,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB;AAED,UAAU,wBAAwB;IAChC;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,4EAA4E;IAC5E,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,gFAAgF;AAChF,MAAM,MAAM,KAAK,GAAG,qBAAqB,GACvC,CAAC,yBAAyB,GAAG,wBAAwB,CAAC,CAAC;AA4GzD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,156 @@
1
+ // @vitest-environment jsdom
2
+ import { expectNoA11yViolations, fireEvent, render, screen, userEvent, } from '@happyvertical/smrt-vitest/svelte';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+ import ProjectBoard from '../../ProjectBoard.svelte';
5
+ const statuses = [
6
+ { id: 'todo', name: 'Todo', order: 0 },
7
+ { id: 'done', name: 'Done', order: 1 },
8
+ ];
9
+ const items = [
10
+ {
11
+ id: 'item-1',
12
+ contentId: 'content-1',
13
+ title: 'Publish release notes',
14
+ status: 'Todo',
15
+ fields: {},
16
+ type: 'Issue',
17
+ },
18
+ {
19
+ id: 'item-2',
20
+ contentId: 'content-2',
21
+ title: 'Stale provider status',
22
+ status: 'Provider-only',
23
+ fields: {},
24
+ type: 'DraftIssue',
25
+ },
26
+ ];
27
+ const sameStatusItems = [
28
+ items[0],
29
+ {
30
+ id: 'item-3',
31
+ contentId: 'content-3',
32
+ title: 'Review release notes',
33
+ status: 'Todo',
34
+ fields: {},
35
+ type: 'Issue',
36
+ },
37
+ ];
38
+ describe('ProjectBoard', () => {
39
+ it('does not persist same-status keyboard or pointer reordering', async () => {
40
+ const onmove = vi.fn().mockResolvedValue(undefined);
41
+ const onrefresh = vi.fn().mockResolvedValue(undefined);
42
+ render(ProjectBoard, {
43
+ props: {
44
+ projectId: 'project-1',
45
+ statuses,
46
+ items: sameStatusItems,
47
+ onmove,
48
+ onrefresh,
49
+ },
50
+ });
51
+ const first = screen.getByRole('button', {
52
+ name: /Publish release notes/,
53
+ });
54
+ const second = screen.getByRole('button', {
55
+ name: /Review release notes/,
56
+ });
57
+ first.focus();
58
+ await userEvent.keyboard(' ');
59
+ await userEvent.keyboard('{ArrowDown}');
60
+ await userEvent.keyboard(' ');
61
+ fireEvent.dragStart(first, { dataTransfer: { setData: vi.fn() } });
62
+ fireEvent.drop(second, { clientY: 999 });
63
+ expect(onmove).not.toHaveBeenCalled();
64
+ expect(onrefresh).not.toHaveBeenCalled();
65
+ expect(screen.queryByText(/Moved Publish release notes to Todo/i)).not.toBeInTheDocument();
66
+ });
67
+ it('stays read-only when a browser move callback has no authoritative refresh', async () => {
68
+ const onmove = vi.fn();
69
+ render(ProjectBoard, {
70
+ // Runtime callers can bypass the public discriminated callback contract.
71
+ // The adapter must still avoid a move that cannot reconcile its cards.
72
+ props: {
73
+ projectId: 'project-1',
74
+ statuses,
75
+ items: [items[0]],
76
+ onmove,
77
+ },
78
+ });
79
+ const card = screen.getByRole('button', {
80
+ name: /Publish release notes/,
81
+ });
82
+ await userEvent.click(card);
83
+ card.focus();
84
+ await userEvent.keyboard(' ');
85
+ await userEvent.keyboard('{ArrowRight}');
86
+ await userEvent.keyboard(' ');
87
+ expect(onmove).not.toHaveBeenCalled();
88
+ });
89
+ it('preserves supplied status order, keeps unmatched items visible, and sends a pure move intent', async () => {
90
+ const onmove = vi.fn().mockResolvedValue(undefined);
91
+ const onrefresh = vi.fn().mockResolvedValue(undefined);
92
+ const onselect = vi.fn();
93
+ const { container } = render(ProjectBoard, {
94
+ props: {
95
+ projectId: 'project-1',
96
+ statuses,
97
+ items,
98
+ onmove,
99
+ onrefresh,
100
+ onselect,
101
+ },
102
+ });
103
+ const lanes = Array.from(container.querySelectorAll('.smrt-board__lane'));
104
+ expect(lanes.map((lane) => lane.getAttribute('aria-label'))).toEqual([
105
+ 'Todo, 1 cards',
106
+ 'Done, 0 cards',
107
+ 'Unassigned, 1 cards',
108
+ ]);
109
+ expect(screen.getByText('Stale provider status')).toBeVisible();
110
+ const card = screen.getByRole('button', {
111
+ name: /Publish release notes/,
112
+ });
113
+ await userEvent.click(card);
114
+ expect(onselect).toHaveBeenCalledWith(items[0]);
115
+ card.focus();
116
+ await userEvent.keyboard(' ');
117
+ await userEvent.keyboard('{ArrowRight}');
118
+ await userEvent.keyboard(' ');
119
+ await vi.waitFor(() => {
120
+ expect(onmove).toHaveBeenCalledWith({
121
+ projectId: 'project-1',
122
+ itemId: 'item-1',
123
+ status: 'Done',
124
+ });
125
+ expect(onrefresh).toHaveBeenCalledTimes(1);
126
+ });
127
+ await expectNoA11yViolations(container);
128
+ });
129
+ it('refreshes authoritative state and lets Board make one sanitized failure announcement', async () => {
130
+ const onmove = vi
131
+ .fn()
132
+ .mockRejectedValue(new Error('provider token detail'));
133
+ const onrefresh = vi.fn().mockResolvedValue(undefined);
134
+ render(ProjectBoard, {
135
+ props: {
136
+ projectId: 'project-1',
137
+ statuses,
138
+ items: [items[0]],
139
+ onmove,
140
+ onrefresh,
141
+ },
142
+ });
143
+ const card = screen.getByRole('button', {
144
+ name: /Publish release notes/,
145
+ });
146
+ card.focus();
147
+ await userEvent.keyboard(' ');
148
+ await userEvent.keyboard('{ArrowRight}');
149
+ await userEvent.keyboard(' ');
150
+ await vi.waitFor(() => {
151
+ expect(onrefresh).toHaveBeenCalledTimes(1);
152
+ expect(screen.getByText('Could not move Publish release notes. The board was restored.')).toBeVisible();
153
+ expect(screen.getAllByText('Could not move Publish release notes. The board was restored.')).toHaveLength(1);
154
+ });
155
+ });
156
+ });
@@ -11,6 +11,8 @@ export declare const M: {
11
11
  readonly 'projects.development_request.list_aria': "projects.development_request.list_aria";
12
12
  readonly 'projects.development_board.aria': "projects.development_board.aria";
13
13
  readonly 'projects.development_board.empty': "projects.development_board.empty";
14
+ readonly 'projects.project_board.aria': "projects.project_board.aria";
15
+ readonly 'projects.project_board.unassigned': "projects.project_board.unassigned";
14
16
  readonly 'projects.development_request.detail_aria': "projects.development_request.detail_aria";
15
17
  readonly 'projects.development_request.status': "projects.development_request.status";
16
18
  readonly 'projects.development_request.visibility': "projects.development_request.visibility";
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgDZ,CAAC"}
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDZ,CAAC"}
@@ -12,6 +12,8 @@ export const M = defineMessages({
12
12
  'projects.development_request.list_aria': 'Development requests',
13
13
  'projects.development_board.aria': 'Development board',
14
14
  'projects.development_board.empty': 'No visible development work',
15
+ 'projects.project_board.aria': 'Project board',
16
+ 'projects.project_board.unassigned': 'Unassigned',
15
17
  'projects.development_request.detail_aria': 'Development request details',
16
18
  'projects.development_request.status': 'Status',
17
19
  'projects.development_request.visibility': 'Visibility',
@@ -21,8 +21,9 @@ import DeliveryStatus from './DeliveryStatus.svelte';
21
21
  import DevelopmentBoard from './DevelopmentBoard.svelte';
22
22
  import DevelopmentRequestDetail from './DevelopmentRequestDetail.svelte';
23
23
  import PreviewApprovalPanel from './PreviewApprovalPanel.svelte';
24
+ import ProjectBoard from './ProjectBoard.svelte';
24
25
  import ServiceEvidenceList from './ServiceEvidenceList.svelte';
25
- export { ApprovalActions, AssistanceLauncher, BulkActions, DeliveryStatus, DevelopmentBoard, DevelopmentRequestDetail, DevelopmentRequestForm, DevelopmentRequestList, DurationDisplay, PreviewApprovalPanel, RejectDialog, ServiceEvidenceList, TimeEntryCard, TimeEntryList, TimeSummary, };
26
+ export { ApprovalActions, AssistanceLauncher, BulkActions, DeliveryStatus, DevelopmentBoard, DevelopmentRequestDetail, DevelopmentRequestForm, DevelopmentRequestList, DurationDisplay, PreviewApprovalPanel, ProjectBoard, RejectDialog, ServiceEvidenceList, TimeEntryCard, TimeEntryList, TimeSummary, };
26
27
  export type ApprovalActionsProps = ComponentProps<typeof ApprovalActions>;
27
28
  export type AssistanceLauncherProps = ComponentProps<typeof AssistanceLauncher>;
28
29
  export type BulkActionsProps = ComponentProps<typeof BulkActions>;
@@ -34,10 +35,12 @@ export type DevelopmentRequestListProps = ComponentProps<typeof DevelopmentReque
34
35
  export type DurationDisplayProps = ComponentProps<typeof DurationDisplay>;
35
36
  export type RejectDialogProps = ComponentProps<typeof RejectDialog>;
36
37
  export type PreviewApprovalPanelProps = ComponentProps<typeof PreviewApprovalPanel>;
38
+ export type ProjectBoardProps = ComponentProps<typeof ProjectBoard>;
37
39
  export type ServiceEvidenceListProps = ComponentProps<typeof ServiceEvidenceList>;
38
40
  export type TimeEntryCardProps = ComponentProps<typeof TimeEntryCard>;
39
41
  export type TimeEntryListProps = ComponentProps<typeof TimeEntryList>;
40
42
  export type TimeSummaryProps = ComponentProps<typeof TimeSummary>;
41
43
  export * from './delivery-types.js';
44
+ export type { ProjectBoardMoveIntent } from './project-board-types.js';
42
45
  export { type ApprovalStatus, type Currency, formatCurrency, formatDate, formatHours, formatHoursHHMM, statusColors, type TimeEntry, type TimeEntryStatus, } from './utils.js';
43
46
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,OAAO,kBAAkB,MAAM,6BAA6B,CAAC;AAE7D,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,WAAW,MAAM,iCAAiC,CAAC;AAC1D,OAAO,sBAAsB,MAAM,4CAA4C,CAAC;AAChF,OAAO,sBAAsB,MAAM,4CAA4C,CAAC;AAChF,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,YAAY,MAAM,kCAAkC,CAAC;AAC5D,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,WAAW,MAAM,iCAAiC,CAAC;AAC1D,OAAO,cAAc,MAAM,yBAAyB,CAAC;AACrD,OAAO,gBAAgB,MAAM,2BAA2B,CAAC;AACzD,OAAO,wBAAwB,MAAM,mCAAmC,CAAC;AACzE,OAAO,oBAAoB,MAAM,+BAA+B,CAAC;AACjE,OAAO,mBAAmB,MAAM,8BAA8B,CAAC;AAG/D,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,WAAW,GACZ,CAAC;AAGF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChF,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,CAAC;AAClE,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AACxE,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5E,MAAM,MAAM,2BAA2B,GAAG,cAAc,CACtD,OAAO,sBAAsB,CAC9B,CAAC;AACF,MAAM,MAAM,6BAA6B,GAAG,cAAc,CACxD,OAAO,wBAAwB,CAChC,CAAC;AACF,MAAM,MAAM,2BAA2B,GAAG,cAAc,CACtD,OAAO,sBAAsB,CAC9B,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,YAAY,CAAC,CAAC;AACpE,MAAM,MAAM,yBAAyB,GAAG,cAAc,CACpD,OAAO,oBAAoB,CAC5B,CAAC;AACF,MAAM,MAAM,wBAAwB,GAAG,cAAc,CACnD,OAAO,mBAAmB,CAC3B,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,CAAC;AAElE,cAAc,qBAAqB,CAAC;AAEpC,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACX,eAAe,EACf,YAAY,EACZ,KAAK,SAAS,EACd,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,OAAO,kBAAkB,MAAM,6BAA6B,CAAC;AAE7D,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,WAAW,MAAM,iCAAiC,CAAC;AAC1D,OAAO,sBAAsB,MAAM,4CAA4C,CAAC;AAChF,OAAO,sBAAsB,MAAM,4CAA4C,CAAC;AAChF,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,YAAY,MAAM,kCAAkC,CAAC;AAC5D,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,WAAW,MAAM,iCAAiC,CAAC;AAC1D,OAAO,cAAc,MAAM,yBAAyB,CAAC;AACrD,OAAO,gBAAgB,MAAM,2BAA2B,CAAC;AACzD,OAAO,wBAAwB,MAAM,mCAAmC,CAAC;AACzE,OAAO,oBAAoB,MAAM,+BAA+B,CAAC;AACjE,OAAO,YAAY,MAAM,uBAAuB,CAAC;AACjD,OAAO,mBAAmB,MAAM,8BAA8B,CAAC;AAG/D,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,WAAW,GACZ,CAAC;AAGF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChF,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,CAAC;AAClE,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AACxE,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5E,MAAM,MAAM,2BAA2B,GAAG,cAAc,CACtD,OAAO,sBAAsB,CAC9B,CAAC;AACF,MAAM,MAAM,6BAA6B,GAAG,cAAc,CACxD,OAAO,wBAAwB,CAChC,CAAC;AACF,MAAM,MAAM,2BAA2B,GAAG,cAAc,CACtD,OAAO,sBAAsB,CAC9B,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,YAAY,CAAC,CAAC;AACpE,MAAM,MAAM,yBAAyB,GAAG,cAAc,CACpD,OAAO,oBAAoB,CAC5B,CAAC;AACF,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,YAAY,CAAC,CAAC;AACpE,MAAM,MAAM,wBAAwB,GAAG,cAAc,CACnD,OAAO,mBAAmB,CAC3B,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,CAAC;AAElE,cAAc,qBAAqB,CAAC;AACpC,YAAY,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACX,eAAe,EACf,YAAY,EACZ,KAAK,SAAS,EACd,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC"}
@@ -23,15 +23,17 @@ import DeliveryStatus from './DeliveryStatus.svelte';
23
23
  import DevelopmentBoard from './DevelopmentBoard.svelte';
24
24
  import DevelopmentRequestDetail from './DevelopmentRequestDetail.svelte';
25
25
  import PreviewApprovalPanel from './PreviewApprovalPanel.svelte';
26
+ import ProjectBoard from './ProjectBoard.svelte';
26
27
  import ServiceEvidenceList from './ServiceEvidenceList.svelte';
27
28
  // Export components
28
- export { ApprovalActions, AssistanceLauncher, BulkActions, DeliveryStatus, DevelopmentBoard, DevelopmentRequestDetail, DevelopmentRequestForm, DevelopmentRequestList, DurationDisplay, PreviewApprovalPanel, RejectDialog, ServiceEvidenceList, TimeEntryCard, TimeEntryList, TimeSummary, };
29
+ export { ApprovalActions, AssistanceLauncher, BulkActions, DeliveryStatus, DevelopmentBoard, DevelopmentRequestDetail, DevelopmentRequestForm, DevelopmentRequestList, DurationDisplay, PreviewApprovalPanel, ProjectBoard, RejectDialog, ServiceEvidenceList, TimeEntryCard, TimeEntryList, TimeSummary, };
29
30
  export * from './delivery-types.js';
30
31
  // Export types and utilities
31
32
  export { formatCurrency, formatDate, formatHours, formatHoursHHMM, statusColors, } from './utils.js';
32
33
  // Auto-register with ModuleUIRegistry
33
34
  ModuleUIRegistry.registerModule(PROJECTS_MODULE_META);
34
35
  ModuleUIRegistry.register('@happyvertical/smrt-projects', 'development-board', DevelopmentBoard);
36
+ ModuleUIRegistry.register('@happyvertical/smrt-projects', 'project-board', ProjectBoard);
35
37
  ModuleUIRegistry.register('@happyvertical/smrt-projects', 'delivery-status', DeliveryStatus);
36
38
  ModuleUIRegistry.register('@happyvertical/smrt-projects', 'preview-approval', PreviewApprovalPanel);
37
39
  ModuleUIRegistry.register('@happyvertical/smrt-projects', 'assistance-launcher', AssistanceLauncher);