@taskforcehq/taskforce 0.3.301 → 0.3.303
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Taskforce.module.css +37 -2
- package/dist/TaskforceCore.filter-persistence.test.js +1 -1
- package/dist/TaskforceCore.js +72 -33
- package/dist/TaskforceCore.test.js +848 -130
- package/dist/components/features/TaskSettings.js +14 -82
- package/dist/components/features/TaskSettings.test.js +33 -70
- package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
- package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
- package/dist/components/task/TaskActionHeader.js +6 -3
- package/dist/components/task/TaskCard.js +6 -3
- package/dist/components/task/TaskForm.js +2 -2
- package/dist/components/task/TaskForm.test.js +41 -2
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/ui/ReferenceBadgeButton.js +1 -1
- package/dist/components/views/PlansPage.d.ts +3 -1
- package/dist/components/views/PlansPage.js +109 -49
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +100 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +473 -256
- package/dist/components/views/panels/PlanningDrawer.js +18 -1
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +48 -0
- package/dist/components/views/standalone/modals/AccountHubModal.js +36 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
- package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
- package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/HelpModal.js +8 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
- package/dist/components/views/standalone/modals/types.d.ts +2 -0
- package/dist/components/views/standalone/modals/types.js +1 -0
- package/dist/components/views/teamManagementAccess.d.ts +1 -1
- package/dist/components/views/teamManagementAccess.js +2 -2
- package/dist/components/views/teamManagementAccess.test.js +3 -3
- package/dist/config/deployment.js +2 -2
- package/dist/config/deployment.test.js +5 -3
- package/dist/core/AiProfileService.js +7 -2
- package/dist/core/AiProfiles.test.js +19 -0
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +29 -5
- package/dist/core/PlanEntitlementService.js +297 -219
- package/dist/core/SignupMonetizationSettings.test.js +173 -49
- package/dist/core/SyncReconciliationService.d.ts +2 -0
- package/dist/core/SyncReconciliationService.js +3 -0
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/TaskAuditTimeline.test.js +40 -0
- package/dist/core/Taskforce.d.ts +100 -10
- package/dist/core/Taskforce.ids.test.js +133 -0
- package/dist/core/Taskforce.js +459 -127
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +111 -0
- package/dist/core/taskReferencePolicy.d.ts +28 -0
- package/dist/core/taskReferencePolicy.js +189 -0
- package/dist/core/types.d.ts +13 -12
- package/dist/hooks/tasks/useTaskFormActions.js +3 -0
- package/dist/hooks/useSyncOrchestrator.js +17 -3
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
- package/dist/hooks/useTaskData.js +3 -2
- package/dist/hooks/useTaskData.test.js +66 -0
- package/dist/hooks/useTaskforce.d.ts +8 -0
- package/dist/hooks/useTaskforce.js +72 -19
- package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
- package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
- package/dist/mcp/aiProfileBootstrap.js +80 -0
- package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
- package/dist/mcp/aiProfileBootstrap.test.js +68 -0
- package/dist/mcp/clientIntegrations.d.ts +2 -8
- package/dist/mcp/clientIntegrations.js +21 -40
- package/dist/mcp/clientIntegrations.test.js +28 -2
- package/dist/mcp/clientProfileDefaults.d.ts +12 -0
- package/dist/mcp/clientProfileDefaults.js +37 -0
- package/dist/mcp/runtime.d.ts +10 -7
- package/dist/mcp/runtime.js +131 -48
- package/dist/mcp/runtime.test.js +321 -83
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +68 -1
- package/dist/qaOpen.test.d.ts +1 -0
- package/dist/qaOpen.test.js +56 -0
- package/dist/server/index.cookieProxy.test.js +2 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +17 -1
- package/dist/server/index.test.js +15 -1
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +105 -45
- package/dist/server/routes/auth.d.ts +6 -0
- package/dist/server/routes/auth.js +505 -30
- package/dist/server/routes/billing.js +517 -24
- package/dist/server/routes/billing.test.js +967 -45
- package/dist/server/routes/documents.js +6 -1
- package/dist/server/routes/shared.d.ts +1 -1
- package/dist/server/routes/shared.js +16 -6
- package/dist/server/routes/sync.integration.test.js +290 -0
- package/dist/server/routes/sync.js +234 -1922
- package/dist/server/routes/tasks.js +18 -4
- package/dist/server/routes.js +29 -22
- package/dist/server/routes.test.js +639 -82
- package/dist/shared/runtimeContract.d.ts +1 -1
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.d.ts +17 -0
- package/dist/sync/collaborationSyncPayload.js +131 -0
- package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.test.js +104 -0
- package/dist/sync/collaborationSyncState.d.ts +15 -0
- package/dist/sync/collaborationSyncState.js +60 -0
- package/dist/sync/contentSyncPayload.d.ts +45 -0
- package/dist/sync/contentSyncPayload.js +104 -0
- package/dist/sync/contentSyncPayload.test.d.ts +1 -0
- package/dist/sync/contentSyncPayload.test.js +104 -0
- package/dist/sync/contentSyncState.d.ts +98 -0
- package/dist/sync/contentSyncState.js +1004 -0
- package/dist/sync/planningSyncPayload.d.ts +9 -0
- package/dist/sync/planningSyncPayload.js +82 -0
- package/dist/sync/planningSyncPayload.test.d.ts +1 -0
- package/dist/sync/planningSyncPayload.test.js +68 -0
- package/dist/sync/syncApplyHandlers.d.ts +83 -0
- package/dist/sync/syncApplyHandlers.js +445 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +21 -8
- package/dist/sync/taskSyncPayload.d.ts +33 -0
- package/dist/sync/taskSyncPayload.js +126 -0
- package/dist/sync/taskSyncPayload.test.d.ts +1 -0
- package/dist/sync/taskSyncPayload.test.js +54 -0
- package/dist/sync/workspacePullFeed.d.ts +38 -0
- package/dist/sync/workspacePullFeed.js +349 -0
- package/dist/sync/workspacePullFeed.test.d.ts +1 -0
- package/dist/sync/workspacePullFeed.test.js +278 -0
- package/dist/sync/workspaceSyncModel.d.ts +4 -1
- package/dist/sync/workspaceSyncModel.js +9 -2
- package/dist/sync/workspaceSyncModel.test.js +29 -0
- package/dist/test/setup.js +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-CpsTmrIW.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-C3GyzrWm.js} +2 -2
- package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-4-Rh2K2n.js} +1 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +8 -0
- package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
- package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
- package/dist/ui/assets/index-CLIMgR6g.js +6 -0
- package/dist/ui/assets/index-DneETxcu.css +1 -0
- package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-pWocEipT.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/dist/utils/taskActivity.js +17 -4
- package/dist/utils/taskActivity.test.js +27 -0
- package/dist/utils/taskNormalization.js +10 -2
- package/dist/utils/taskReferences.d.ts +11 -1
- package/dist/utils/taskReferences.js +33 -1
- package/package.json +4 -3
- package/scripts/qa-open-lib.mjs +175 -0
- package/scripts/qa-open-lib.mjs.d.ts +49 -0
- package/scripts/qa-open.mjs +352 -0
- package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
- package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
- package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
- package/dist/ui/assets/index-xBWLPOEb.js +0 -6
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildWorkspacePullFeed } from './workspacePullFeed';
|
|
3
|
+
function createCoreMock() {
|
|
4
|
+
return {
|
|
5
|
+
getPaths: () => ({ basePath: '/tmp/taskforce-test' }),
|
|
6
|
+
listTasksForSync: () => [],
|
|
7
|
+
listWorkspaceSyncDeletesSince: () => [],
|
|
8
|
+
getTaxonomiesSyncSnapshot: () => null,
|
|
9
|
+
listInitiatives: () => [],
|
|
10
|
+
listWorkstreams: () => [],
|
|
11
|
+
listAiProfiles: () => [],
|
|
12
|
+
listWorkspaceUsers: () => [],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function createContentSyncStateMock() {
|
|
16
|
+
return {
|
|
17
|
+
listWorkspaceSyncDocumentIndex: async () => [],
|
|
18
|
+
listWorkspaceSyncDocumentsSnapshot: async () => [],
|
|
19
|
+
loadWorkspaceSyncDocumentPayload: async () => null,
|
|
20
|
+
listWorkspaceSyncDocumentDeletesSnapshot: () => [],
|
|
21
|
+
listWorkspaceSyncBinaryAssetIndex: async () => [],
|
|
22
|
+
listWorkspaceSyncBinaryAssetsSnapshot: async () => [],
|
|
23
|
+
loadWorkspaceSyncBinaryAssetPayload: async () => null,
|
|
24
|
+
listWorkspaceSyncBinaryAssetDeletesSnapshot: () => [],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function createCollaborationSyncStateMock() {
|
|
28
|
+
return {
|
|
29
|
+
listWorkspaceSyncDocumentReviewSessionsSnapshot: () => [],
|
|
30
|
+
listWorkspaceSyncDocumentReviewCommentsSnapshot: () => [],
|
|
31
|
+
listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: () => [],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function encodeCursor(cursor) {
|
|
35
|
+
return cursor ? `${cursor.u}|${cursor.id}` : null;
|
|
36
|
+
}
|
|
37
|
+
describe('workspacePullFeed', () => {
|
|
38
|
+
it('sorts entries by watermark and sort id, then paginates deterministically', async () => {
|
|
39
|
+
const core = createCoreMock();
|
|
40
|
+
core.listTasksForSync = () => [
|
|
41
|
+
{
|
|
42
|
+
archived: false,
|
|
43
|
+
task: {
|
|
44
|
+
id: 'b-task',
|
|
45
|
+
title: 'Task B',
|
|
46
|
+
status: 'task',
|
|
47
|
+
category: 'general',
|
|
48
|
+
type: 'task',
|
|
49
|
+
createdAt: '2026-04-10T10:00:00.000Z',
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
archived: false,
|
|
54
|
+
task: {
|
|
55
|
+
id: 'a-task',
|
|
56
|
+
title: 'Task A',
|
|
57
|
+
status: 'task',
|
|
58
|
+
category: 'general',
|
|
59
|
+
type: 'task',
|
|
60
|
+
createdAt: '2026-04-10T10:00:00.000Z',
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
archived: false,
|
|
65
|
+
task: {
|
|
66
|
+
id: 'c-task',
|
|
67
|
+
title: 'Task C',
|
|
68
|
+
status: 'task',
|
|
69
|
+
category: 'general',
|
|
70
|
+
type: 'task',
|
|
71
|
+
createdAt: '2026-04-11T10:00:00.000Z',
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
const result = await buildWorkspacePullFeed({
|
|
76
|
+
core,
|
|
77
|
+
workspaceId: 'workspace-1',
|
|
78
|
+
cursor: null,
|
|
79
|
+
pageSize: 2,
|
|
80
|
+
encodeWorkspaceSyncCursor: encodeCursor,
|
|
81
|
+
contentSyncState: createContentSyncStateMock(),
|
|
82
|
+
collaborationSyncState: createCollaborationSyncStateMock(),
|
|
83
|
+
});
|
|
84
|
+
expect(result.changes).toHaveLength(2);
|
|
85
|
+
expect(result.changes[0]).toEqual(expect.objectContaining({
|
|
86
|
+
op: 'upsert',
|
|
87
|
+
watermark: '2026-04-10T10:00:00.000Z',
|
|
88
|
+
task: expect.objectContaining({ id: 'a-task' }),
|
|
89
|
+
}));
|
|
90
|
+
expect(result.changes[1]).toEqual(expect.objectContaining({
|
|
91
|
+
op: 'upsert',
|
|
92
|
+
watermark: '2026-04-10T10:00:00.000Z',
|
|
93
|
+
task: expect.objectContaining({ id: 'b-task' }),
|
|
94
|
+
}));
|
|
95
|
+
expect(result.nextCursor).toBe('2026-04-10T10:00:00.000Z|b-task');
|
|
96
|
+
expect(result.hasMore).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
it('resumes strictly after the cursor and preserves workspace member snapshots', async () => {
|
|
99
|
+
const core = createCoreMock();
|
|
100
|
+
core.listTasksForSync = () => [
|
|
101
|
+
{
|
|
102
|
+
archived: false,
|
|
103
|
+
task: {
|
|
104
|
+
id: 'z-task',
|
|
105
|
+
title: 'Carry task',
|
|
106
|
+
status: 'review',
|
|
107
|
+
category: 'general',
|
|
108
|
+
type: 'task',
|
|
109
|
+
createdAt: '2026-04-10T10:00:00.000Z',
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
];
|
|
113
|
+
core.listWorkspaceSyncDeletesSince = () => [
|
|
114
|
+
{
|
|
115
|
+
taskId: 'delete-task',
|
|
116
|
+
deletedAt: '2026-04-10T10:00:00.000Z',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
taskId: 'later-delete',
|
|
120
|
+
deletedAt: '2026-04-11T09:00:00.000Z',
|
|
121
|
+
}
|
|
122
|
+
];
|
|
123
|
+
core.listWorkspaceUsers = () => [
|
|
124
|
+
{
|
|
125
|
+
userId: 'user-1',
|
|
126
|
+
email: 'person@example.com',
|
|
127
|
+
displayName: 'Person',
|
|
128
|
+
role: 'owner',
|
|
129
|
+
permissionMode: 'read-write',
|
|
130
|
+
betaAccess: true,
|
|
131
|
+
status: 'active',
|
|
132
|
+
disabled: false,
|
|
133
|
+
createdAt: '2026-04-01T00:00:00.000Z',
|
|
134
|
+
updatedAt: '2026-04-02T00:00:00.000Z',
|
|
135
|
+
emailVerifiedAt: '2026-04-03T00:00:00.000Z',
|
|
136
|
+
}
|
|
137
|
+
];
|
|
138
|
+
const result = await buildWorkspacePullFeed({
|
|
139
|
+
core,
|
|
140
|
+
workspaceId: 'workspace-2',
|
|
141
|
+
cursor: { u: '2026-04-10T10:00:00.000Z', id: 'delete-task' },
|
|
142
|
+
pageSize: 10,
|
|
143
|
+
encodeWorkspaceSyncCursor: encodeCursor,
|
|
144
|
+
contentSyncState: createContentSyncStateMock(),
|
|
145
|
+
collaborationSyncState: createCollaborationSyncStateMock(),
|
|
146
|
+
});
|
|
147
|
+
expect(result.changes).toEqual([
|
|
148
|
+
expect.objectContaining({
|
|
149
|
+
op: 'upsert',
|
|
150
|
+
watermark: '2026-04-10T10:00:00.000Z',
|
|
151
|
+
task: expect.objectContaining({ id: 'z-task' }),
|
|
152
|
+
}),
|
|
153
|
+
expect.objectContaining({
|
|
154
|
+
op: 'delete',
|
|
155
|
+
taskId: 'later-delete',
|
|
156
|
+
watermark: '2026-04-11T09:00:00.000Z',
|
|
157
|
+
}),
|
|
158
|
+
]);
|
|
159
|
+
expect(result.hasMore).toBe(false);
|
|
160
|
+
expect(result.nextCursor).toBe('2026-04-11T09:00:00.000Z|later-delete');
|
|
161
|
+
expect(result.workspaceMembers).toEqual([
|
|
162
|
+
expect.objectContaining({
|
|
163
|
+
userId: 'user-1',
|
|
164
|
+
workspaceId: 'workspace-2',
|
|
165
|
+
email: 'person@example.com',
|
|
166
|
+
displayName: 'Person',
|
|
167
|
+
})
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
it('hydrates document and asset payloads only for entries included in the current page', async () => {
|
|
171
|
+
const core = createCoreMock();
|
|
172
|
+
const content = createContentSyncStateMock();
|
|
173
|
+
const loadedDocs = [];
|
|
174
|
+
const loadedAssets = [];
|
|
175
|
+
content.listWorkspaceSyncDocumentIndex = async () => [
|
|
176
|
+
{
|
|
177
|
+
path: 'docs/a.md',
|
|
178
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
179
|
+
assetId: 'asset-doc-a',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
path: 'docs/z.md',
|
|
183
|
+
updatedAt: '2026-04-12T10:00:00.000Z',
|
|
184
|
+
assetId: 'asset-doc-z',
|
|
185
|
+
}
|
|
186
|
+
];
|
|
187
|
+
content.loadWorkspaceSyncDocumentPayload = async (_basePath, _workspaceId, relativePath) => {
|
|
188
|
+
loadedDocs.push(relativePath);
|
|
189
|
+
return {
|
|
190
|
+
path: relativePath,
|
|
191
|
+
updatedAt: relativePath === 'docs/a.md' ? '2026-04-10T10:00:00.000Z' : '2026-04-12T10:00:00.000Z',
|
|
192
|
+
content: `content:${relativePath}`
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
content.listWorkspaceSyncBinaryAssetIndex = async () => [
|
|
196
|
+
{
|
|
197
|
+
path: 'workspaces/ws/assets/images/a.png',
|
|
198
|
+
updatedAt: '2026-04-11T10:00:00.000Z',
|
|
199
|
+
assetId: 'asset-image-a',
|
|
200
|
+
kind: 'image',
|
|
201
|
+
mimeType: 'image/png'
|
|
202
|
+
}
|
|
203
|
+
];
|
|
204
|
+
content.loadWorkspaceSyncBinaryAssetPayload = async (_basePath, _workspaceId, assetPath) => {
|
|
205
|
+
loadedAssets.push(assetPath);
|
|
206
|
+
return {
|
|
207
|
+
path: assetPath,
|
|
208
|
+
updatedAt: '2026-04-11T10:00:00.000Z',
|
|
209
|
+
contentBase64: 'Zm9v',
|
|
210
|
+
kind: 'image',
|
|
211
|
+
mimeType: 'image/png'
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
const result = await buildWorkspacePullFeed({
|
|
215
|
+
core,
|
|
216
|
+
workspaceId: 'workspace-3',
|
|
217
|
+
cursor: null,
|
|
218
|
+
pageSize: 2,
|
|
219
|
+
encodeWorkspaceSyncCursor: encodeCursor,
|
|
220
|
+
contentSyncState: content,
|
|
221
|
+
collaborationSyncState: createCollaborationSyncStateMock(),
|
|
222
|
+
});
|
|
223
|
+
expect(result.changes).toHaveLength(2);
|
|
224
|
+
expect(result.changes[0]).toEqual(expect.objectContaining({
|
|
225
|
+
op: 'document-upsert',
|
|
226
|
+
path: 'docs/a.md',
|
|
227
|
+
content: 'content:docs/a.md'
|
|
228
|
+
}));
|
|
229
|
+
expect(result.changes[1]).toEqual(expect.objectContaining({
|
|
230
|
+
op: 'asset-upsert',
|
|
231
|
+
path: 'workspaces/ws/assets/images/a.png',
|
|
232
|
+
contentBase64: 'Zm9v'
|
|
233
|
+
}));
|
|
234
|
+
expect(loadedDocs).toEqual(['docs/a.md']);
|
|
235
|
+
expect(loadedAssets).toEqual(['workspaces/ws/assets/images/a.png']);
|
|
236
|
+
});
|
|
237
|
+
it('does not advance the cursor past a hydration miss', async () => {
|
|
238
|
+
const core = createCoreMock();
|
|
239
|
+
const content = createContentSyncStateMock();
|
|
240
|
+
content.listWorkspaceSyncDocumentIndex = async () => [
|
|
241
|
+
{
|
|
242
|
+
path: 'docs/a.md',
|
|
243
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
244
|
+
assetId: 'asset-doc-a',
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
path: 'docs/b.md',
|
|
248
|
+
updatedAt: '2026-04-11T10:00:00.000Z',
|
|
249
|
+
assetId: 'asset-doc-b',
|
|
250
|
+
}
|
|
251
|
+
];
|
|
252
|
+
content.loadWorkspaceSyncDocumentPayload = async (_basePath, _workspaceId, relativePath) => (relativePath === 'docs/a.md'
|
|
253
|
+
? {
|
|
254
|
+
path: relativePath,
|
|
255
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
256
|
+
content: 'content:docs/a.md'
|
|
257
|
+
}
|
|
258
|
+
: null);
|
|
259
|
+
const result = await buildWorkspacePullFeed({
|
|
260
|
+
core,
|
|
261
|
+
workspaceId: 'workspace-4',
|
|
262
|
+
cursor: null,
|
|
263
|
+
pageSize: 2,
|
|
264
|
+
encodeWorkspaceSyncCursor: encodeCursor,
|
|
265
|
+
contentSyncState: content,
|
|
266
|
+
collaborationSyncState: createCollaborationSyncStateMock(),
|
|
267
|
+
});
|
|
268
|
+
expect(result.changes).toEqual([
|
|
269
|
+
expect.objectContaining({
|
|
270
|
+
op: 'document-upsert',
|
|
271
|
+
path: 'docs/a.md',
|
|
272
|
+
content: 'content:docs/a.md'
|
|
273
|
+
})
|
|
274
|
+
]);
|
|
275
|
+
expect(result.nextCursor).toBe('2026-04-10T10:00:00.000Z|doc:docs/a.md');
|
|
276
|
+
expect(result.hasMore).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
@@ -3,6 +3,8 @@ import type { DocumentReviewCommentRecord, DocumentReviewSessionStatus } from '.
|
|
|
3
3
|
import type { AnnotatedAttachmentAnnotation } from '../annotatedAttachments/types.js';
|
|
4
4
|
import type { WorkspaceTaxonomyState } from '../shared/taxonomyState.js';
|
|
5
5
|
import { type AiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
|
|
6
|
+
import { type TaskSyncPayload } from './taskSyncPayload.js';
|
|
7
|
+
export type WorkspaceSyncTaskSnapshot = TaskSyncPayload;
|
|
6
8
|
export interface WorkspaceSyncAiProfileSnapshot {
|
|
7
9
|
id: string;
|
|
8
10
|
workspaceId: string;
|
|
@@ -98,7 +100,7 @@ export interface WorkspaceSyncWorkstreamSnapshot {
|
|
|
98
100
|
export type WorkspaceSyncChange = {
|
|
99
101
|
op: 'upsert';
|
|
100
102
|
archived: boolean;
|
|
101
|
-
task:
|
|
103
|
+
task: WorkspaceSyncTaskSnapshot;
|
|
102
104
|
} | {
|
|
103
105
|
op: 'delete';
|
|
104
106
|
taskId: string;
|
|
@@ -204,6 +206,7 @@ export interface WorkspaceSyncPayload {
|
|
|
204
206
|
/** Watermarks for every task upsert included in this payload (id → updatedAt|createdAt). */
|
|
205
207
|
pushedWatermarks: Map<string, string>;
|
|
206
208
|
}
|
|
209
|
+
export declare function toWorkspaceSyncTaskSnapshot(task: TaskItem): WorkspaceSyncTaskSnapshot;
|
|
207
210
|
export interface WorkspaceSyncDocumentSnapshot {
|
|
208
211
|
path: string;
|
|
209
212
|
updatedAt: string;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { parseAiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
|
|
2
|
+
import { serializeTaskForSync } from './taskSyncPayload.js';
|
|
3
|
+
export function toWorkspaceSyncTaskSnapshot(task) {
|
|
4
|
+
return serializeTaskForSync(task);
|
|
5
|
+
}
|
|
2
6
|
function sortTasksForSync(tasks) {
|
|
3
7
|
return [...tasks].sort((left, right) => {
|
|
4
8
|
const leftId = String(left?.id || '').trim();
|
|
@@ -13,6 +17,9 @@ export function buildWorkspaceSyncPayloadModel(input) {
|
|
|
13
17
|
.filter((taskId) => taskId.length > 0));
|
|
14
18
|
const lastWatermarks = input.lastPushedWatermarks;
|
|
15
19
|
const isDirty = (task) => {
|
|
20
|
+
if ((task?.referenceNumber === undefined || task?.referenceNumber === null) && Number.isFinite(Number(task?.localReferenceNumber))) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
16
23
|
if (!lastWatermarks || lastWatermarks.size === 0)
|
|
17
24
|
return true;
|
|
18
25
|
const taskId = String(task?.id || '').trim();
|
|
@@ -31,7 +38,7 @@ export function buildWorkspaceSyncPayloadModel(input) {
|
|
|
31
38
|
const wm = String(task?.updatedAt || task?.createdAt || '').trim();
|
|
32
39
|
if (taskId)
|
|
33
40
|
pushedWatermarks.set(taskId, wm);
|
|
34
|
-
return { op: 'upsert', archived: false, task };
|
|
41
|
+
return { op: 'upsert', archived: false, task: toWorkspaceSyncTaskSnapshot(task) };
|
|
35
42
|
});
|
|
36
43
|
const archivedChanges = archivedTasksOrdered
|
|
37
44
|
.filter((task) => !pendingDeleteIds.has(String(task?.id || '').trim()) && isDirty(task))
|
|
@@ -40,7 +47,7 @@ export function buildWorkspaceSyncPayloadModel(input) {
|
|
|
40
47
|
const wm = String(task?.updatedAt || task?.createdAt || '').trim();
|
|
41
48
|
if (taskId)
|
|
42
49
|
pushedWatermarks.set(taskId, wm);
|
|
43
|
-
return { op: 'upsert', archived: true, task };
|
|
50
|
+
return { op: 'upsert', archived: true, task: toWorkspaceSyncTaskSnapshot(task) };
|
|
44
51
|
});
|
|
45
52
|
const currentTaskIds = new Set();
|
|
46
53
|
for (const task of input.tasks || []) {
|
|
@@ -711,5 +711,34 @@ describe('workspaceSyncModel', () => {
|
|
|
711
711
|
]));
|
|
712
712
|
expect(payload.pushedWatermarks.get('task-1')).toBe('2026-03-16T10:05:00.000Z');
|
|
713
713
|
});
|
|
714
|
+
it('keeps provisional local-reference tasks dirty until a canonical reference is assigned', () => {
|
|
715
|
+
const payload = buildWorkspaceSyncPayloadModel({
|
|
716
|
+
tasks: [{
|
|
717
|
+
id: 'task-provisional-1',
|
|
718
|
+
title: 'Offline draft',
|
|
719
|
+
status: 'task',
|
|
720
|
+
createdAt: '2026-03-16T10:00:00.000Z',
|
|
721
|
+
updatedAt: '2026-03-16T10:05:00.000Z',
|
|
722
|
+
referenceNumber: null,
|
|
723
|
+
localReferenceNumber: 3
|
|
724
|
+
}],
|
|
725
|
+
archivedTasks: [],
|
|
726
|
+
lastPushedTaskIds: new Set(['task-provisional-1']),
|
|
727
|
+
pendingDeletedTaskIds: new Set(),
|
|
728
|
+
lastPushedWatermarks: new Map([
|
|
729
|
+
['task-provisional-1', '2026-03-16T10:05:00.000Z']
|
|
730
|
+
])
|
|
731
|
+
});
|
|
732
|
+
expect(payload.changes).toEqual(expect.arrayContaining([
|
|
733
|
+
expect.objectContaining({
|
|
734
|
+
op: 'upsert',
|
|
735
|
+
archived: false,
|
|
736
|
+
task: expect.objectContaining({
|
|
737
|
+
id: 'task-provisional-1',
|
|
738
|
+
referenceNumber: null
|
|
739
|
+
})
|
|
740
|
+
})
|
|
741
|
+
]));
|
|
742
|
+
});
|
|
714
743
|
});
|
|
715
744
|
});
|
package/dist/test/setup.js
CHANGED
|
@@ -6,7 +6,7 @@ import os from 'os';
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
// Ensure all tests write global settings to a writable temp home.
|
|
8
8
|
const testHome = path.join(os.tmpdir(), 'taskforce-vitest-home');
|
|
9
|
-
fs.mkdirSync(path.join(testHome, '.
|
|
9
|
+
fs.mkdirSync(path.join(testHome, '.taskforce'), { recursive: true });
|
|
10
10
|
process.env.HOME = testHome;
|
|
11
11
|
process.env.USERPROFILE = testHome;
|
|
12
12
|
// Cleanup after each test case (e.g. clearing jsdom)
|
package/dist/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as n,j as e}from"./vendor-react-CKJs5o3c.js";import{
|
|
1
|
+
import{r as n,j as e}from"./vendor-react-CKJs5o3c.js";import{p as T,u as C,v as w,w as S,t as s,x as M}from"./index-CLIMgR6g.js";import{h as L,B as A,k as I,a9 as H,aH as B,aI as D,b as R}from"./vendor-icons-pWocEipT.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";function F({workspaceId:h}){const[x,k]=n.useState([]),[j,y]=n.useState(!1),[c,P]=n.useState(null),[p,m]=n.useState(null),[d,g]=n.useState(null),N=n.useCallback(async()=>{if(h){y(!0);try{const a=await(await fetch(`/api/taskforce/workspace/assignee-options?workspaceId=${encodeURIComponent(h)}`,{credentials:"include"})).json().catch(()=>({})),t=Array.isArray(a?.assignees)?a.assignees.filter(i=>i.kind==="agent").map(i=>({id:String(i.value||""),name:String(i.label||i.value||"Unknown Agent"),username:String(i.username||i.value||""),icon:String(i.icon||"Bot"),color:String(i.color||"#6B7280"),kind:String(i.kind||"agent"),description:typeof i.description=="string"?i.description:null,role:typeof i.role=="string"?i.role:null,provider:typeof i.provider=="string"?i.provider:null,model:typeof i.model=="string"?i.model:null,surfaceType:T(i.surfaceType),createdAt:String(i.createdAt||""),updatedAt:String(i.updatedAt||i.createdAt||""),lastActiveAt:typeof i.lastActiveAt=="string"?i.lastActiveAt:null})):[];k(t)}finally{y(!1)}}},[h]);n.useEffect(()=>{N()},[N]);const f=n.useMemo(()=>{const r=new Map;for(const a of x){const t=C(a.surfaceType);r.has(t)||r.set(t,new Map);const i=r.get(t),v=a.name.toLowerCase();i.has(v)||i.set(v,[]),i.get(v).push(a)}return w.map(a=>({section:a,label:S(a),groups:Array.from(r.get(a)?.entries()||[]).map(([t,i])=>({groupId:`${a}:${t}`,section:a,sectionLabel:S(a),profiles:i,primaryProfile:i[0]}))})).filter(a=>a.groups.length>0)},[x]),o=n.useMemo(()=>f.flatMap(r=>r.groups),[f]),l=n.useMemo(()=>o.find(r=>r.groupId===d)||o[0]||null,[o,d]);n.useEffect(()=>{if(!o.length){d!==null&&g(null);return}(!d||!o.some(r=>r.groupId===d))&&g(o[0].groupId)},[o,d]);const b=async(r,a)=>{m(null);try{const t=await fetch("/api/taskforce/workspace/ai-profiles/merge",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({keepId:r,mergeId:a})}),i=await t.json().catch(()=>({}));if(!t.ok){m({type:"error",message:String(i?.error||"Failed to merge AI profiles.")});return}P(null),m({type:"success",message:"AI profiles merged."}),await N()}catch{m({type:"error",message:"Failed to merge AI profiles."})}},u=r=>{const a=String(r||"").trim();if(!a)return"Unknown";const t=Date.parse(a);return Number.isFinite(t)?new Date(t).toLocaleString(void 0,{dateStyle:"medium",timeStyle:"short"}):a};return e.jsx("div",{className:`${s.standalonePage} ${s.standaloneContent}`,style:{overflow:"auto"},children:e.jsx("div",{className:s.settingsContent,style:{maxWidth:960,margin:"0 auto",width:"100%"},children:e.jsx("div",{className:s.settingGroup,children:e.jsxs("div",{children:[e.jsx("h4",{className:s.settingSubTitle,children:"Registered AI Profiles"}),e.jsx("p",{className:`${s.settingsHint} ${s.marginBottom12}`,children:"AI agents register profiles when connecting via MCP. Merge duplicates created when a token was lost."}),j&&e.jsxs("div",{className:s.settingsHint,children:[e.jsx(L,{size:13,className:s.spinner})," Loading profiles…"]}),!j&&x.length===0&&e.jsx("div",{className:s.settingsHint,children:"No AI profiles registered yet."}),!j&&f.length>0&&e.jsxs("div",{className:s.aiProfilesExplorer,children:[e.jsx("div",{className:s.aiProfilesListPane,children:e.jsx("div",{className:s.aiProfilesList,children:f.map(r=>e.jsxs("div",{className:s.aiProfilesSection,children:[e.jsx("div",{className:s.aiProfilesSectionHeader,children:r.label}),r.groups.map(a=>{const t=l?.groupId===a.groupId,i=a.primaryProfile;return e.jsxs("button",{type:"button",className:`${s.aiProfileGroup} ${a.profiles.length>1?s.aiProfileGroupDuplicate:""} ${t?s.aiProfileGroupSelected:""}`,onClick:()=>g(a.groupId),"aria-pressed":t,children:[e.jsxs("div",{className:s.aiProfileGroupHeader,children:[e.jsx(A,{size:13,style:{color:i.color}}),e.jsx("span",{className:s.aiProfileName,children:i.name}),a.profiles.length>1&&e.jsxs("span",{className:s.aiProfileDuplicateBadge,children:[e.jsx(I,{size:11})," ",a.profiles.length," duplicates"]})]}),e.jsxs("div",{className:s.settingsHint,style:{marginTop:6},children:["@",i.username]}),(i.surfaceType||i.role||i.provider||i.model)&&e.jsxs("div",{className:s.aiProfileMetaRow,children:[i.surfaceType&&e.jsx("span",{className:s.aiProfileSurfaceBadge,children:M(i.surfaceType)}),i.role&&e.jsx("span",{children:i.role}),i.provider&&e.jsx("span",{children:i.provider}),i.model&&e.jsx("span",{children:i.model})]}),i.description&&e.jsx("div",{className:s.settingsHint,style:{marginTop:4},children:i.description})]},a.groupId)})]},r.section))})}),l&&e.jsx("div",{className:s.aiProfileDetailPane,children:e.jsxs("div",{className:s.aiProfileDetailCard,children:[e.jsxs("div",{className:s.aiProfileDetailHero,children:[e.jsx("div",{className:s.aiProfileDetailIcon,style:{color:l.primaryProfile.color},children:e.jsx(A,{size:18})}),e.jsxs("div",{className:s.aiProfileDetailHeading,children:[e.jsx("div",{className:s.aiProfileDetailTitleRow,children:e.jsx("h5",{className:s.aiProfileDetailTitle,children:l.primaryProfile.name})}),e.jsxs("div",{className:s.aiProfileDetailMetaRow,children:[e.jsxs("span",{className:s.aiProfileDetailHandle,children:["@",l.primaryProfile.username]}),e.jsx("span",{className:s.aiProfileDetailSectionBadge,children:l.sectionLabel}),l.profiles.length>1&&e.jsxs("span",{className:s.aiProfileDetailLinkedCount,children:[l.profiles.length," linked"]})]})]})]}),l.primaryProfile.description&&e.jsx("div",{className:s.aiProfileDetailDescription,children:l.primaryProfile.description}),e.jsxs("div",{className:s.aiProfileDetailGrid,children:[e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(H,{size:12})," Role"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:l.primaryProfile.role||"Not set"})]}),e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(B,{size:12})," Provider"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:[l.primaryProfile.provider,l.primaryProfile.model].filter(Boolean).join(" · ")||"Not set"})]}),e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(D,{size:12})," Last active"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:u(l.primaryProfile.lastActiveAt)})]}),e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(D,{size:12})," Updated"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:u(l.primaryProfile.updatedAt)})]})]}),e.jsxs("div",{className:s.aiProfileInstanceSection,children:[e.jsxs("div",{className:s.aiProfileInstanceSectionHeader,children:[e.jsx("span",{children:"Profile instances"}),e.jsx("span",{className:s.settingsHint,children:"Choose a keeper here if duplicates need to be merged."})]}),e.jsx("div",{className:s.aiProfileInstanceList,children:l.profiles.map(r=>e.jsxs("div",{className:s.aiProfileInstanceCard,children:[e.jsxs("div",{className:s.aiProfileInstanceTopRow,children:[e.jsxs("div",{children:[e.jsxs("div",{className:s.aiProfileInstanceName,children:["@",r.username]}),e.jsx("div",{className:s.aiProfileIdChip,children:r.id})]}),l.profiles.length>1&&c?.keepId===r.id&&e.jsx("span",{className:s.aiProfileKeepBadge,children:"Keeping"})]}),e.jsxs("div",{className:s.aiProfileInstanceMeta,children:[e.jsxs("span",{children:["Created ",u(r.createdAt)]}),e.jsxs("span",{children:["Updated ",u(r.updatedAt)]})]}),l.profiles.length>1&&c?.keepId!==r.id&&e.jsx("button",{className:s.secondaryHeaderBtn,title:"Keep this profile, merge others into it",onClick:()=>{const a=l.profiles.find(t=>t.id!==r.id)?.id;a&&P({keepId:r.id,mergeId:a})},children:"Keep this"})]},r.id))}),c&&l.profiles.some(r=>r.id===c.keepId)&&e.jsxs("div",{className:s.aiProfileMergeActions,children:[e.jsx("button",{className:s.dangerBtn,onClick:()=>{b(c.keepId,c.mergeId)},children:"Merge duplicates"}),e.jsx("button",{className:s.secondaryHeaderBtn,onClick:()=>P(null),children:"Cancel"})]})]})]})})]}),p&&e.jsxs("div",{className:`${p.type==="success"?s.successMessage:s.errorMessage} ${s.marginTop12}`,children:[p.type==="success"?e.jsx(R,{size:14}):e.jsx(I,{size:14}),p.message]})]})})})})}export{F as AgentsModule};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{j as t,r as a,R as qt}from"./vendor-react-CKJs5o3c.js";import{R as xs,f as Ce,e as Ct,g as vs,t as Bn,M as Jt}from"./index-xBWLPOEb.js";import{q as _s,m as js,c as Zt,T as Tn,s as ks,a1 as ws,h as An,ai as Cs,R as Is,aj as Ss,ak as Ns,al as $s,am as tn,C as nn,an as Rs,ao as Dn,ap as zn,aq as Un,ar as Gn,g as Ps,f as Bs,b as sn}from"./vendor-icons-QZyhEwgT.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";function Ts({copied:o,disabled:l=!1,label:m,onClick:g,title:w="Copy image reference",ariaLabel:y,className:M=""}){return t.jsx(xs,{copied:o,disabled:l,label:"",onClick:g,title:w,ariaLabel:y,className:M,children:m})}const As="_shell_sf7jc_1",Es="_panel_sf7jc_16",Ms="_sessionPanel_sf7jc_22",Ls="_detailPanel_sf7jc_23",Fs="_sessionContextBar_sf7jc_29",Hs="_sessionContextLeft_sf7jc_40",Os="_sessionContextRight_sf7jc_41",Ds="_sessionContextLabel_sf7jc_56",zs="_sessionContextSpacer_sf7jc_62",Us="_canvasPanel_sf7jc_67",Gs="_canvasWorkspace_sf7jc_74",Ys="_canvasMain_sf7jc_81",Ks="_panelHeader_sf7jc_88",Ws="_panelHeaderText_sf7jc_97",Xs="_panelTitle_sf7jc_101",Vs="_canvasHeading_sf7jc_105",qs="_sessionActions_sf7jc_109",Js="_sessionList_sf7jc_117",Zs="_annotationList_sf7jc_118",Qs="_markerHelpModalBody_sf7jc_133",ea="_openImageModalBody_sf7jc_139",ta="_openImageField_sf7jc_145",na="_openImageActions_sf7jc_149",sa="_markerHelpItem_sf7jc_155",aa="_markerHelpHeader_sf7jc_163",oa="_markerHelpExample_sf7jc_175",ra="_sessionCard_sf7jc_179",ia="_annotationCard_sf7jc_180",la="_sessionEmptyState_sf7jc_197",ca="_sessionCardButton_sf7jc_204",da="_annotationCardButton_sf7jc_214",ua="_sessionCardBody_sf7jc_224",fa="_sessionCardActive_sf7jc_230",ma="_annotationCardActive_sf7jc_231",pa="_annotationMeta_sf7jc_241",ha="_annotationInstructionPreview_sf7jc_248",ba="_annotationPreviewFooter_sf7jc_256",ga="_annotationInstructionEditor_sf7jc_263",ya="_annotationTypeField_sf7jc_269",xa="_annotationInstructionButton_sf7jc_275",va="_annotationInstructionField_sf7jc_284",_a="_sessionMeta_sf7jc_288",ja="_sessionTitle_sf7jc_295",ka="_annotationTitle_sf7jc_296",wa="_sessionTimestamp_sf7jc_302",Ca="_annotationKind_sf7jc_303",Ia="_annotationInstructionTypeIcon_sf7jc_307",Sa="_sessionInstructionPreview_sf7jc_313",Na="_sessionInstructionEditor_sf7jc_321",$a="_sessionCardFooter_sf7jc_327",Ra="_toolRail_sf7jc_334",Pa="_canvasToolRail_sf7jc_343",Ba="_toolbarCluster_sf7jc_364",Ta="_toolbarViewportCluster_sf7jc_371",Aa="_toolbarSeparator_sf7jc_375",Ea="_toolBtn_sf7jc_381",Ma="_toolRailButton_sf7jc_385",La="_toolbarButton_sf7jc_395",Fa="_toolBtnActive_sf7jc_400",Ha="_toolbarActions_sf7jc_407",Oa="_toolbarSelectionActions_sf7jc_416",Da="_toolbarColorPicker_sf7jc_424",za="_colorPickerButton_sf7jc_428",Ua="_colorPickerSwatch_sf7jc_433",Ga="_colorPickerPopover_sf7jc_441",Ya="_colorOption_sf7jc_456",Ka="_colorOptionActive_sf7jc_466",Wa="_toolbarUtilities_sf7jc_473",Xa="_toolbarGeometryFields_sf7jc_481",Va="_toolbarGeometryField_sf7jc_481",qa="_toolbarGeometryLabel_sf7jc_494",Ja="_toolbarGeometryInput_sf7jc_500",Za="_iconButton_sf7jc_505",Qa="_ghostBtn_sf7jc_510",eo="_payloadBtn_sf7jc_511",to="_backToTaskBtn_sf7jc_512",no="_canvasScroller_sf7jc_532",so="_canvasFrame_sf7jc_548",ao="_canvasMedia_sf7jc_555",oo="_canvasStatusOverlay_sf7jc_563",ro="_canvasStatusCard_sf7jc_575",io="_canvasImage_sf7jc_588",lo="_overlay_sf7jc_595",co="_overlaySelect_sf7jc_601",uo="_overlayPan_sf7jc_605",fo="_overlaySvg_sf7jc_609",mo="_overlayHitLayer_sf7jc_618",po="_arrowHitArea_sf7jc_627",ho="_canvasHandleHit_sf7jc_634",bo="_canvasResizeHandleHit_sf7jc_641",go="_canvasHandleVisible_sf7jc_645",yo="_pin_sf7jc_662",xo="_note_sf7jc_663",vo="_annotationNumberBadge_sf7jc_680",_o="_box_sf7jc_705",jo="_boxNumberBadge_sf7jc_714",ko="_arrowNumberBadge_sf7jc_720",wo="_boxSurface_sf7jc_724",Co="_selected_sf7jc_737",Io="_textInput_sf7jc_754",So="_textArea_sf7jc_755",No="_select_sf7jc_737",$o="_sessionTitleInput_sf7jc_762",Ro="_sessionInstructionField_sf7jc_767",Po="_detailEmpty_sf7jc_776",Bo="_emptyState_sf7jc_777",To="_payloadModalBody_sf7jc_789",Ao="_payloadModalToolbar_sf7jc_796",Eo="_payloadViewToggle_sf7jc_803",Mo="_payloadModalActions_sf7jc_804",Lo="_payloadModalPreview_sf7jc_811",Fo="_statusBar_sf7jc_826",Ho="_annotationSummary_sf7jc_838",s={shell:As,panel:Es,sessionPanel:Ms,detailPanel:Ls,sessionContextBar:Fs,sessionContextLeft:Hs,sessionContextRight:Os,sessionContextLabel:Ds,sessionContextSpacer:zs,canvasPanel:Us,canvasWorkspace:Gs,canvasMain:Ys,panelHeader:Ks,panelHeaderText:Ws,panelTitle:Xs,canvasHeading:Vs,sessionActions:qs,sessionList:Js,annotationList:Zs,markerHelpModalBody:Qs,openImageModalBody:ea,openImageField:ta,openImageActions:na,markerHelpItem:sa,markerHelpHeader:aa,markerHelpExample:oa,sessionCard:ra,annotationCard:ia,sessionEmptyState:la,sessionCardButton:ca,annotationCardButton:da,sessionCardBody:ua,sessionCardActive:fa,annotationCardActive:ma,annotationMeta:pa,annotationInstructionPreview:ha,annotationPreviewFooter:ba,annotationInstructionEditor:ga,annotationTypeField:ya,annotationInstructionButton:xa,annotationInstructionField:va,sessionMeta:_a,sessionTitle:ja,annotationTitle:ka,sessionTimestamp:wa,annotationKind:Ca,annotationInstructionTypeIcon:Ia,sessionInstructionPreview:Sa,sessionInstructionEditor:Na,sessionCardFooter:$a,toolRail:Ra,canvasToolRail:Pa,toolbarCluster:Ba,toolbarViewportCluster:Ta,toolbarSeparator:Aa,toolBtn:Ea,toolRailButton:Ma,toolbarButton:La,toolBtnActive:Fa,toolbarActions:Ha,toolbarSelectionActions:Oa,toolbarColorPicker:Da,colorPickerButton:za,colorPickerSwatch:Ua,colorPickerPopover:Ga,colorOption:Ya,colorOptionActive:Ka,toolbarUtilities:Wa,toolbarGeometryFields:Xa,toolbarGeometryField:Va,toolbarGeometryLabel:qa,toolbarGeometryInput:Ja,iconButton:Za,ghostBtn:Qa,payloadBtn:eo,backToTaskBtn:to,canvasScroller:no,canvasFrame:so,canvasMedia:ao,canvasStatusOverlay:oo,canvasStatusCard:ro,canvasImage:io,overlay:lo,overlaySelect:co,overlayPan:uo,overlaySvg:fo,overlayHitLayer:mo,arrowHitArea:po,canvasHandleHit:ho,canvasResizeHandleHit:bo,canvasHandleVisible:go,pin:yo,note:xo,annotationNumberBadge:vo,box:_o,boxNumberBadge:jo,arrowNumberBadge:ko,boxSurface:wo,selected:Co,textInput:Io,textArea:So,select:No,sessionTitleInput:$o,sessionInstructionField:Ro,detailEmpty:Po,emptyState:Bo,payloadModalBody:To,payloadModalToolbar:Ao,payloadViewToggle:Eo,payloadModalActions:Mo,payloadModalPreview:Lo,statusBar:Fo,annotationSummary:Ho},En=[{value:"review",label:"Review"},{value:"change",label:"Change"},{value:"question",label:"Question"}],Mn=[{value:"select",label:"Select",icon:Rs},{value:"pin",label:"Pin",icon:Dn},{value:"box",label:"Box",icon:zn},{value:"arrow",label:"Arrow",icon:Un},{value:"text-note",label:"Note",icon:Gn}],Oo={pin:Dn,box:zn,arrow:Un,"text-note":Gn},Do={pin:"Pin",box:"Box",arrow:"Arrow","text-note":"Note"},zo={review:tn,change:sn,question:nn,issue:nn,idea:tn},It={select:{short:"Select and edit existing markers.",detail:"Use Select to click, drag, reorder, resize, and update markers that are already on the image.",example:"Example: move an existing marker after the screenshot changes."},pin:{short:"Mark a precise spot.",detail:"Use Pin when feedback points to one exact location instead of a broader area.",example:'Example: "This icon is misaligned by 2px."'},box:{short:"Mark an area or component.",detail:"Use Box when the feedback applies to a whole region, card, panel, or bounded UI block.",example:'Example: "This whole card needs tighter padding and a stronger border."'},arrow:{short:"Show direction or relationship.",detail:"Use Arrow when you need to show movement, attachment, flow, or source-to-target intent.",example:'Example: "This tooltip should anchor to this button, not the panel."'},"text-note":{short:"Add a comment-style point marker.",detail:"Use Note when you want a point marker that reads more like a comment or open question.",example:'Example: "Ask design whether this badge should stay."'}},it={question:"#0f766e",change:"#2563eb",issue:"#dc2626",idea:"#d97706",review:"#7c3aed"},Uo=["#7c3aed","#2563eb","#0f766e","#dc2626","#d97706","#111827"],re="review";function ze(o){const l=String(o.displayName||"").trim();return l?`${l} review`:"Annotated session"}function Yn(){return`annotation-${Math.random().toString(36).slice(2,10)}`}function _(o){return!Number.isFinite(o)||o<=0?0:o>=1?1:o}function Se(o){return _(Math.max(.02,o))}function rt(o){return o?[String(o.taskId||"").trim(),String(o.assetId||"").trim(),String(o.path||"").trim()].join("::"):""}function Go(o){if(!(o instanceof HTMLElement))return!1;const l=o.tagName.toLowerCase();return o.isContentEditable?!0:l==="input"||l==="textarea"||l==="select"}function Ie(o){return o.map((l,m)=>({...l,order:m}))}function Qt(o){if(!o)return"Unsaved";const l=new Date(o);return Number.isNaN(l.getTime())?"Unsaved":l.toLocaleString()}function St(o){const l=String(o.createdByActor?.label||"").trim();return l||null}function Yo(o){const l=String(o||"").trim().replace(/\s+/g," ");return l?l.length>110?`${l.slice(0,107)}...`:l:""}function Ln(o,l,m,g=it[re]){const w={id:Yn(),order:0,instruction:"",markerType:re,color:g};if(o==="pin")return{...w,kind:o,x:l.x,y:l.y};if(o==="text-note")return{...w,kind:o,x:l.x,y:l.y};if(o==="box"){const M=m||l;return{...w,kind:o,x:_(Math.min(l.x,M.x)),y:_(Math.min(l.y,M.y)),width:Se(Math.abs(M.x-l.x)),height:Se(Math.abs(M.y-l.y))}}const y=m||l;return{...w,kind:"arrow",x:l.x,y:l.y,x2:y.x,y2:y.y}}function en(o){return o.color?o.color:it[o.markerType||re]}function Ko(o,l){const m=Math.max(l.width,1),g=Math.max(l.height,1),w=o.x*m,y=o.y*g,M=o.x2*m,ne=o.y2*g,Ue=M-w,Ge=ne-y,me=Math.hypot(Ue,Ge)||1,ie=Ue/me,Ye=Ge/me,Ne=Math.max(10,Math.min(16,me-2)),T=Ne*.62,Ke=M-ie*Ne,pe=ne-Ye*Ne,he=-Ye,se=ie;return{shaftX1:w,shaftY1:y,shaftX2:Ke,shaftY2:pe,headPoints:[`${M},${ne}`,`${Ke+he*T},${pe+se*T}`,`${Ke-he*T},${pe-se*T}`].join(" ")}}function Wo(o,l){return{...o,markerType:l,color:o.color||it[l]}}function Xo(o,l){return{...o,id:Yn(),order:l}}function Vo(o,l){const m=String(o||"").trim()||(l?ze(l):"Annotated session");return/\bcopy$/i.test(m)?`${m} 2`:`${m} copy`}function qo(o,l,m){if(l===m||l<0||m<0||l>=o.length||m>=o.length)return o;const g=[...o],[w]=g.splice(l,1);return w?(g.splice(m,0,w),Ie(g)):o}function te(o){return String(Math.round(_(o)*1e3)/10)}function Jo(o){const l=Number.parseFloat(o);return Number.isFinite(l)?_(l/100):null}function Zo(o,l,m){return o.kind==="pin"||o.kind==="text-note"?l==="x"||l==="y"?{...o,[l]:_(m)}:o:o.kind==="box"?l==="x"||l==="y"?{...o,[l]:_(m)}:l==="width"||l==="height"?{...o,[l]:Se(m)}:o:l==="x"||l==="y"||l==="x2"||l==="y2"?{...o,[l]:_(m)}:o}function Qo(o){return o.kind==="pin"||o.kind==="text-note"?[{key:"x",label:"X",value:te(o.x)},{key:"y",label:"Y",value:te(o.y)}]:o.kind==="box"?[{key:"x",label:"X",value:te(o.x)},{key:"y",label:"Y",value:te(o.y)},{key:"width",label:"Width",value:te(o.width)},{key:"height",label:"Height",value:te(o.height)}]:[{key:"x",label:"Start X",value:te(o.x)},{key:"y",label:"Start Y",value:te(o.y)},{key:"x2",label:"End X",value:te(o.x2)},{key:"y2",label:"End Y",value:te(o.y2)}]}function er(o){if(!o)return null;const l=Math.round(o.x*100),m=Math.round(o.y*100),g=Math.round(o.width*100),w=Math.round(o.height*100);return`crop ${l}%, ${m}% size ${g}% x ${w}%`}function tr(o){return Number.isFinite(o)?Math.min(4,Math.max(.25,Number(o.toFixed(2)))):1}function Fn(o){const l=[`Annotated attachment: ${o.title||o.image.displayName}`,`Image: ${o.image.displayName}`,`Image Reference: ${o.image.referenceLabel||o.image.assetId}`,`Task ID: ${o.taskId}`,o.globalInstruction?`Global instruction: ${o.globalInstruction}`:"Global instruction: None provided.","Markers:"];return o.annotations.length===0?(l.push("0. No markers."),l.join(`
|
|
1
|
+
import{j as t,r as a,R as qt}from"./vendor-react-CKJs5o3c.js";import{R as xs,f as Ce,e as Ct,g as vs,t as Bn,M as Jt}from"./index-CLIMgR6g.js";import{q as _s,m as js,c as Zt,T as Tn,s as ks,a2 as ws,h as An,aj as Cs,R as Is,ak as Ss,al as Ns,am as $s,an as tn,C as nn,ao as Rs,ap as Dn,aq as zn,ar as Un,as as Gn,g as Ps,f as Bs,b as sn}from"./vendor-icons-pWocEipT.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";function Ts({copied:o,disabled:l=!1,label:m,onClick:g,title:w="Copy image reference",ariaLabel:y,className:M=""}){return t.jsx(xs,{copied:o,disabled:l,label:"",onClick:g,title:w,ariaLabel:y,className:M,children:m})}const As="_shell_sf7jc_1",Es="_panel_sf7jc_16",Ms="_sessionPanel_sf7jc_22",Ls="_detailPanel_sf7jc_23",Fs="_sessionContextBar_sf7jc_29",Hs="_sessionContextLeft_sf7jc_40",Os="_sessionContextRight_sf7jc_41",Ds="_sessionContextLabel_sf7jc_56",zs="_sessionContextSpacer_sf7jc_62",Us="_canvasPanel_sf7jc_67",Gs="_canvasWorkspace_sf7jc_74",Ys="_canvasMain_sf7jc_81",Ks="_panelHeader_sf7jc_88",Ws="_panelHeaderText_sf7jc_97",Xs="_panelTitle_sf7jc_101",Vs="_canvasHeading_sf7jc_105",qs="_sessionActions_sf7jc_109",Js="_sessionList_sf7jc_117",Zs="_annotationList_sf7jc_118",Qs="_markerHelpModalBody_sf7jc_133",ea="_openImageModalBody_sf7jc_139",ta="_openImageField_sf7jc_145",na="_openImageActions_sf7jc_149",sa="_markerHelpItem_sf7jc_155",aa="_markerHelpHeader_sf7jc_163",oa="_markerHelpExample_sf7jc_175",ra="_sessionCard_sf7jc_179",ia="_annotationCard_sf7jc_180",la="_sessionEmptyState_sf7jc_197",ca="_sessionCardButton_sf7jc_204",da="_annotationCardButton_sf7jc_214",ua="_sessionCardBody_sf7jc_224",fa="_sessionCardActive_sf7jc_230",ma="_annotationCardActive_sf7jc_231",pa="_annotationMeta_sf7jc_241",ha="_annotationInstructionPreview_sf7jc_248",ba="_annotationPreviewFooter_sf7jc_256",ga="_annotationInstructionEditor_sf7jc_263",ya="_annotationTypeField_sf7jc_269",xa="_annotationInstructionButton_sf7jc_275",va="_annotationInstructionField_sf7jc_284",_a="_sessionMeta_sf7jc_288",ja="_sessionTitle_sf7jc_295",ka="_annotationTitle_sf7jc_296",wa="_sessionTimestamp_sf7jc_302",Ca="_annotationKind_sf7jc_303",Ia="_annotationInstructionTypeIcon_sf7jc_307",Sa="_sessionInstructionPreview_sf7jc_313",Na="_sessionInstructionEditor_sf7jc_321",$a="_sessionCardFooter_sf7jc_327",Ra="_toolRail_sf7jc_334",Pa="_canvasToolRail_sf7jc_343",Ba="_toolbarCluster_sf7jc_364",Ta="_toolbarViewportCluster_sf7jc_371",Aa="_toolbarSeparator_sf7jc_375",Ea="_toolBtn_sf7jc_381",Ma="_toolRailButton_sf7jc_385",La="_toolbarButton_sf7jc_395",Fa="_toolBtnActive_sf7jc_400",Ha="_toolbarActions_sf7jc_407",Oa="_toolbarSelectionActions_sf7jc_416",Da="_toolbarColorPicker_sf7jc_424",za="_colorPickerButton_sf7jc_428",Ua="_colorPickerSwatch_sf7jc_433",Ga="_colorPickerPopover_sf7jc_441",Ya="_colorOption_sf7jc_456",Ka="_colorOptionActive_sf7jc_466",Wa="_toolbarUtilities_sf7jc_473",Xa="_toolbarGeometryFields_sf7jc_481",Va="_toolbarGeometryField_sf7jc_481",qa="_toolbarGeometryLabel_sf7jc_494",Ja="_toolbarGeometryInput_sf7jc_500",Za="_iconButton_sf7jc_505",Qa="_ghostBtn_sf7jc_510",eo="_payloadBtn_sf7jc_511",to="_backToTaskBtn_sf7jc_512",no="_canvasScroller_sf7jc_532",so="_canvasFrame_sf7jc_548",ao="_canvasMedia_sf7jc_555",oo="_canvasStatusOverlay_sf7jc_563",ro="_canvasStatusCard_sf7jc_575",io="_canvasImage_sf7jc_588",lo="_overlay_sf7jc_595",co="_overlaySelect_sf7jc_601",uo="_overlayPan_sf7jc_605",fo="_overlaySvg_sf7jc_609",mo="_overlayHitLayer_sf7jc_618",po="_arrowHitArea_sf7jc_627",ho="_canvasHandleHit_sf7jc_634",bo="_canvasResizeHandleHit_sf7jc_641",go="_canvasHandleVisible_sf7jc_645",yo="_pin_sf7jc_662",xo="_note_sf7jc_663",vo="_annotationNumberBadge_sf7jc_680",_o="_box_sf7jc_705",jo="_boxNumberBadge_sf7jc_714",ko="_arrowNumberBadge_sf7jc_720",wo="_boxSurface_sf7jc_724",Co="_selected_sf7jc_737",Io="_textInput_sf7jc_754",So="_textArea_sf7jc_755",No="_select_sf7jc_737",$o="_sessionTitleInput_sf7jc_762",Ro="_sessionInstructionField_sf7jc_767",Po="_detailEmpty_sf7jc_776",Bo="_emptyState_sf7jc_777",To="_payloadModalBody_sf7jc_789",Ao="_payloadModalToolbar_sf7jc_796",Eo="_payloadViewToggle_sf7jc_803",Mo="_payloadModalActions_sf7jc_804",Lo="_payloadModalPreview_sf7jc_811",Fo="_statusBar_sf7jc_826",Ho="_annotationSummary_sf7jc_838",s={shell:As,panel:Es,sessionPanel:Ms,detailPanel:Ls,sessionContextBar:Fs,sessionContextLeft:Hs,sessionContextRight:Os,sessionContextLabel:Ds,sessionContextSpacer:zs,canvasPanel:Us,canvasWorkspace:Gs,canvasMain:Ys,panelHeader:Ks,panelHeaderText:Ws,panelTitle:Xs,canvasHeading:Vs,sessionActions:qs,sessionList:Js,annotationList:Zs,markerHelpModalBody:Qs,openImageModalBody:ea,openImageField:ta,openImageActions:na,markerHelpItem:sa,markerHelpHeader:aa,markerHelpExample:oa,sessionCard:ra,annotationCard:ia,sessionEmptyState:la,sessionCardButton:ca,annotationCardButton:da,sessionCardBody:ua,sessionCardActive:fa,annotationCardActive:ma,annotationMeta:pa,annotationInstructionPreview:ha,annotationPreviewFooter:ba,annotationInstructionEditor:ga,annotationTypeField:ya,annotationInstructionButton:xa,annotationInstructionField:va,sessionMeta:_a,sessionTitle:ja,annotationTitle:ka,sessionTimestamp:wa,annotationKind:Ca,annotationInstructionTypeIcon:Ia,sessionInstructionPreview:Sa,sessionInstructionEditor:Na,sessionCardFooter:$a,toolRail:Ra,canvasToolRail:Pa,toolbarCluster:Ba,toolbarViewportCluster:Ta,toolbarSeparator:Aa,toolBtn:Ea,toolRailButton:Ma,toolbarButton:La,toolBtnActive:Fa,toolbarActions:Ha,toolbarSelectionActions:Oa,toolbarColorPicker:Da,colorPickerButton:za,colorPickerSwatch:Ua,colorPickerPopover:Ga,colorOption:Ya,colorOptionActive:Ka,toolbarUtilities:Wa,toolbarGeometryFields:Xa,toolbarGeometryField:Va,toolbarGeometryLabel:qa,toolbarGeometryInput:Ja,iconButton:Za,ghostBtn:Qa,payloadBtn:eo,backToTaskBtn:to,canvasScroller:no,canvasFrame:so,canvasMedia:ao,canvasStatusOverlay:oo,canvasStatusCard:ro,canvasImage:io,overlay:lo,overlaySelect:co,overlayPan:uo,overlaySvg:fo,overlayHitLayer:mo,arrowHitArea:po,canvasHandleHit:ho,canvasResizeHandleHit:bo,canvasHandleVisible:go,pin:yo,note:xo,annotationNumberBadge:vo,box:_o,boxNumberBadge:jo,arrowNumberBadge:ko,boxSurface:wo,selected:Co,textInput:Io,textArea:So,select:No,sessionTitleInput:$o,sessionInstructionField:Ro,detailEmpty:Po,emptyState:Bo,payloadModalBody:To,payloadModalToolbar:Ao,payloadViewToggle:Eo,payloadModalActions:Mo,payloadModalPreview:Lo,statusBar:Fo,annotationSummary:Ho},En=[{value:"review",label:"Review"},{value:"change",label:"Change"},{value:"question",label:"Question"}],Mn=[{value:"select",label:"Select",icon:Rs},{value:"pin",label:"Pin",icon:Dn},{value:"box",label:"Box",icon:zn},{value:"arrow",label:"Arrow",icon:Un},{value:"text-note",label:"Note",icon:Gn}],Oo={pin:Dn,box:zn,arrow:Un,"text-note":Gn},Do={pin:"Pin",box:"Box",arrow:"Arrow","text-note":"Note"},zo={review:tn,change:sn,question:nn,issue:nn,idea:tn},It={select:{short:"Select and edit existing markers.",detail:"Use Select to click, drag, reorder, resize, and update markers that are already on the image.",example:"Example: move an existing marker after the screenshot changes."},pin:{short:"Mark a precise spot.",detail:"Use Pin when feedback points to one exact location instead of a broader area.",example:'Example: "This icon is misaligned by 2px."'},box:{short:"Mark an area or component.",detail:"Use Box when the feedback applies to a whole region, card, panel, or bounded UI block.",example:'Example: "This whole card needs tighter padding and a stronger border."'},arrow:{short:"Show direction or relationship.",detail:"Use Arrow when you need to show movement, attachment, flow, or source-to-target intent.",example:'Example: "This tooltip should anchor to this button, not the panel."'},"text-note":{short:"Add a comment-style point marker.",detail:"Use Note when you want a point marker that reads more like a comment or open question.",example:'Example: "Ask design whether this badge should stay."'}},it={question:"#0f766e",change:"#2563eb",issue:"#dc2626",idea:"#d97706",review:"#7c3aed"},Uo=["#7c3aed","#2563eb","#0f766e","#dc2626","#d97706","#111827"],re="review";function ze(o){const l=String(o.displayName||"").trim();return l?`${l} review`:"Annotated session"}function Yn(){return`annotation-${Math.random().toString(36).slice(2,10)}`}function _(o){return!Number.isFinite(o)||o<=0?0:o>=1?1:o}function Se(o){return _(Math.max(.02,o))}function rt(o){return o?[String(o.taskId||"").trim(),String(o.assetId||"").trim(),String(o.path||"").trim()].join("::"):""}function Go(o){if(!(o instanceof HTMLElement))return!1;const l=o.tagName.toLowerCase();return o.isContentEditable?!0:l==="input"||l==="textarea"||l==="select"}function Ie(o){return o.map((l,m)=>({...l,order:m}))}function Qt(o){if(!o)return"Unsaved";const l=new Date(o);return Number.isNaN(l.getTime())?"Unsaved":l.toLocaleString()}function St(o){const l=String(o.createdByActor?.label||"").trim();return l||null}function Yo(o){const l=String(o||"").trim().replace(/\s+/g," ");return l?l.length>110?`${l.slice(0,107)}...`:l:""}function Ln(o,l,m,g=it[re]){const w={id:Yn(),order:0,instruction:"",markerType:re,color:g};if(o==="pin")return{...w,kind:o,x:l.x,y:l.y};if(o==="text-note")return{...w,kind:o,x:l.x,y:l.y};if(o==="box"){const M=m||l;return{...w,kind:o,x:_(Math.min(l.x,M.x)),y:_(Math.min(l.y,M.y)),width:Se(Math.abs(M.x-l.x)),height:Se(Math.abs(M.y-l.y))}}const y=m||l;return{...w,kind:"arrow",x:l.x,y:l.y,x2:y.x,y2:y.y}}function en(o){return o.color?o.color:it[o.markerType||re]}function Ko(o,l){const m=Math.max(l.width,1),g=Math.max(l.height,1),w=o.x*m,y=o.y*g,M=o.x2*m,ne=o.y2*g,Ue=M-w,Ge=ne-y,me=Math.hypot(Ue,Ge)||1,ie=Ue/me,Ye=Ge/me,Ne=Math.max(10,Math.min(16,me-2)),T=Ne*.62,Ke=M-ie*Ne,pe=ne-Ye*Ne,he=-Ye,se=ie;return{shaftX1:w,shaftY1:y,shaftX2:Ke,shaftY2:pe,headPoints:[`${M},${ne}`,`${Ke+he*T},${pe+se*T}`,`${Ke-he*T},${pe-se*T}`].join(" ")}}function Wo(o,l){return{...o,markerType:l,color:o.color||it[l]}}function Xo(o,l){return{...o,id:Yn(),order:l}}function Vo(o,l){const m=String(o||"").trim()||(l?ze(l):"Annotated session");return/\bcopy$/i.test(m)?`${m} 2`:`${m} copy`}function qo(o,l,m){if(l===m||l<0||m<0||l>=o.length||m>=o.length)return o;const g=[...o],[w]=g.splice(l,1);return w?(g.splice(m,0,w),Ie(g)):o}function te(o){return String(Math.round(_(o)*1e3)/10)}function Jo(o){const l=Number.parseFloat(o);return Number.isFinite(l)?_(l/100):null}function Zo(o,l,m){return o.kind==="pin"||o.kind==="text-note"?l==="x"||l==="y"?{...o,[l]:_(m)}:o:o.kind==="box"?l==="x"||l==="y"?{...o,[l]:_(m)}:l==="width"||l==="height"?{...o,[l]:Se(m)}:o:l==="x"||l==="y"||l==="x2"||l==="y2"?{...o,[l]:_(m)}:o}function Qo(o){return o.kind==="pin"||o.kind==="text-note"?[{key:"x",label:"X",value:te(o.x)},{key:"y",label:"Y",value:te(o.y)}]:o.kind==="box"?[{key:"x",label:"X",value:te(o.x)},{key:"y",label:"Y",value:te(o.y)},{key:"width",label:"Width",value:te(o.width)},{key:"height",label:"Height",value:te(o.height)}]:[{key:"x",label:"Start X",value:te(o.x)},{key:"y",label:"Start Y",value:te(o.y)},{key:"x2",label:"End X",value:te(o.x2)},{key:"y2",label:"End Y",value:te(o.y2)}]}function er(o){if(!o)return null;const l=Math.round(o.x*100),m=Math.round(o.y*100),g=Math.round(o.width*100),w=Math.round(o.height*100);return`crop ${l}%, ${m}% size ${g}% x ${w}%`}function tr(o){return Number.isFinite(o)?Math.min(4,Math.max(.25,Number(o.toFixed(2)))):1}function Fn(o){const l=[`Annotated attachment: ${o.title||o.image.displayName}`,`Image: ${o.image.displayName}`,`Image Reference: ${o.image.referenceLabel||o.image.assetId}`,`Task ID: ${o.taskId}`,o.globalInstruction?`Global instruction: ${o.globalInstruction}`:"Global instruction: None provided.","Markers:"];return o.annotations.length===0?(l.push("0. No markers."),l.join(`
|
|
2
2
|
`)):(o.annotations.forEach((m,g)=>{const w=m.markerType||re,y=er(m.cropHint);l.push(`${g+1}. ${m.kind} (${w})`),l.push(`Instruction: ${m.instruction||"No marker instruction."}`),y&&l.push(`Region: ${y}`)}),l.join(`
|
|
3
3
|
`))}function Nt(o){return{title:o.title,globalInstruction:o.globalInstruction,annotations:Ie(o.annotations)}}function Hn(o){return JSON.stringify(Nt(o))}function On(o){const l=JSON.parse(o);return Nt({title:String(l?.title||""),globalInstruction:String(l?.globalInstruction||""),annotations:Array.isArray(l?.annotations)?l.annotations:[]})}function nr(o,l){return Nt({title:o?.title||l,globalInstruction:o?.globalInstruction||"",annotations:o?.annotations||[]})}function dr({runtimeMode:o="local",apiBaseUrl:l="",cloudAuthBaseUrl:m="",workspaceId:g="default",sessionLoadReady:w=!0,requestedTarget:y=null,requestedSessionId:M=null,requestedOpenVersion:ne=0,resolveTaskReferenceLabel:Ue,resolveImageReferenceLabel:Ge,onRequestedTargetHandled:me,onOpenTarget:ie,onContextChange:Ye,onBackToTask:Ne}){const T=o==="cloud"&&(m||l)||"",[Ke,pe]=a.useState(y),[he,se]=a.useState([]),[x,$e]=a.useState(null),[$t,We]=a.useState(!1),[Re,Xe]=a.useState(""),[Pe,Ve]=a.useState(""),[N,ae]=a.useState([]),[C,L]=a.useState(null),[lt,an]=a.useState(it[re]),[ct,dt]=a.useState(!1),[$,Be]=a.useState("select"),[Kn,be]=a.useState(!1),[ut,on]=a.useState(!1),[W,X]=a.useState(!1),[rn,h]=a.useState(null),[U,R]=a.useState("saved"),[sr,D]=a.useState(null),[ft,ln]=a.useState(!1),[oe,Rt]=a.useState(null),[le,Pt]=a.useState(!1),[Wn,Bt]=a.useState(!1),[Tt,cn]=a.useState("json"),[mt,At]=a.useState(!1),[pt,Et]=a.useState(!1),[Xn,dn]=a.useState(!1),[Vn,ht]=a.useState(!1),[Mt,bt]=a.useState(""),[qe,un]=a.useState(!1),[ce,Je]=a.useState(!1),[Lt,Ze]=a.useState(!1),[P,Ft]=a.useState(1),[G,Qe]=a.useState(!1),[gt,fn]=a.useState(!1),[yt,et]=a.useState({width:0,height:0}),mn=a.useRef(null),Te=a.useRef(null),Ht=a.useRef(null),pn=a.useRef(null),ge=a.useRef(M),ye=a.useRef(""),xt=a.useRef(0),Ot=a.useRef(""),xe=a.useRef(0),vt=a.useRef(null),hn=a.useRef(0),de=a.useRef(!1),tt=a.useRef(null),Dt=a.useRef(null),ve=a.useRef(null),F=a.useRef(""),A=a.useRef(""),_t=a.useRef(null),V=a.useRef(null),Ae=a.useRef(!1),jt=a.useRef(null),nt=a.useRef(null),st=a.useRef(null),_e=a.useRef(null),Ee=a.useRef(null),Me=a.useRef(null),Le=a.useRef(null),ue=a.useRef(null),q=a.useRef(null),bn=a.useRef(null),gn=a.useRef(null),B=a.useMemo(()=>he.find(e=>e.id===x)||null,[x,he]),J=a.useMemo(()=>`workspaceId=${encodeURIComponent(String(g||"default").trim()||"default")}`,[g]),E=a.useMemo(()=>({"x-taskforce-workspace-id":String(g||"default").trim()||"default"}),[g]),z=a.useMemo(()=>N.find(e=>e.id===C)||null,[N,C]),Z=a.useMemo(()=>rt(y),[y]),je=typeof ie=="function",d=je?y:Ke,kt=a.useMemo(()=>{const e=String(d?.taskId||"").trim();if(!e)return"";const n=Ue?.(e).trim()||"";if(n)return n;const r=String(d?.taskReferenceLabel||"").trim();return r&&r!==e?r:n||e},[d?.taskId,d?.taskReferenceLabel,Ue]),at=a.useMemo(()=>{const e=String(d?.assetId||"").trim();if(!e)return"";const n=Ge?.(e).trim()||"";return n||String(d?.imageReferenceLabel||"").trim()},[d?.assetId,d?.imageReferenceLabel,Ge]),zt=a.useMemo(()=>N.findIndex(e=>e.id===C),[N,C]),Ut=z?.color||lt,yn=a.useMemo(()=>Nt({title:Re,globalInstruction:Pe,annotations:N}),[N,Pe,Re]),Fe=a.useMemo(()=>Hn(yn),[yn]),xn=Fe!==A.current,b=a.useMemo(()=>({width:Math.max(yt.width*P,0),height:Math.max(yt.height*P,0)}),[yt.height,yt.width,P]),He=a.useMemo(()=>({visibleRadius:7,hitRadius:11}),[]),Gt=a.useMemo(()=>`0 0 ${Math.max(b.width,1)} ${Math.max(b.height,1)}`,[b.height,b.width]),ke=a.useMemo(()=>{const e=Te.current;return e?P>1||b.width>e.clientWidth+1||b.height>e.clientHeight+1:P>1},[b.height,b.width,P]),Yt=`${Math.round(P*100)}%`,Kt=a.useMemo(()=>{const e=new Map;return N.forEach((n,r)=>{e.set(n.id,r+1)}),e},[N]);a.useEffect(()=>{ge.current=M},[M]),a.useEffect(()=>{const e=pn.current;if(!e||!C)return;e.focus();const n=e.value.length;e.setSelectionRange(n,n)},[C]),a.useEffect(()=>{ve.current=x},[x]),a.useEffect(()=>{We(!1)},[x]),a.useEffect(()=>{if(!z){dt(!1);return}an(z.color||it[z.markerType||re])},[z]);const wt=a.useCallback(async e=>{const n=typeof performance<"u"?performance.now():Date.now(),r=await Ce(`/api/taskforce/annotated-attachments/sessions?${J}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...E},body:JSON.stringify({taskId:e.taskId,baseImageAssetId:e.assetId,title:ze(e),globalInstruction:"",annotations:[]})},T),i=await r.json().catch(()=>({}));if(!r.ok)throw new Error(String(i?.error||"Failed to create session."));const c=i?.session;if(!c?.id)throw new Error("Failed to create session.");return Ct("annotated_session_create_completed",{assetId:e.assetId,taskId:e.taskId||null,sessionId:c.id,durationMs:Math.round((typeof performance<"u"?performance.now():Date.now())-n),debugTimings:i?.debugTimings||null,serverTiming:typeof r.headers?.get=="function"&&r.headers.get("server-timing")||null}),c},[E,T,J]),vn=a.useCallback(e=>new Promise((n,r)=>{const i=new FileReader;i.onload=()=>n(typeof i.result=="string"?i.result:""),i.onerror=()=>r(i.error||new Error("Failed to read clipboard image.")),i.readAsDataURL(e)}),[]),I=a.useCallback(()=>{tt.current!==null&&(window.clearTimeout(tt.current),tt.current=null),st.current!==null&&(window.clearTimeout(st.current),st.current=null)},[]),Oe=a.useCallback(e=>{Xe(e.title),Ve(e.globalInstruction),ae(e.annotations),L(n=>n&&e.annotations.some(r=>r.id===n)?n:e.annotations[0]?.id||null)},[]),fe=a.useCallback((e,n)=>{const r=ze(n||d||{assetId:e.baseImageAssetId,displayName:"Annotated session"}),i=nr(e,r),c=Hn(i);de.current=!0,I(),Oe(i),A.current=c,F.current=c,_t.current=null,Ae.current=!1,jt.current=null,nt.current=null,D(null),R("saved"),X(!1)},[d,Oe,I]),Wt=a.useCallback(e=>{de.current=!0,I(),se([]),$e(null),be(!1),We(!1),Xe(ze(e)),Ve(""),ae([]),L(null),A.current="",F.current="",_t.current=null,Ae.current=!1,jt.current=null,nt.current=null,Rt(null),Bt(!1),D(null),R("saved"),X(!1)},[I]),we=a.useCallback(async(e,n,r)=>{const i=ve.current;if(!i)return!0;if(e===A.current)return V.current||(D(null),R("saved")),!0;if(V.current){if(Ae.current=!0,!r?.waitForInFlight||!await V.current)return!1;const f=F.current;return f===A.current?!0:we(f,n,r)}I();const c=On(e);jt.current=i,_t.current=e,X(!0),h(null),D(null),R("saving");const p=(async()=>{try{const u=await Ce(`/api/taskforce/annotated-attachments/sessions/${encodeURIComponent(i)}?${J}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json",...E},body:JSON.stringify(c)},T),f=await u.json().catch(()=>({}));if(!u.ok)throw new Error(String(f?.error||"Failed to save session."));const v=f?.session;if(!v?.id)throw new Error("Failed to save session.");se(Vt=>{const Rn=Vt.findIndex(ys=>ys.id===v.id);if(Rn===-1)return[v,...Vt];const Pn=[...Vt];return Pn[Rn]=v,Pn}),A.current=e,nt.current=null,D(null);const k=ve.current===v.id,S=F.current,K=S!==e,ot=Ae.current||K;return Ae.current=!1,k&&!ot?(de.current=!0,Oe(c),R("saved")):!ot&&S===A.current?R("saved"):R("pending"),!0}catch(u){const f=u instanceof Error?u.message:"Failed to save session.";return h(f),D(f),R("error"),nt.current!==e&&ve.current===i&&F.current===e&&(nt.current=e,st.current=window.setTimeout(()=>{st.current=null,!(ve.current!==i||F.current!==e)&&we(e,n,{waitForInFlight:!0})},1500)),!1}finally{_t.current=null,V.current=null,jt.current=null,X(!1)}})();V.current=p;const j=await p;if(j){const u=F.current;if(u!==A.current)return we(u,n,r)}return j},[Oe,I,E,T,J]),_n=a.useCallback(e=>{if(ve.current){if(F.current===A.current){D(null),V.current||R("saved");return}U!=="saving"&&R("pending"),I(),tt.current=window.setTimeout(()=>{tt.current=null,we(F.current,"structure")},e)}},[I,we,U]),Y=a.useCallback(e=>{Dt.current=e},[]),H=a.useCallback(async e=>{I();const n=F.current;return!ve.current||n===A.current?(D(null),V.current||R("saved"),!0):we(n,e,{waitForInFlight:!0})},[I,we]),qn=a.useCallback(()=>{const e=A.current;e&&(I(),de.current=!0,Oe(On(e)),D(null),R("saved"),h(null))},[Oe,I]),Q=a.useCallback(async(e,n)=>{const r=typeof performance<"u"?performance.now():Date.now(),i=JSON.stringify({targetKey:rt(e),requestedSessionId:n?.requestedSessionId??null,autoCreateIfEmpty:n?.autoCreateIfEmpty===!0});if(vt.current===i)return;const c=xe.current+1;xe.current=c,vt.current=i,on(!0),h(null);try{const p=new URLSearchParams;g&&p.set("workspaceId",g),e.taskId&&p.set("taskId",e.taskId),p.set("imageAssetId",e.assetId);const j=await Ce(`/api/taskforce/annotated-attachments/sessions?${p.toString()}`,{credentials:"include",headers:E,cache:"no-store"},T),u=await j.json().catch(()=>({}));if(!j.ok)throw new Error(String(u?.error||"Failed to load annotated attachment sessions."));const f=Array.isArray(u?.sessions)?u.sessions:[];if(Ct("annotated_sessions_loaded",{assetId:e.assetId,taskId:e.taskId||null,requestedSessionId:n?.requestedSessionId??null,autoCreateIfEmpty:n?.autoCreateIfEmpty===!0,sessionCount:f.length,durationMs:Math.round((typeof performance<"u"?performance.now():Date.now())-r),serverTiming:typeof j.headers?.get=="function"&&j.headers.get("server-timing")||null}),f.length===0&&n?.autoCreateIfEmpty&&String(e.assetId||"").trim()){const S=n?.requestedSessionId??ge.current;if(ge.current=null,S&&h("The previously selected annotation session could not be restored."),xe.current!==c)return;const K=await wt(e);if(Ct("annotated_sessions_auto_created_after_empty_load",{assetId:e.assetId,taskId:e.taskId||null,sessionId:K.id,totalDurationMs:Math.round((typeof performance<"u"?performance.now():Date.now())-r)}),xe.current!==c)return;se([K]),$e(K.id),be(!1),fe(K,e);return}if(xe.current!==c)return;se(f),be(f.length===0);const v=n?.requestedSessionId??ge.current;ge.current=null;const k=f.find(S=>S.id===v)||f[0]||null;v&&!k&&h("The previously selected annotation session could not be restored."),$e(k?.id||null),k?fe(k,e):(de.current=!0,I(),Xe(ze(e)),Ve(""),ae([]),L(null),A.current="",F.current="",D(null),R("saved"))}catch(p){if(xe.current!==c)return;h(p instanceof Error?p.message:"Failed to load sessions.")}finally{vt.current===i&&(vt.current=null),xe.current===c&&on(!1)}},[I,wt,E,T,fe,g]),Jn=a.useCallback(async()=>{if(typeof navigator>"u"||!navigator.clipboard||typeof navigator.clipboard.read!="function"){h("Clipboard image paste is not supported in this environment.");return}fn(!0),h(null),hn.current=Date.now()+2e3;try{const n=(await navigator.clipboard.read()).find(v=>v.types.some(k=>k.startsWith("image/"))),r=n?.types.find(v=>v.startsWith("image/"))||"";if(!n||!r)throw new Error("No image found on the clipboard.");const i=await n.getType(r),c=await vn(i);if(!c)throw new Error("Failed to read clipboard image.");const p=r==="image/jpeg"?"jpg":r==="image/webp"?"webp":r==="image/gif"?"gif":"png",j=await fetch("/api/taskforce/context-upload",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...E},body:JSON.stringify({file:c,originalName:`pasted-image.${p}`,workspaceId:String(g||"default").trim()||"default"})}),u=await j.json().catch(()=>({}));if(!j.ok||!u?.success||typeof u?.assetId!="string"||typeof u?.path!="string")throw new Error(String(u?.error||"Failed to paste image into Image Notes."));const f={assetId:u.assetId,imageReferenceLabel:typeof u?.referenceLabel=="string"?u.referenceLabel:void 0,path:u.path,displayName:typeof u?.displayName=="string"&&u.displayName.trim().length>0?u.displayName.trim():"Pasted image"};ge.current=null,je?(ye.current="",ie?.(f,{sessionId:null})):(pe(f),ye.current=rt(f),Q(f,{autoCreateIfEmpty:!0}))}catch(e){h(e instanceof Error?e.message:"Failed to paste image.")}finally{fn(!1)}},[je,Q,ie,vn,E,g]),Zn=a.useCallback(async()=>{const e=Mt.trim();if(!e){h("Enter an image reference to open.");return}un(!0),h(null);try{const n=new URLSearchParams({workspaceId:String(g||"default").trim()||"default"}),r=await Ce(`/api/taskforce/annotated-attachments/images/${encodeURIComponent(e)}?${n.toString()}`,{method:"GET",credentials:"include",headers:E}),i=await r.json().catch(()=>({}));if(!r.ok||!i?.target||typeof i.target.assetId!="string"||typeof i.target.path!="string")throw new Error(String(i?.error||"Failed to open image reference."));const c={assetId:i.target.assetId,path:i.target.path,displayName:typeof i.target.displayName=="string"&&i.target.displayName.trim().length>0?i.target.displayName.trim():"Image attachment",taskId:typeof i.target.taskId=="string"&&i.target.taskId.trim().length>0?i.target.taskId.trim():void 0,taskReferenceLabel:typeof i.target.taskReferenceLabel=="string"&&i.target.taskReferenceLabel.trim().length>0?i.target.taskReferenceLabel.trim():void 0,imageReferenceLabel:typeof i.target.imageReferenceLabel=="string"&&i.target.imageReferenceLabel.trim().length>0?i.target.imageReferenceLabel.trim():void 0};ge.current=null,ht(!1),bt(""),je?(ye.current="",ie?.(c,{sessionId:null})):(pe(c),ye.current=rt(c),Q(c,{autoCreateIfEmpty:!0}))}catch(n){h(n instanceof Error?n.message:"Failed to open image reference.")}finally{un(!1)}},[je,Q,ie,Mt,E,g]),jn=a.useCallback(e=>{se(n=>{const r=n.findIndex(c=>c.id===e.id);if(r===-1)return[e,...n];const i=[...n];return i[r]=e,i}),$e(e.id),fe(e,d)},[d,fe]),kn=a.useCallback(e=>{se(n=>{const r=n.filter(c=>c.id!==e),i=r[0]||null;return $e(i?.id||null),i?fe(i,d):(de.current=!0,I(),Xe(ze(d||{displayName:"Annotated session"})),Ve(""),ae([]),L(null),A.current="",F.current="",D(null),R("saved")),r})},[d,I,fe]);a.useEffect(()=>{if(!y)return;if(je||pe(n=>n&&rt(n)===Z&&n.taskReferenceLabel===y.taskReferenceLabel&&n.imageReferenceLabel===y.imageReferenceLabel&&n.displayName===y.displayName?n:y),!w){Z&&(Z!==ye.current||ne!==xt.current)&&Z!==Ot.current&&(Wt(y),Ot.current=Z,xt.current=ne,Ct("annotated_sessions_load_deferred",{assetId:y.assetId,taskId:y.taskId||null,requestedTargetKey:Z})),me?.();return}Z&&(Z!==ye.current||ne!==xt.current)&&(Wt(y),ye.current=Z,xt.current=ne,Ot.current="",Q(y,{autoCreateIfEmpty:!0})),me?.()},[je,Q,me,ne,y,Z,Wt,M,w]),a.useEffect(()=>{d&&Ye?.({target:d,sessionId:x})},[d,Ye,x]),a.useEffect(()=>{if(!d||typeof document>"u"||!w)return;const e=()=>{Date.now()<hn.current||F.current!==A.current||W||ut||Q(d,{requestedSessionId:x})},n=()=>{document.visibilityState==="visible"&&e()};return document.addEventListener("visibilitychange",n),()=>{document.removeEventListener("visibilitychange",n)}},[d,Q,ut,W,x,w]),a.useEffect(()=>{if(!d?.path){Je(!1),Ze(!1),et({width:0,height:0});return}Je(!0),Ze(!1),et({width:0,height:0}),Ft(1),Qe(!1)},[d?.path]),a.useEffect(()=>{if(!ce)return;const e=Ht.current;!e||!e.complete||e.naturalWidth<=0||e.naturalHeight<=0||(et({width:e.naturalWidth,height:e.naturalHeight}),Je(!1),Ze(!1))},[d?.path,ce]),a.useEffect(()=>{ke||Qe(!1)},[ke]),a.useEffect(()=>{Rt(null)},[x]),a.useEffect(()=>{if(F.current=Fe,de.current){de.current=!1;return}if(!x){I(),D(null),R("saved");return}if(Fe===A.current){I(),V.current||(D(null),R("saved"));return}const e=Dt.current;if(Dt.current=null,U==="error"&&e===null)return;const n=e??600;if(V.current){Ae.current=!0,R("pending");return}_n(n)},[I,Fe,U,_n,x]),a.useEffect(()=>{x&&(V.current||Fe===A.current&&U!=="error"&&U!=="saved"&&(D(null),R("saved")))},[Fe,U,x]),a.useEffect(()=>{if(typeof window>"u")return;const e=n=>{F.current!==A.current&&(n.preventDefault(),n.returnValue="")};return window.addEventListener("beforeunload",e),()=>window.removeEventListener("beforeunload",e)},[]),a.useEffect(()=>{if(typeof document>"u")return;const e=()=>{document.visibilityState==="hidden"&&H("visibility-hidden")};return document.addEventListener("visibilitychange",e),()=>document.removeEventListener("visibilitychange",e)},[H]),a.useEffect(()=>{if(!ct||typeof document>"u")return;const e=n=>{const r=n.target;r instanceof Node&&(bn.current?.contains(r)||dt(!1))};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[ct]),a.useEffect(()=>{if(!$t||typeof document>"u")return;const e=n=>{const r=n.target;r instanceof Node&&(gn.current?.contains(r)||We(!1))};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[$t]),a.useEffect(()=>()=>{I()},[I]),a.useEffect(()=>{if(!le)return;const e=window.setTimeout(()=>Pt(!1),2e3);return()=>window.clearTimeout(e)},[le]),a.useEffect(()=>{if(!mt)return;const e=window.setTimeout(()=>At(!1),2e3);return()=>window.clearTimeout(e)},[mt]),a.useEffect(()=>{if(!pt)return;const e=window.setTimeout(()=>Et(!1),2e3);return()=>window.clearTimeout(e)},[pt]);const ee=a.useCallback((e,n,r=300)=>{ae(i=>Ie(i.map(c=>c.id===e?n(c):c))),Y(r)},[Y]),Qn=a.useCallback(e=>{z&&(ee(z.id,n=>({...n,color:e})),an(e),dt(!1))},[z,ee]),wn=a.useCallback(async()=>{if(!(!d||!await H("session-switch"))){X(!0),h(null);try{const n=await wt(d);be(!1),await Q(d,{requestedSessionId:n.id,autoCreateIfEmpty:!1}),Be("select")}catch(n){h(n instanceof Error?n.message:"Failed to create session."),be(!0)}finally{X(!1)}}},[d,wt,H,Q]),es=a.useCallback(e=>{if(e!=="select"&&!B){Qe(!1),h("No session exists for this image yet."),be(!0);return}h(null),be(!1),Qe(!1),Be(e)},[B]),ts=a.useCallback(async()=>{if(!(!d||!B||!await H("duplicate"))){X(!0),h(null);try{const n=Ie(N.map((p,j)=>Xo(p,j))),r=await Ce(`/api/taskforce/annotated-attachments/sessions?${J}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...E},body:JSON.stringify({taskId:d.taskId,baseImageAssetId:d.assetId,title:Vo(Re||B.title,d),globalInstruction:Pe,annotations:n})},T),i=await r.json().catch(()=>({}));if(!r.ok)throw new Error(String(i?.error||"Failed to duplicate session."));const c=i?.session;if(!c?.id)throw new Error("Failed to duplicate session.");jn(c),Be("select")}catch(n){h(n instanceof Error?n.message:"Failed to duplicate session.")}finally{X(!1)}}},[d,N,Pe,Re,jn,H,E,T,B,J]),ns=a.useCallback(async()=>H("manual"),[H]),Cn=a.useCallback(async()=>{if(x){ln(!0),h(null);try{const e=await Ce(`/api/taskforce/annotated-attachments/sessions/${encodeURIComponent(x)}/payload?${J}`,{credentials:"include",headers:E},T),n=await e.json().catch(()=>({}));if(!e.ok)throw new Error(String(n?.error||"Failed to load payload preview."));Rt(n?.payload||null)}catch(e){h(e instanceof Error?e.message:"Failed to load payload preview.")}finally{ln(!1)}}},[E,T,x,J]),ss=a.useCallback(()=>{x&&H("payload-preview").then(e=>{e&&(Bt(!0),Cn())})},[H,Cn,x]),In=a.useCallback(async e=>{if(!oe||!navigator.clipboard||typeof navigator.clipboard.writeText!="function"){h("Clipboard copy is not available in this browser.");return}try{const n=e==="json"?JSON.stringify(oe,null,2):Fn(oe);await navigator.clipboard.writeText(n),Pt(e)}catch(n){h(n instanceof Error?n.message:"Failed to copy payload content."),Pt(!1)}},[oe]),as=a.useCallback(async()=>{if(!kt||!navigator.clipboard||typeof navigator.clipboard.writeText!="function"){h("Clipboard copy is not available in this browser.");return}try{await navigator.clipboard.writeText(kt),At(!0)}catch(e){h(e instanceof Error?e.message:"Failed to copy task id."),At(!1)}},[kt]),os=a.useCallback(async()=>{if(!at||!navigator.clipboard||typeof navigator.clipboard.writeText!="function"){h("Clipboard copy is not available in this browser.");return}try{await navigator.clipboard.writeText(at),Et(!0)}catch(e){h(e instanceof Error?e.message:"Failed to copy image reference."),Et(!1)}},[at]),rs=a.useCallback(async()=>{if(!x||!await H("delete-session"))return;const n=(B?.title||"Untitled session").trim()||"Untitled session";if(window.confirm(`Delete the annotated attachment session "${n}"?`)){X(!0),h(null);try{const r=await Ce(`/api/taskforce/annotated-attachments/sessions/${encodeURIComponent(x)}?${J}`,{method:"DELETE",credentials:"include",headers:E},T),i=await r.json().catch(()=>({}));if(!r.ok)throw new Error(String(i?.error||"Failed to delete session."));kn(x),Be("select")}catch(r){h(r instanceof Error?r.message:"Failed to delete session.")}finally{X(!1)}}},[H,kn,E,T,B,x,J]),Xt=a.useCallback(()=>{C&&(ae(e=>Ie(e.filter(n=>n.id!==C))),L(null),Y(300))},[Y,C]),De=a.useCallback(e=>{const n=tr(e),r=Te.current;if(!r||n===P){Ft(n);return}const i=(r.scrollLeft+r.clientWidth/2)*(n/P)-r.clientWidth/2,c=(r.scrollTop+r.clientHeight/2)*(n/P)-r.clientHeight/2;Ft(n),window.requestAnimationFrame(()=>{r.scrollLeft=Math.max(0,i),r.scrollTop=Math.max(0,c)})},[P]),is=a.useCallback(()=>{De(P+.25)},[De,P]),ls=a.useCallback(()=>{De(P-.25)},[De,P]),cs=a.useCallback(()=>{De(1);const e=Te.current;e&&window.requestAnimationFrame(()=>{e.scrollLeft=0,e.scrollTop=0})},[De]),Sn=a.useCallback(e=>{C&&ae(n=>{const r=n.findIndex(c=>c.id===C);if(r===-1)return n;const i=e==="up"?r-1:r+1;return i<0||i>=n.length?n:(Y(300),qo(n,r,i))})},[Y,C]);a.useEffect(()=>{if(!C)return;const e=n=>{n.key==="Delete"&&(Go(n.target)||(n.preventDefault(),Xt()))};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[Xt,C]);const O=a.useCallback(e=>{const n=mn.current?.getBoundingClientRect();return!n||n.width<=0||n.height<=0?null:{x:_((e.clientX-n.left)/n.width),y:_((e.clientY-n.top)/n.height)}},[]),ds=a.useCallback(e=>{if(G&&ke){const i=Te.current;if(!i)return;q.current={pointerId:e.pointerId,startX:e.clientX,startY:e.clientY,scrollLeft:i.scrollLeft,scrollTop:i.scrollTop},e.currentTarget.setPointerCapture(e.pointerId),e.preventDefault();return}if(!x)return;if($==="select"){L(null);return}const n=O(e);if(!n)return;if($==="pin"||$==="text-note"){const i=Ln($,n,n,lt);ae(c=>Ie([...c,i])),L(i.id),Y(300),Be("select");return}_e.current=n;const r=Ln($,n,n,lt);ue.current={annotationId:r.id,kind:$},e.currentTarget.setPointerCapture?.(e.pointerId),ae(i=>Ie([...i,r])),L(r.id),Y(300)},[ke,lt,Y,G,O,x,$]),us=a.useCallback(e=>{if(q.current?.pointerId===e.pointerId){e.currentTarget.releasePointerCapture?.(e.pointerId);return}if(!_e.current||!ue.current||$!=="box"&&$!=="arrow")return;const n=O(e);_e.current=null,ue.current=null,e.currentTarget.releasePointerCapture?.(e.pointerId),n&&Be("select")},[O,$]),Nn=a.useCallback((e,n)=>{if($!=="select"||G)return;const r=O(e);r&&(Ee.current={annotationId:n.id,originPointer:r,originAnnotation:n},L(n.id),e.stopPropagation())},[G,O,$]),fs=a.useCallback(e=>{if(q.current?.pointerId===e.pointerId){const u=Te.current;if(!u)return;const f=e.clientX-q.current.startX,v=e.clientY-q.current.startY;u.scrollLeft=q.current.scrollLeft-f,u.scrollTop=q.current.scrollTop-v;return}if(ue.current&&_e.current){const u=O(e);if(!u)return;const{annotationId:f,kind:v}=ue.current,k=_e.current;ee(f,S=>v==="box"&&S.kind==="box"?{...S,x:_(Math.min(k.x,u.x)),y:_(Math.min(k.y,u.y)),width:Se(Math.abs(u.x-k.x)),height:Se(Math.abs(u.y-k.y))}:v==="arrow"&&S.kind==="arrow"?{...S,x:k.x,y:k.y,x2:u.x,y2:u.y}:S);return}if(Me.current){const u=O(e);if(!u)return;const{annotationId:f,originPointer:v,originAnnotation:k}=Me.current,S=u.x-v.x,K=u.y-v.y;ee(f,()=>({...k,width:Se(k.width+S),height:Se(k.height+K)}));return}if(Le.current){const u=O(e);if(!u)return;const{annotationId:f,endpoint:v,originPointer:k,originAnnotation:S}=Le.current,K=u.x-k.x,ot=u.y-k.y;ee(f,()=>v==="tail"?{...S,x:_(S.x+K),y:_(S.y+ot)}:{...S,x2:_(S.x2+K),y2:_(S.y2+ot)});return}if(!Ee.current)return;const n=O(e);if(!n)return;const{annotationId:r,originPointer:i,originAnnotation:c}=Ee.current,p=n.x-i.x,j=n.y-i.y;ee(r,()=>c.kind==="pin"||c.kind==="text-note"?{...c,x:_(c.x+p),y:_(c.y+j)}:c.kind==="box"?{...c,x:_(c.x+p),y:_(c.y+j)}:{...c,x:_(c.x+p),y:_(c.y+j),x2:_(c.x2+p),y2:_(c.y2+j)})},[O,ee]),ms=a.useCallback(()=>{_e.current=null,Ee.current=null,Me.current=null,Le.current=null,ue.current=null,q.current=null},[]),ps=a.useCallback(()=>{_e.current=null,Ee.current=null,Me.current=null,Le.current=null,ue.current=null,q.current=null},[]),hs=a.useCallback(()=>{Ee.current=null,Me.current=null,Le.current=null,ue.current=null,q.current=null},[]),bs=a.useCallback((e,n)=>{if($!=="select"||G)return;const r=O(e);r&&(Me.current={annotationId:n.id,originPointer:r,originAnnotation:n},L(n.id),e.stopPropagation())},[G,O,$]),$n=a.useCallback((e,n,r)=>{if($!=="select"||G)return;const i=O(e);i&&(Le.current={annotationId:n.id,originPointer:i,originAnnotation:n,endpoint:r},L(n.id),e.stopPropagation())},[G,O,$]),gs=B?.updatedAt?Qt(B.updatedAt):"Not saved yet";return t.jsxs("div",{className:s.shell,children:[t.jsxs("section",{className:`tf-surface-panel ${s.panel} ${s.sessionPanel}`,children:[t.jsx("div",{className:s.sessionContextBar,children:d?.taskId?t.jsxs(t.Fragment,{children:[t.jsx("div",{className:s.sessionContextLeft,children:Ne?t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn} ${s.backToTaskBtn}`,onClick:()=>{H("back-to-task").then(e=>{e&&d.taskId&&Ne(d.taskId)})},"aria-label":"Back to task",title:"Back to task",children:t.jsx(_s,{size:16})}):t.jsx("div",{className:s.sessionContextSpacer,"aria-hidden":"true"})}),t.jsx("div",{className:s.sessionContextRight,children:t.jsx(vs,{copied:mt,onClick:()=>{as()},title:"Copy task id",ariaLabel:mt?"Copied task id":"Copy task id",label:kt})})]}):t.jsxs(t.Fragment,{children:[t.jsx("div",{className:s.sessionContextLeft,children:t.jsx("span",{className:`tf-label-micro ${s.sessionContextLabel}`,children:"Unattached Image"})}),t.jsx("div",{className:s.sessionContextRight,children:t.jsx("div",{className:s.sessionContextSpacer,"aria-hidden":"true"})})]})}),t.jsxs("div",{className:s.panelHeader,children:[t.jsxs("div",{className:s.panelHeaderText,children:[t.jsx("div",{className:`tf-heading-card ${s.panelTitle}`,children:"Sessions"}),d?null:t.jsx("div",{className:"tf-text-secondary",children:"Open an image attachment to begin"})]}),t.jsx("div",{className:s.sessionActions,children:t.jsx("button",{type:"button",className:`tf-control-icon ${s.iconButton}`,onClick:()=>{wn()},disabled:!d||W,"aria-label":"Create session",title:"Create session",children:t.jsx(js,{size:16})})})]}),d?ut?t.jsx("div",{className:s.emptyState,children:t.jsx("p",{className:"tf-empty-copy",children:"Loading sessions…"})}):t.jsx("div",{className:`tf-scrollbar ${s.sessionList}`,children:he.length===0?t.jsxs("div",{className:s.sessionEmptyState,children:[t.jsx("div",{className:`tf-heading-card ${s.sessionTitle}`,children:"No sessions yet"}),t.jsx("div",{className:`tf-text-secondary ${s.annotationSummary}`,children:"Create the first annotation session for this image."})]}):he.map(e=>{const n=x===e.id,r=n&&$t,i=Yo(n?Pe:e.globalInstruction),c=(n?Re:e.title)||"Untitled session";return t.jsxs("div",{className:`tf-surface-elevated ${s.sessionCard} ${n?s.sessionCardActive:""}`,ref:n?gn:void 0,onBlur:r?p=>{const j=p.relatedTarget;j instanceof Node&&p.currentTarget.contains(j)||We(!1)}:void 0,children:[r?t.jsxs("div",{className:s.sessionCardBody,children:[t.jsxs("div",{className:s.sessionMeta,children:[t.jsx("label",{className:"tf-field-label",htmlFor:"annotated-session-title",children:"Session title"}),t.jsx("input",{id:"annotated-session-title",className:`tf-field-shell ${s.textInput} ${s.sessionTitleInput}`,value:Re,onChange:p=>{Xe(p.target.value),Y(600)},placeholder:"Session title"}),t.jsx("span",{className:`tf-text-meta ${s.sessionTimestamp}`,children:Qt(e.updatedAt)})]}),St(e)?t.jsxs("div",{className:`tf-text-secondary ${s.sessionCreator}`,children:["Created by ",St(e)]}):null,t.jsxs("div",{className:s.sessionInstructionEditor,children:[t.jsx("label",{className:"tf-field-label",htmlFor:"annotated-session-instruction",children:"Session instruction"}),t.jsx("textarea",{id:"annotated-session-instruction",className:`tf-field-shell ${s.textArea} ${s.sessionInstructionField}`,value:Pe,onChange:p=>{Ve(p.target.value),Y(600)},placeholder:"Add overall instructions, context, or framing for this session."})]}),t.jsxs("div",{className:`tf-text-secondary ${s.annotationSummary}`,children:[e.annotations.length," annotation",e.annotations.length===1?"":"s"]})]}):t.jsxs("button",{type:"button",className:s.sessionCardButton,onClick:()=>{if(n){We(!0);return}H("session-switch").then(p=>{p&&($e(e.id),fe(e,d))})},"aria-pressed":n,children:[t.jsxs("div",{className:s.sessionMeta,children:[t.jsx("span",{className:`tf-heading-card ${s.sessionTitle}`,children:c}),t.jsx("span",{className:`tf-text-meta ${s.sessionTimestamp}`,children:Qt(e.updatedAt)})]}),St(e)?t.jsxs("div",{className:`tf-text-secondary ${s.sessionCreator}`,children:["Created by ",St(e)]}):null,i?t.jsx("div",{className:`tf-text-secondary ${s.sessionInstructionPreview}`,children:i}):null,t.jsxs("div",{className:`tf-text-secondary ${s.annotationSummary}`,children:[e.annotations.length," annotation",e.annotations.length===1?"":"s"]})]}),n?t.jsx("div",{className:s.sessionCardFooter,children:t.jsxs(t.Fragment,{children:[t.jsx("button",{type:"button",className:`tf-button-ghost ${s.toolRailButton} ${s.iconButton}`,onClick:()=>{ts()},disabled:W,"aria-label":"Duplicate session",title:`Duplicate session "${c}"`,children:t.jsx(Zt,{size:16})}),t.jsx("button",{type:"button",className:`tf-button-ghost ${s.toolRailButton} ${s.iconButton}`,onClick:()=>{rs()},disabled:W,"aria-label":"Delete session",title:`Delete session "${c}"`,children:t.jsx(Tn,{size:16})})]})}):null]},e.id)})}):t.jsxs("div",{className:s.emptyState,children:[t.jsx("strong",{className:"tf-empty-title",children:"No image selected"}),t.jsx("p",{className:"tf-empty-copy",children:"Open an image attachment from a task to start an annotated session."})]})]}),t.jsxs("section",{className:`tf-surface-panel ${s.panel} ${s.canvasPanel}`,children:[t.jsxs("div",{className:s.panelHeader,children:[t.jsxs("div",{className:s.canvasHeading,children:[t.jsx("div",{className:"tf-heading-card",children:d?.displayName||"Annotated attachment"}),t.jsx("div",{className:"tf-text-secondary",children:B?`${N.length} markers · ${gs}`:"Pick or create a session"})]}),at?t.jsx(Ts,{copied:pt,onClick:()=>{os()},title:"Copy image reference",ariaLabel:pt?"Copied image reference":"Copy image reference",label:at}):null]}),t.jsxs("div",{className:s.toolRail,children:[t.jsxs("div",{className:`${s.toolbarCluster} ${s.toolbarViewportCluster}`,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn} ${s.toolbarButton}`,onClick:()=>{ns()},disabled:!B||W||!xn&&U!=="error","aria-label":W?"Saving session":"Save session",title:W?"Saving session":"Save session",children:t.jsx(ks,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn} ${s.toolbarButton}`,onClick:()=>ht(!0),disabled:qe,"aria-label":"Open image",title:"Open image",children:t.jsx(ws,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn} ${s.toolbarButton}`,onClick:()=>{Jn()},disabled:gt,"aria-label":gt?"Pasting image":"Paste image",title:gt?"Pasting image":"Paste image",children:gt?t.jsx(An,{size:18,className:Bn.spin}):t.jsx(Cs,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn} ${s.toolbarButton}`,onClick:()=>{B&&qn()},disabled:!B||!xn,"aria-label":"Reset unsaved changes",title:"Reset unsaved changes",children:t.jsx(Is,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost ${s.toolRailButton} ${s.iconButton}`,onClick:ls,disabled:!d||ce||P<=.25,"aria-label":"Zoom out",children:t.jsx(Ss,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn} ${s.toolbarButton}`,onClick:cs,disabled:!d||ce||P===1,"aria-label":`Reset zoom to 100 percent (currently ${Yt})`,title:`Reset zoom to 100% (currently ${Yt})`,children:t.jsx("span",{children:Yt})}),t.jsx("button",{type:"button",className:`tf-button-ghost ${s.toolRailButton} ${s.iconButton}`,onClick:is,disabled:!d||ce||P>=4,"aria-label":"Zoom in",children:t.jsx(Ns,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.toolBtn} ${s.toolbarButton} ${G?s.toolBtnActive:""}`,onClick:()=>Qe(e=>!e),disabled:!d||ce||!ke,"aria-label":"Pan canvas",title:"Pan canvas",children:t.jsx($s,{size:18})})]}),t.jsx("span",{className:s.toolbarSeparator,"aria-hidden":"true"}),t.jsxs("div",{className:s.toolbarActions,children:[t.jsx("div",{className:s.toolbarSelectionActions,children:z?t.jsxs(t.Fragment,{children:[t.jsxs("div",{ref:bn,className:s.toolbarColorPicker,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn} ${s.toolbarButton} ${s.colorPickerButton}`,onClick:()=>dt(e=>!e),"aria-label":"Marker color","aria-expanded":ct,title:"Marker color",children:t.jsx("span",{className:s.colorPickerSwatch,style:{backgroundColor:Ut},"aria-hidden":"true"})}),ct?t.jsx("div",{className:s.colorPickerPopover,role:"menu","aria-label":"Marker color options",children:Uo.map(e=>t.jsx("button",{type:"button",className:`${s.colorOption} ${Ut===e?s.colorOptionActive:""}`,style:{backgroundColor:e},onClick:()=>Qn(e),"aria-label":`Use marker color ${e}`,"aria-pressed":Ut===e},e))}):null]}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn} ${s.toolbarButton}`,onClick:Xt,"aria-label":"Delete marker",title:"Delete selected marker",children:t.jsx(Tn,{size:18})}),t.jsx("div",{className:s.toolbarGeometryFields,"aria-label":"Marker geometry percent controls",children:Qo(z).map(e=>t.jsxs("label",{className:s.toolbarGeometryField,children:[t.jsx("span",{className:`tf-text-meta ${s.toolbarGeometryLabel}`,children:e.label}),t.jsx("input",{className:`tf-field-shell ${s.toolbarGeometryInput}`,type:"number",min:0,max:100,step:.1,value:e.value,onChange:n=>{const r=Jo(n.target.value);r!==null&&ee(z.id,i=>Zo(i,e.key,r))},"aria-label":`${e.label} percent`})]},e.key))})]}):null}),t.jsxs("div",{className:s.toolbarUtilities,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn} ${s.payloadBtn} ${s.toolbarButton}`,onClick:ss,disabled:!B||ft,"aria-label":ft?"Loading payload preview":"Preview payload",title:ft?"Loading payload preview":"Preview payload",children:t.jsx(tn,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn} ${s.toolbarButton}`,onClick:()=>dn(!0),"aria-label":"How to use marker tools",title:"How to use marker tools",children:t.jsx(nn,{size:18})})]})]})]}),t.jsxs("div",{className:s.canvasWorkspace,children:[t.jsx("div",{className:s.canvasToolRail,"aria-label":"Annotation tools",children:Mn.map(e=>{const n=e.icon;return t.jsx("button",{type:"button",className:`tf-button-ghost ${s.toolRailButton} ${$===e.value?s.toolBtnActive:""}`,onClick:()=>es(e.value),disabled:!d,title:e.label,"aria-label":`${e.label} tool. ${It[e.value].short}`,children:t.jsx(n,{size:18})},e.value)})}),t.jsx("div",{className:s.canvasMain,children:t.jsx("div",{ref:Te,className:`tf-scrollbar ${s.canvasScroller}`,children:d?t.jsxs(t.Fragment,{children:[ce&&!Lt?t.jsx("div",{className:s.canvasStatusOverlay,"aria-live":"polite",children:t.jsxs("div",{className:s.canvasStatusCard,children:[t.jsx(An,{size:20,className:Bn.spinner}),t.jsx("span",{children:"Loading image…"})]})}):null,Lt?t.jsx("div",{className:s.canvasStatusOverlay,"aria-live":"polite",children:t.jsx("div",{className:s.canvasStatusCard,children:t.jsx("span",{children:"Image failed to load."})})}):null,t.jsx("div",{className:s.canvasFrame,children:t.jsxs("div",{className:s.canvasMedia,style:{width:b.width?`${b.width}px`:void 0,height:b.height?`${b.height}px`:void 0},children:[t.jsx("img",{ref:Ht,src:d.path,alt:d.displayName,className:s.canvasImage,onLoad:()=>{const e=Ht.current;et({width:e?.naturalWidth||0,height:e?.naturalHeight||0}),Je(!1),Ze(!1)},onError:()=>{et({width:0,height:0}),Je(!1),Ze(!0)}}),!ce&&!Lt&&B?t.jsxs("div",{ref:mn,className:`${s.overlay} ${$==="select"?s.overlaySelect:""} ${G&&ke?s.overlayPan:""}`,onPointerDown:ds,onPointerMove:fs,onPointerUp:e=>{us(e),hs()},"data-testid":"annotated-attachment-overlay",onPointerLeave:ms,onPointerCancel:ps,children:[t.jsx("svg",{className:s.overlaySvg,viewBox:Gt,preserveAspectRatio:"none","aria-hidden":"true",children:N.filter(e=>e.kind==="arrow").map(e=>{const n=Ko(e,b),r=en(e),i=C===e.id;return t.jsxs(qt.Fragment,{children:[i?t.jsxs(t.Fragment,{children:[t.jsx("line",{x1:n.shaftX1,y1:n.shaftY1,x2:n.shaftX2,y2:n.shaftY2,stroke:"color-mix(in srgb, var(--surface-elevated) 92%, transparent)",strokeWidth:14,strokeLinecap:"round"}),t.jsx("polygon",{points:n.headPoints,fill:r,stroke:"color-mix(in srgb, var(--surface-elevated) 92%, transparent)",strokeWidth:4,strokeLinejoin:"round"})]}):null,t.jsx("line",{x1:n.shaftX1,y1:n.shaftY1,x2:n.shaftX2,y2:n.shaftY2,stroke:r,strokeWidth:i?10:6,strokeLinecap:"round"}),t.jsx("polygon",{points:n.headPoints,fill:r})]},e.id)})}),t.jsx("svg",{className:s.overlayHitLayer,viewBox:Gt,preserveAspectRatio:"none",children:N.filter(e=>e.kind==="arrow").map(e=>t.jsxs(qt.Fragment,{children:[t.jsx("line",{x1:e.x*b.width,y1:e.y*b.height,x2:e.x2*b.width,y2:e.y2*b.height,className:s.arrowHitArea,"data-testid":`annotated-arrow-hit-${e.id}`,"aria-label":`Arrow marker ${Kt.get(e.id)||0}`,onPointerDown:n=>Nn(n,e),onClick:n=>{n.stopPropagation(),L(e.id)}}),C===e.id?t.jsxs(t.Fragment,{children:[t.jsx("circle",{cx:e.x*b.width,cy:e.y*b.height,r:He.hitRadius,className:s.canvasHandleHit,role:"button",tabIndex:0,"aria-label":"Move arrow tail",onPointerDown:n=>$n(n,e,"tail")}),t.jsx("circle",{cx:e.x*b.width,cy:e.y*b.height,r:He.visibleRadius,className:s.canvasHandleVisible,"aria-hidden":"true"}),t.jsx("circle",{cx:e.x2*b.width,cy:e.y2*b.height,r:He.hitRadius,className:s.canvasHandleHit,role:"button",tabIndex:0,"aria-label":"Move arrow tip",onPointerDown:n=>$n(n,e,"tip")}),t.jsx("circle",{cx:e.x2*b.width,cy:e.y2*b.height,r:He.visibleRadius,className:s.canvasHandleVisible,"aria-hidden":"true"})]}):null]},`hit-${e.id}`))}),N.filter(e=>e.kind==="arrow").map(e=>t.jsx("div",{className:`${s.annotationNumberBadge} ${s.arrowNumberBadge}`,style:{left:`${(e.x+e.x2)/2*100}%`,top:`${(e.y+e.y2)/2*100}%`,backgroundColor:en(e)},"aria-hidden":"true",children:Kt.get(e.id)||0},`arrow-number-${e.id}`)),N.filter(e=>e.kind!=="arrow").map(e=>{const n=en(e),r=Kt.get(e.id)||0,i={onPointerDown:c=>Nn(c,e),onClick:c=>{c.stopPropagation(),L(e.id)}};return e.kind==="pin"?t.jsx("button",{type:"button",...i,className:`${s.pin} ${C===e.id?s.selected:""}`,style:{left:`${e.x*100}%`,top:`${e.y*100}%`,backgroundColor:n},children:r},e.id):e.kind==="text-note"?t.jsx("button",{type:"button",...i,className:`${s.note} ${C===e.id?s.selected:""}`,style:{left:`${e.x*100}%`,top:`${e.y*100}%`,backgroundColor:n},children:r},e.id):t.jsxs("div",{className:`${s.box} ${C===e.id?s.selected:""}`,style:{left:`${e.x*100}%`,top:`${e.y*100}%`,width:`${e.width*100}%`,height:`${e.height*100}%`,borderColor:n},children:[t.jsx("span",{className:`${s.annotationNumberBadge} ${s.boxNumberBadge}`,style:{backgroundColor:n},"aria-hidden":"true",children:r}),t.jsx("button",{type:"button",...i,className:s.boxSurface,"aria-label":`Box marker ${r}`})]},e.id)}),t.jsx("svg",{className:s.overlaySvg,viewBox:Gt,preserveAspectRatio:"none",children:N.filter(e=>e.kind==="box"&&C===e.id).map(e=>t.jsxs(qt.Fragment,{children:[t.jsx("circle",{cx:(e.x+e.width)*b.width,cy:(e.y+e.height)*b.height,r:He.hitRadius,className:`${s.canvasHandleHit} ${s.canvasResizeHandleHit}`,role:"button",tabIndex:0,"aria-label":"Resize box marker",onPointerDown:n=>bs(n,e)}),t.jsx("circle",{cx:(e.x+e.width)*b.width,cy:(e.y+e.height)*b.height,r:He.visibleRadius,className:s.canvasHandleVisible,"aria-hidden":"true"})]},`box-handle-${e.id}`))})]}):null]})})]}):t.jsx("div",{className:s.emptyState,children:t.jsx("p",{className:"tf-empty-copy",children:"Select an image attachment to annotate."})})})})]}),t.jsxs("div",{className:s.statusBar,children:[t.jsx("span",{children:U==="error"?"Save failed. Retry now.":U==="saving"||U==="pending"?"Saving…":"Saved"}),rn?t.jsx("span",{children:rn}):t.jsx("span",{children:B?G&&ke?"Drag on the image to pan.":$==="select"?"Select a marker to edit it.":`Click on the image to place a ${$}.`:"Create a session to begin placing markers on this image."}),d&&!B&&Kn?t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn} ${s.toolbarButton}`,onClick:()=>{wn()},disabled:W||ut,children:W?"Creating…":"Create one now"}):null]})]}),t.jsx("section",{className:`tf-surface-panel ${s.panel} ${s.detailPanel}`,children:B?t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:s.panelHeader,children:[t.jsxs("div",{className:s.panelHeaderText,children:[t.jsx("div",{className:`tf-heading-card ${s.panelTitle}`,children:"Markers"}),t.jsxs("div",{className:"tf-text-secondary",children:[N.length," in this session"]})]}),t.jsxs("div",{className:s.sessionActions,children:[t.jsx("button",{type:"button",className:`tf-control-icon ${s.iconButton}`,onClick:()=>Sn("up"),disabled:!z||zt<=0,"aria-label":"Move marker up",children:t.jsx(Ps,{size:16})}),t.jsx("button",{type:"button",className:`tf-control-icon ${s.iconButton}`,onClick:()=>Sn("down"),disabled:!z||zt===-1||zt>=N.length-1,"aria-label":"Move marker down",children:t.jsx(Bs,{size:16})})]})]}),t.jsx("div",{className:`tf-scrollbar ${s.annotationList}`,children:N.length===0?t.jsx("div",{className:s.detailEmpty,children:t.jsx("p",{className:"tf-empty-copy",children:"Add a marker on the image to begin."})}):N.map((e,n)=>{const r=Oo[e.kind],i=Do[e.kind],c=`${i} ${n+1}`,p=zo[e.markerType||re],j=En.find(f=>f.value===(e.markerType||re))?.label||"Review",u=C===e.id;return t.jsxs("div",{className:`tf-surface-elevated ${s.annotationCard} ${u?s.annotationCardActive:""}`,children:[t.jsx("button",{type:"button",className:s.annotationCardButton,onClick:()=>L(e.id),"aria-label":i,children:t.jsxs("div",{className:s.annotationMeta,children:[t.jsx("span",{className:`tf-heading-card ${s.annotationTitle}`,children:c}),t.jsx("span",{className:`tf-text-meta ${s.annotationKind}`,"aria-hidden":"true",children:t.jsx(r,{size:16})})]})}),u?t.jsxs("div",{className:s.annotationInstructionEditor,children:[t.jsx("label",{className:"tf-field-label",htmlFor:"annotated-marker-instruction",children:"Marker instruction"}),t.jsx("textarea",{id:"annotated-marker-instruction",ref:u?pn:null,className:`tf-field-shell ${s.textArea} ${s.annotationInstructionField}`,value:e.instruction,onChange:f=>{ee(e.id,v=>({...v,instruction:f.target.value}),600)},onBlur:()=>{H("text")},placeholder:"What should the AI focus on for this marker?"}),t.jsxs("div",{className:s.annotationTypeField,children:[t.jsx("label",{className:"tf-field-label",htmlFor:"annotated-marker-type",children:"Instruction type"}),t.jsx("select",{id:"annotated-marker-type",className:`tf-field-shell ${s.select}`,value:e.markerType||re,onChange:f=>{ee(e.id,v=>Wo(v,f.target.value))},children:En.map(f=>t.jsx("option",{value:f.value,children:f.label},f.value))})]})]}):t.jsx("button",{type:"button",className:s.annotationInstructionButton,onClick:()=>L(e.id),"aria-label":`Edit ${i} instruction`,children:t.jsxs("div",{className:s.annotationInstructionEditor,children:[t.jsx("div",{className:`tf-text-secondary ${s.annotationInstructionPreview}`,children:e.instruction.trim()||"No marker instruction yet."}),t.jsx("div",{className:s.annotationPreviewFooter,children:t.jsx("span",{className:`tf-text-meta ${s.annotationInstructionTypeIcon}`,"aria-label":`Instruction type: ${j}`,title:j,children:t.jsx(p,{size:16})})})]})})]},e.id)})})]}):t.jsxs("div",{className:s.emptyState,children:[t.jsx("strong",{className:"tf-empty-title",children:"No active session"}),t.jsx("p",{className:"tf-empty-copy",children:"Create or select a session to edit annotations."})]})}),t.jsx(Jt,{isOpen:Vn,onClose:()=>{qe||(ht(!1),bt(""))},title:"Open Image",size:"sm",children:t.jsxs("form",{className:s.openImageModalBody,onSubmit:e=>{e.preventDefault(),Zn()},children:[t.jsx("label",{className:"tf-field-label",htmlFor:"annotated-open-image-reference",children:"Image reference number"}),t.jsx("input",{id:"annotated-open-image-reference",className:`tf-field-shell ${s.textInput} ${s.openImageField}`,value:Mt,onChange:e=>bt(e.target.value),placeholder:"I-24",autoFocus:!0}),t.jsxs("div",{className:s.openImageActions,children:[t.jsx("button",{type:"button",className:"tf-button-ghost tf-button-compact",onClick:()=>{ht(!1),bt("")},disabled:qe,children:"Cancel"}),t.jsx("button",{type:"submit",className:"tf-button-primary tf-button-compact",disabled:qe,children:qe?"Opening…":"Open"})]})]})}),t.jsx(Jt,{isOpen:Xn,onClose:()=>dn(!1),title:"How To Use Markers",size:"md",children:t.jsx("div",{className:s.markerHelpModalBody,children:Mn.filter(e=>e.value!=="select").map(e=>t.jsxs("div",{className:`tf-surface-inset ${s.markerHelpItem}`,children:[t.jsxs("div",{className:s.markerHelpHeader,children:[t.jsx("strong",{className:"tf-heading-card",children:e.label}),t.jsx("span",{className:"tf-text-meta",children:It[e.value].short})]}),t.jsx("p",{className:"tf-text-secondary",children:It[e.value].detail}),t.jsx("p",{className:`tf-text-body ${s.markerHelpExample}`,children:It[e.value].example})]},e.value))})}),t.jsx(Jt,{isOpen:Wn,onClose:()=>Bt(!1),title:"AI Payload",size:"lg",children:t.jsxs("div",{className:s.payloadModalBody,children:[t.jsxs("div",{className:s.payloadModalToolbar,children:[t.jsxs("div",{className:s.payloadViewToggle,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.toolBtn} ${Tt==="json"?s.toolBtnActive:""}`,onClick:()=>cn("json"),children:"JSON"}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.toolBtn} ${Tt==="brief"?s.toolBtnActive:""}`,onClick:()=>cn("brief"),children:"AI Brief"})]}),t.jsxs("div",{className:s.payloadModalActions,children:[t.jsxs("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn}`,onClick:()=>{In("json")},disabled:!oe,"aria-label":le==="json"?"Copied JSON":"Copy JSON",title:le==="json"?"Copied JSON":"Copy JSON",children:[le==="json"?t.jsx(sn,{size:16}):t.jsx(Zt,{size:16}),t.jsx("span",{children:"JSON"})]}),t.jsxs("button",{type:"button",className:`tf-button-ghost tf-button-compact ${s.ghostBtn}`,onClick:()=>{In("brief")},disabled:!oe,"aria-label":le==="brief"?"Copied AI Brief":"Copy AI Brief",title:le==="brief"?"Copied AI Brief":"Copy AI Brief",children:[le==="brief"?t.jsx(sn,{size:16}):t.jsx(Zt,{size:16}),t.jsx("span",{children:"AI Brief"})]})]})]}),ft?t.jsx("div",{className:s.detailEmpty,children:t.jsx("p",{className:"tf-empty-copy",children:"Loading payload…"})}):oe?t.jsx("pre",{className:`tf-surface-inset tf-scrollbar ${s.payloadModalPreview}`,children:Tt==="json"?JSON.stringify(oe,null,2):Fn(oe)}):t.jsx("div",{className:s.detailEmpty,children:t.jsx("p",{className:"tf-empty-copy",children:"Load a payload preview to inspect the current session contract."})})]})})]})}export{dr as AnnotatedAttachmentWorkspaceShell};
|