@taskforcehq/taskforce 0.3.306 → 0.3.308
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/README.md +0 -2
- package/dist/Taskforce.module.css +317 -27
- package/dist/TaskforceCore.js +1 -0
- package/dist/TaskforceCore.test.js +178 -0
- package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
- package/dist/components/activity/EntityActivityTimeline.js +81 -0
- package/dist/components/context/ContextAttachmentManager.js +15 -3
- package/dist/components/features/AgentsModule.d.ts +5 -1
- package/dist/components/features/AgentsModule.js +142 -39
- package/dist/components/features/AgentsModule.test.js +260 -11
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
- package/dist/components/features/DocumentIndex.d.ts +2 -2
- package/dist/components/features/DocumentIndex.js +3 -4
- package/dist/components/features/DocumentViewer.d.ts +3 -1
- package/dist/components/features/DocumentViewer.js +6 -5
- package/dist/components/features/DocumentWorkspace.d.ts +10 -2
- package/dist/components/features/DocumentWorkspace.js +15 -4
- package/dist/components/features/DocumentWorkspace.test.js +20 -3
- package/dist/components/features/TaskSettings.d.ts +2 -0
- package/dist/components/features/TaskSettings.js +83 -78
- package/dist/components/features/TaskSettings.test.js +171 -82
- package/dist/components/task/TaskActionHeader.js +11 -5
- package/dist/components/task/TaskActionHeader.test.js +10 -0
- package/dist/components/task/TaskCard.js +6 -2
- package/dist/components/task/TaskContextUpload.test.js +3 -3
- package/dist/components/task/TaskForm.js +14 -64
- package/dist/components/task/TaskKanban.js +0 -8
- package/dist/components/task/TaskList.js +5 -1
- package/dist/components/task/TaskList.test.js +4 -0
- package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
- package/dist/components/ui/HierarchyActionBadge.js +5 -0
- package/dist/components/views/AppShellHeader.d.ts +3 -1
- package/dist/components/views/AppShellHeader.js +27 -11
- package/dist/components/views/AppShellHeader.test.js +18 -1
- package/dist/components/views/PlanComparisonPage.d.ts +20 -15
- package/dist/components/views/PlanComparisonPage.js +221 -82
- package/dist/components/views/PlanComparisonPage.test.js +268 -14
- package/dist/components/views/PlansPage.js +16 -10
- package/dist/components/views/PlansPage.test.js +4 -2
- package/dist/components/views/StandaloneLayout.js +256 -69
- package/dist/components/views/WidgetView.js +10 -8
- package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
- package/dist/components/views/panels/PlanningDrawer.js +259 -51
- package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
- package/dist/config/envSchema.js +1 -0
- package/dist/core/AiProfileService.d.ts +7 -0
- package/dist/core/AiProfileService.js +460 -35
- package/dist/core/AiProfiles.test.js +648 -2
- package/dist/core/AttachmentLinkService.d.ts +3 -0
- package/dist/core/AttachmentLinkService.js +58 -13
- package/dist/core/GlobalSettingsService.js +30 -0
- package/dist/core/GlobalSettingsService.test.d.ts +1 -0
- package/dist/core/GlobalSettingsService.test.js +45 -0
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanEntitlementService.d.ts +20 -0
- package/dist/core/PlanEntitlementService.js +215 -19
- package/dist/core/PlanFeatureCatalog.test.js +55 -0
- package/dist/core/PlanningEntities.test.js +366 -4
- package/dist/core/TaskActivityService.d.ts +28 -1
- package/dist/core/TaskActivityService.js +227 -46
- package/dist/core/TaskActivitySync.test.d.ts +1 -0
- package/dist/core/TaskActivitySync.test.js +90 -0
- package/dist/core/TaskAuditTimeline.test.js +96 -0
- package/dist/core/Taskforce.d.ts +204 -26
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +781 -71
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
- package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
- package/dist/hooks/bootstrapTimeouts.js +8 -0
- package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
- package/dist/hooks/bootstrapTimeouts.test.js +12 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
- package/dist/hooks/sync/orchestratorShared.js +61 -1
- package/dist/hooks/sync/recovery.js +8 -0
- package/dist/hooks/sync/recovery.test.js +5 -5
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +36 -9
- package/dist/hooks/sync/transfers.test.js +534 -2
- package/dist/hooks/useFilterSort.js +13 -13
- package/dist/hooks/useFilterSort.test.js +87 -1
- package/dist/hooks/useSyncOrchestrator.js +50 -5
- package/dist/hooks/useTaskforce.d.ts +85 -50
- package/dist/hooks/useTaskforce.js +44 -14
- package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
- package/dist/hooks/useWorkspaceSyncController.js +7 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
- package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
- package/dist/mcp/canonicalAssetHelpers.js +18 -10
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
- package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
- package/dist/mcp/clientIntegrations.d.ts +1 -0
- package/dist/mcp/clientIntegrations.js +25 -0
- package/dist/mcp/clientIntegrations.test.js +14 -3
- package/dist/mcp/clientProfileDefaults.js +3 -3
- package/dist/mcp/collaborationRegistrar.js +7 -7
- package/dist/mcp/documentAssetRegistrar.js +113 -7
- package/dist/mcp/runtime.d.ts +1 -0
- package/dist/mcp/runtime.js +969 -122
- package/dist/mcp/runtime.test.js +1083 -11
- package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
- package/dist/mcp/taskPlanningRegistrar.js +48 -0
- package/dist/mcp/toolRegistry.d.ts +56 -0
- package/dist/mcp/toolRegistry.js +8 -0
- package/dist/mcp/toolRegistry.test.js +4 -0
- package/dist/migrations/taskSchemaMigrations.js +58 -15
- package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
- package/dist/server/aiProfileAvatarAssets.js +319 -0
- package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
- package/dist/server/aiProfileAvatarAssets.test.js +213 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
- package/dist/server/index.js +4 -0
- package/dist/server/index.test.js +6 -1
- package/dist/server/routes/admin.js +328 -9
- package/dist/server/routes/annotatedAttachments.js +51 -0
- package/dist/server/routes/auth.d.ts +11 -0
- package/dist/server/routes/auth.js +189 -27
- package/dist/server/routes/authSupport.d.ts +5 -0
- package/dist/server/routes/authSupport.js +13 -1
- package/dist/server/routes/billing.d.ts +1 -0
- package/dist/server/routes/billing.js +116 -63
- package/dist/server/routes/billing.test.js +162 -11
- package/dist/server/routes/documents.js +10 -0
- package/dist/server/routes/shared.js +0 -10
- package/dist/server/routes/sync.integration.test.js +221 -0
- package/dist/server/routes/sync.js +13 -3
- package/dist/server/routes/syncSnapshotRoutes.js +2 -0
- package/dist/server/routes/tasks.js +12 -9
- package/dist/server/routes.js +87 -8
- package/dist/server/routes.test.js +972 -23
- package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
- package/dist/shared/aiProfileSurfaceType.js +10 -8
- package/dist/storage/objectStorage.d.ts +1 -0
- package/dist/storage/objectStorage.js +2 -1
- package/dist/storage/objectStorageClient.d.ts +1 -0
- package/dist/storage/objectStorageClient.js +41 -8
- package/dist/storage/objectStorageClient.test.js +26 -1
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.js +13 -1
- package/dist/sync/collaborationSyncPayload.js +2 -0
- package/dist/sync/collaborationSyncPayload.test.js +1 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -3
- package/dist/sync/collaborationSyncState.js +19 -12
- package/dist/sync/contentSyncPayload.d.ts +5 -1
- package/dist/sync/contentSyncPayload.js +9 -0
- package/dist/sync/contentSyncPayload.test.js +39 -0
- package/dist/sync/contentSyncState.d.ts +5 -1
- package/dist/sync/contentSyncState.js +231 -37
- package/dist/sync/planningSyncPayload.js +5 -0
- package/dist/sync/planningSyncPayload.test.js +24 -0
- package/dist/sync/syncApplyHandlers.d.ts +4 -4
- package/dist/sync/syncApplyHandlers.js +89 -31
- package/dist/sync/syncApplyHandlers.test.js +184 -4
- package/dist/sync/syncResourceAdapters.d.ts +2 -0
- package/dist/sync/syncResourceAdapters.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +13 -1
- package/dist/sync/syncService.test.js +39 -0
- package/dist/sync/workspacePullFeed.d.ts +4 -0
- package/dist/sync/workspacePullFeed.js +129 -70
- package/dist/sync/workspacePullFeed.test.js +49 -0
- package/dist/sync/workspaceSyncModel.d.ts +28 -6
- package/dist/sync/workspaceSyncModel.js +159 -45
- package/dist/sync/workspaceSyncModel.test.js +269 -3
- package/dist/types.d.ts +33 -2
- package/dist/ui/agent-logos/ChatGPT.png +0 -0
- package/dist/ui/agent-logos/Gemini CLI.jpeg +0 -0
- package/dist/ui/agent-logos/antigravity.jpeg +0 -0
- package/dist/ui/agent-logos/claude-code.jpeg +0 -0
- package/dist/ui/agent-logos/codex.jpeg +0 -0
- package/dist/ui/agent-logos/cursor.png +0 -0
- package/dist/ui/agent-logos/openclaw.jpeg +0 -0
- package/dist/ui/agent-logos/windsurf.png +0 -0
- package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-CCckzWYZ.js +3 -0
- package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-BVaWPA25.js +252 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-DSj0ThaK.js +1 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-AxMkWtxH.js} +1 -1
- package/dist/ui/assets/PlansPage-B_z-D6Nh.css +1 -0
- package/dist/ui/assets/PlansPage-Ndt7mYfo.js +1 -0
- package/dist/ui/assets/TaskContextUpload-Dw4rTF4i.js +1 -0
- package/dist/ui/assets/TaskSettings-f2ga42_V.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-E4UL3mov.js} +4 -4
- package/dist/ui/assets/documentReferences-BOUcJm6-.js +1 -0
- package/dist/ui/assets/index-BvAbqx5Q.css +1 -0
- package/dist/ui/assets/index-MAHKvFqp.js +4 -0
- package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/ui/og-image.png +0 -0
- package/dist/ui/product/agent manager.png +0 -0
- package/dist/ui/product/agent_manager_02.png +0 -0
- package/dist/utils/aiProfileEvents.d.ts +7 -0
- package/dist/utils/aiProfileEvents.js +6 -0
- package/dist/utils/assignees.d.ts +6 -0
- package/dist/utils/assignees.js +8 -1
- package/dist/utils/assignees.test.d.ts +1 -0
- package/dist/utils/assignees.test.js +25 -0
- package/dist/utils/avatarDisplay.d.ts +1 -0
- package/dist/utils/avatarDisplay.js +15 -0
- package/dist/utils/contextFiles.d.ts +3 -1
- package/dist/utils/contextFiles.js +2 -1
- package/dist/utils/taskActivity.d.ts +7 -1
- package/dist/utils/taskActivity.js +63 -1
- package/dist/utils/taskActivity.test.js +23 -1
- package/dist/utils/taskSearch.d.ts +22 -0
- package/dist/utils/taskSearch.js +87 -0
- package/dist/utils/taskSearch.test.d.ts +1 -0
- package/dist/utils/taskSearch.test.js +69 -0
- package/dist/utils/taskUtils.js +10 -3
- package/dist/utils/taskUtils.test.d.ts +1 -0
- package/dist/utils/taskUtils.test.js +19 -0
- package/package.json +1 -1
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
- package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
- package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
- package/dist/ui/assets/index-C6k75jr8.js +0 -6
package/dist/ui/index.html
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
|
|
9
9
|
<link rel="apple-touch-icon" href="/taskforce/favicon/apple-touch-icon.png" />
|
|
10
10
|
<title>Taskforce</title>
|
|
11
|
-
<script type="module" crossorigin src="/taskforce/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/taskforce/assets/index-MAHKvFqp.js"></script>
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-react-CKJs5o3c.js">
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-icons-
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-icons-B32hGuF2.js">
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-markdown-BUxTU7dS.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-dnd-DRzYolkg.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-router-BbWMxlnO.js">
|
|
17
|
-
<link rel="stylesheet" crossorigin href="/taskforce/assets/index-
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/taskforce/assets/index-BvAbqx5Q.css">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
20
|
<div id="root"></div>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const TASKFORCE_AI_PROFILES_MUTATED_EVENT = "taskforce:ai-profiles-mutated";
|
|
2
|
+
export interface TaskforceAiProfilesMutatedDetail {
|
|
3
|
+
workspaceId?: string | null;
|
|
4
|
+
profileId?: string | null;
|
|
5
|
+
reason?: 'avatar' | 'merge' | 'archive' | 'update';
|
|
6
|
+
}
|
|
7
|
+
export declare function dispatchTaskforceAiProfilesMutated(detail: TaskforceAiProfilesMutatedDetail): void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const TASKFORCE_AI_PROFILES_MUTATED_EVENT = 'taskforce:ai-profiles-mutated';
|
|
2
|
+
export function dispatchTaskforceAiProfilesMutated(detail) {
|
|
3
|
+
if (typeof window === 'undefined' || typeof window.dispatchEvent !== 'function')
|
|
4
|
+
return;
|
|
5
|
+
window.dispatchEvent(new CustomEvent(TASKFORCE_AI_PROFILES_MUTATED_EVENT, { detail }));
|
|
6
|
+
}
|
|
@@ -15,6 +15,12 @@ export interface TaskAssigneeOption {
|
|
|
15
15
|
color: string;
|
|
16
16
|
kind: AssigneeKind;
|
|
17
17
|
avatarUrl?: string | null;
|
|
18
|
+
avatarRevision?: number | null;
|
|
19
|
+
avatarUpdatedAt?: string | null;
|
|
20
|
+
username?: string | null;
|
|
21
|
+
archivedAt?: string | null;
|
|
22
|
+
createdAt?: string | null;
|
|
23
|
+
updatedAt?: string | null;
|
|
18
24
|
}
|
|
19
25
|
export declare function isAgentAssignee(value: unknown): boolean;
|
|
20
26
|
export declare function isUnassignedAssignee(value: unknown): boolean;
|
package/dist/utils/assignees.js
CHANGED
|
@@ -61,7 +61,8 @@ export function mergeAssigneeOptions(options) {
|
|
|
61
61
|
}
|
|
62
62
|
for (const option of Array.isArray(options) ? options : []) {
|
|
63
63
|
const value = String(option?.value || '').trim();
|
|
64
|
-
|
|
64
|
+
const archivedAt = typeof option?.archivedAt === 'string' ? option.archivedAt.trim() : '';
|
|
65
|
+
if (!value || archivedAt || value === TASK_ASSIGNEE_UNASSIGNED || value === TASK_ASSIGNEE_AGENT)
|
|
65
66
|
continue;
|
|
66
67
|
deduped.set(value, {
|
|
67
68
|
value,
|
|
@@ -70,6 +71,12 @@ export function mergeAssigneeOptions(options) {
|
|
|
70
71
|
color: String(option.color || (option.kind === 'agent' ? '#8b5cf6' : '#22c55e')),
|
|
71
72
|
kind: option.kind === 'agent' ? 'agent' : 'member',
|
|
72
73
|
avatarUrl: typeof option.avatarUrl === 'string' && option.avatarUrl.trim().length > 0 ? option.avatarUrl.trim() : null,
|
|
74
|
+
avatarRevision: Number.isFinite(Number(option.avatarRevision)) ? Math.max(0, Math.floor(Number(option.avatarRevision))) : null,
|
|
75
|
+
avatarUpdatedAt: typeof option.avatarUpdatedAt === 'string' && option.avatarUpdatedAt.trim().length > 0 ? option.avatarUpdatedAt.trim() : null,
|
|
76
|
+
username: typeof option.username === 'string' && option.username.trim().length > 0 ? option.username.trim() : null,
|
|
77
|
+
archivedAt: null,
|
|
78
|
+
createdAt: typeof option.createdAt === 'string' && option.createdAt.trim().length > 0 ? option.createdAt.trim() : null,
|
|
79
|
+
updatedAt: typeof option.updatedAt === 'string' && option.updatedAt.trim().length > 0 ? option.updatedAt.trim() : null,
|
|
73
80
|
});
|
|
74
81
|
}
|
|
75
82
|
const defaults = [TASK_ASSIGNEE_UNASSIGNED];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { mergeAssigneeOptions } from './assignees';
|
|
3
|
+
describe('mergeAssigneeOptions', () => {
|
|
4
|
+
it('keeps one unassigned option and filters archived AI profile options', () => {
|
|
5
|
+
expect(mergeAssigneeOptions([
|
|
6
|
+
{ value: 'unassigned', label: 'Unassigned', icon: 'HelpCircle', color: '#999', kind: 'unassigned' },
|
|
7
|
+
{ value: 'ai-profile-retired', label: 'Retired Agent', icon: 'Bot', color: '#8b5cf6', kind: 'agent', archivedAt: '2026-04-24T15:28:13.577Z' },
|
|
8
|
+
{ value: 'ai-profile-codex', label: 'Codex', icon: 'Bot', color: '#8b5cf6', kind: 'agent' },
|
|
9
|
+
])).toEqual([
|
|
10
|
+
{ value: 'unassigned', label: 'Unassigned', icon: 'HelpCircle', color: 'var(--text-secondary)', kind: 'unassigned' },
|
|
11
|
+
{
|
|
12
|
+
value: 'ai-profile-codex',
|
|
13
|
+
label: 'Codex',
|
|
14
|
+
icon: 'Bot',
|
|
15
|
+
color: '#8b5cf6',
|
|
16
|
+
kind: 'agent',
|
|
17
|
+
avatarUrl: null,
|
|
18
|
+
username: null,
|
|
19
|
+
archivedAt: null,
|
|
20
|
+
createdAt: null,
|
|
21
|
+
updatedAt: null,
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildVersionedAvatarUrl(url: unknown, revision?: unknown, updatedAt?: unknown): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function buildVersionedAvatarUrl(url, revision, updatedAt) {
|
|
2
|
+
const normalizedUrl = typeof url === 'string' ? url.trim() : '';
|
|
3
|
+
if (!normalizedUrl || normalizedUrl.startsWith('data:') || normalizedUrl.startsWith('blob:')) {
|
|
4
|
+
return normalizedUrl;
|
|
5
|
+
}
|
|
6
|
+
const normalizedRevision = Number(revision);
|
|
7
|
+
const version = Number.isFinite(normalizedRevision) && normalizedRevision > 0
|
|
8
|
+
? String(Math.floor(normalizedRevision))
|
|
9
|
+
: (typeof updatedAt === 'string' && updatedAt.trim().length > 0 ? updatedAt.trim() : '');
|
|
10
|
+
if (!version)
|
|
11
|
+
return normalizedUrl;
|
|
12
|
+
const [base, hash = ''] = normalizedUrl.split('#');
|
|
13
|
+
const separator = base.includes('?') ? '&' : '?';
|
|
14
|
+
return `${base}${separator}v=${encodeURIComponent(version)}${hash ? `#${hash}` : ''}`;
|
|
15
|
+
}
|
|
@@ -2,11 +2,13 @@ export interface OpenMarkdownEditorDetail {
|
|
|
2
2
|
path: string;
|
|
3
3
|
fsPath: string;
|
|
4
4
|
assetId?: string;
|
|
5
|
+
taskId?: string | null;
|
|
5
6
|
}
|
|
6
7
|
type MarkdownOpenTarget = string | {
|
|
7
8
|
path: string;
|
|
8
9
|
fsPath?: string;
|
|
9
|
-
assetId?: string;
|
|
10
|
+
assetId?: string | null;
|
|
11
|
+
taskId?: string | null;
|
|
10
12
|
caption?: string;
|
|
11
13
|
displayName?: string;
|
|
12
14
|
originalFilename?: string;
|
|
@@ -77,7 +77,8 @@ export function dispatchOpenMarkdownDocument(target, fsPath) {
|
|
|
77
77
|
detail: {
|
|
78
78
|
path: sourcePath,
|
|
79
79
|
fsPath: resolvedFsPath,
|
|
80
|
-
...(assetId ? { assetId } : {})
|
|
80
|
+
...(assetId ? { assetId } : {}),
|
|
81
|
+
...((typeof target !== 'string' && target.taskId) ? { taskId: target.taskId } : {})
|
|
81
82
|
},
|
|
82
83
|
cancelable: true
|
|
83
84
|
});
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import type { TaskActivityItem, TaskEvent, TaskItem } from '../types';
|
|
1
|
+
import type { EntityActivityItem, EntityEvent, TaskActivityItem, TaskEvent, TaskItem } from '../types';
|
|
2
2
|
export type TaskEventValueFormatter = (field: string, value: unknown) => string | undefined;
|
|
3
3
|
export declare function summarizeTaskChange(field: string, change: {
|
|
4
4
|
from?: unknown;
|
|
5
5
|
to?: unknown;
|
|
6
6
|
} | undefined, formatter?: TaskEventValueFormatter): string;
|
|
7
7
|
export declare function summarizeTaskEvent(event: TaskEvent, formatter?: TaskEventValueFormatter): string;
|
|
8
|
+
export declare function summarizeEntityEvent(event: EntityEvent, formatter?: TaskEventValueFormatter): string;
|
|
8
9
|
export declare function getTaskEventPrimaryChangeField(event: TaskEvent): string | null;
|
|
10
|
+
export declare function getEntityEventPrimaryChangeField(event: EntityEvent): string | null;
|
|
11
|
+
export declare function getEntityActivityItems(task: {
|
|
12
|
+
activity?: EntityActivityItem[] | TaskActivityItem[];
|
|
13
|
+
comments?: TaskItem['comments'];
|
|
14
|
+
}): EntityActivityItem[];
|
|
9
15
|
export declare function getTaskActivityItems(task: Pick<TaskItem, 'activity' | 'comments'>): TaskActivityItem[];
|
|
10
16
|
export declare function getLatestTaskActivity(task: Pick<TaskItem, 'activity' | 'comments'>): TaskActivityItem | null;
|
|
11
17
|
export declare function summarizeTaskActivity(item: TaskActivityItem | null, formatter?: TaskEventValueFormatter): string;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
function getActivityFieldLabel(field) {
|
|
2
|
+
if (field === 'taskId')
|
|
3
|
+
return 'Task';
|
|
2
4
|
if (field === 'title')
|
|
3
5
|
return 'Title';
|
|
4
6
|
if (field === 'description')
|
|
5
7
|
return 'Description';
|
|
6
8
|
if (field === 'assignee')
|
|
7
9
|
return 'Assigned to';
|
|
10
|
+
if (field === 'ownerId')
|
|
11
|
+
return 'Owner';
|
|
8
12
|
if (field === 'status')
|
|
9
13
|
return 'Status';
|
|
10
14
|
if (field === 'scheduledDate')
|
|
@@ -277,6 +281,47 @@ export function summarizeTaskEvent(event, formatter) {
|
|
|
277
281
|
return `${fields.length} fields updated`;
|
|
278
282
|
return 'Task updated';
|
|
279
283
|
}
|
|
284
|
+
export function summarizeEntityEvent(event, formatter) {
|
|
285
|
+
if (event.taskId || event.entityType === 'task') {
|
|
286
|
+
return summarizeTaskEvent(event, formatter);
|
|
287
|
+
}
|
|
288
|
+
const changes = event.details?.changes || {};
|
|
289
|
+
const fields = Object.keys(changes);
|
|
290
|
+
const entityLabel = event.entityType === 'initiative' ? 'Initiative' : 'Workstream';
|
|
291
|
+
const taskTitle = typeof event.details?.taskTitle === 'string' && event.details.taskTitle.trim().length > 0
|
|
292
|
+
? event.details.taskTitle.trim()
|
|
293
|
+
: null;
|
|
294
|
+
if (event.action === 'initiative-created')
|
|
295
|
+
return 'Initiative created';
|
|
296
|
+
if (event.action === 'initiative-archived')
|
|
297
|
+
return 'Initiative archived';
|
|
298
|
+
if (event.action === 'initiative-unarchived')
|
|
299
|
+
return 'Initiative restored';
|
|
300
|
+
if (event.action === 'workstream-created')
|
|
301
|
+
return 'Workstream created';
|
|
302
|
+
if (event.action === 'workstream-archived')
|
|
303
|
+
return 'Workstream archived';
|
|
304
|
+
if (event.action === 'workstream-unarchived')
|
|
305
|
+
return 'Workstream restored';
|
|
306
|
+
if (event.action === 'workstream-task-added')
|
|
307
|
+
return taskTitle ? `Task added: ${taskTitle}` : 'Task added';
|
|
308
|
+
if (event.action === 'workstream-task-removed')
|
|
309
|
+
return taskTitle ? `Task removed: ${taskTitle}` : 'Task removed';
|
|
310
|
+
if (event.action === 'workstream-relationship-changed' && changes.initiativeId) {
|
|
311
|
+
return `Initiative changed: ${formatValue(changes.initiativeId.from, 'initiativeId', formatter)} -> ${formatValue(changes.initiativeId.to, 'initiativeId', formatter)}`;
|
|
312
|
+
}
|
|
313
|
+
if ((event.action === 'initiative-attachments-changed' || event.action === 'workstream-attachments-changed') && changes.attachments) {
|
|
314
|
+
return summarizeTaskChange('attachments', changes.attachments, formatter);
|
|
315
|
+
}
|
|
316
|
+
if (fields.length === 1) {
|
|
317
|
+
return summarizeTaskChange(fields[0], changes[fields[0]], formatter);
|
|
318
|
+
}
|
|
319
|
+
if (fields.length > 1)
|
|
320
|
+
return `${fields.length} fields updated`;
|
|
321
|
+
if (event.action === 'initiative-updated' || event.action === 'workstream-updated')
|
|
322
|
+
return `${entityLabel} updated`;
|
|
323
|
+
return `${entityLabel} activity updated`;
|
|
324
|
+
}
|
|
280
325
|
function activityTimestamp(item) {
|
|
281
326
|
const parsed = Date.parse(String(item.timestamp || ''));
|
|
282
327
|
return Number.isFinite(parsed) ? parsed : null;
|
|
@@ -302,7 +347,21 @@ export function getTaskEventPrimaryChangeField(event) {
|
|
|
302
347
|
return 'attachments';
|
|
303
348
|
return fields.length === 1 ? fields[0] : null;
|
|
304
349
|
}
|
|
305
|
-
export function
|
|
350
|
+
export function getEntityEventPrimaryChangeField(event) {
|
|
351
|
+
if (event.taskId || event.entityType === 'task') {
|
|
352
|
+
return getTaskEventPrimaryChangeField(event);
|
|
353
|
+
}
|
|
354
|
+
const changes = event.details?.changes || {};
|
|
355
|
+
const fields = Object.keys(changes);
|
|
356
|
+
if ((event.action === 'workstream-task-added' || event.action === 'workstream-task-removed') && event.details?.taskId)
|
|
357
|
+
return 'taskId';
|
|
358
|
+
if (event.action === 'workstream-relationship-changed' && changes.initiativeId)
|
|
359
|
+
return 'initiativeId';
|
|
360
|
+
if ((event.action === 'initiative-attachments-changed' || event.action === 'workstream-attachments-changed') && changes.attachments)
|
|
361
|
+
return 'attachments';
|
|
362
|
+
return fields.length === 1 ? fields[0] : null;
|
|
363
|
+
}
|
|
364
|
+
export function getEntityActivityItems(task) {
|
|
306
365
|
const deduped = new Map();
|
|
307
366
|
const commentIds = new Set();
|
|
308
367
|
const addItem = (item) => {
|
|
@@ -344,6 +403,9 @@ export function getTaskActivityItems(task) {
|
|
|
344
403
|
return String(left.id || '').localeCompare(String(right.id || ''));
|
|
345
404
|
});
|
|
346
405
|
}
|
|
406
|
+
export function getTaskActivityItems(task) {
|
|
407
|
+
return getEntityActivityItems(task);
|
|
408
|
+
}
|
|
347
409
|
export function getLatestTaskActivity(task) {
|
|
348
410
|
const items = getTaskActivityItems(task);
|
|
349
411
|
if (items.length === 0)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getTaskActivityItems, summarizeTaskChange, summarizeTaskEvent } from './taskActivity';
|
|
2
|
+
import { getTaskActivityItems, summarizeEntityEvent, summarizeTaskChange, summarizeTaskEvent } from './taskActivity';
|
|
3
3
|
describe('summarizeTaskChange', () => {
|
|
4
4
|
it('summarizes title edits without dumping before and after text', () => {
|
|
5
5
|
expect(summarizeTaskChange('title', {
|
|
@@ -149,6 +149,28 @@ describe('summarizeTaskEvent', () => {
|
|
|
149
149
|
},
|
|
150
150
|
})).toBe('Image replaced: Old proof -> New proof');
|
|
151
151
|
});
|
|
152
|
+
it('uses task-focused summaries for workstream task relationship events', () => {
|
|
153
|
+
expect(summarizeEntityEvent({
|
|
154
|
+
id: 'event-3',
|
|
155
|
+
workspaceId: 'default',
|
|
156
|
+
entityType: 'workstream',
|
|
157
|
+
entityId: 'workstream-1',
|
|
158
|
+
action: 'workstream-task-added',
|
|
159
|
+
actor: 'user',
|
|
160
|
+
actorType: 'human',
|
|
161
|
+
createdAt: '2026-04-12T21:42:00.000Z',
|
|
162
|
+
details: {
|
|
163
|
+
taskId: 'task-1',
|
|
164
|
+
taskTitle: 'Ship launch checklist',
|
|
165
|
+
changes: {
|
|
166
|
+
workstreamId: {
|
|
167
|
+
from: null,
|
|
168
|
+
to: 'workstream-1',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
})).toBe('Task added: Ship launch checklist');
|
|
173
|
+
});
|
|
152
174
|
});
|
|
153
175
|
describe('getTaskActivityItems', () => {
|
|
154
176
|
it('combines comments and audit events while hiding duplicate comment-added events', () => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CategoryConfig, PriorityDefinition, TaskItem, TaxonomyDefinition } from '../types';
|
|
2
|
+
import type { TaskAssigneeOption } from './assignees';
|
|
3
|
+
export interface NormalizedTaskSearchQuery {
|
|
4
|
+
raw: string;
|
|
5
|
+
tokens: string[];
|
|
6
|
+
referenceToken: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface TaskSearchContext {
|
|
9
|
+
categories?: CategoryConfig[];
|
|
10
|
+
types?: Array<{
|
|
11
|
+
value: string | number;
|
|
12
|
+
label: string;
|
|
13
|
+
}>;
|
|
14
|
+
priorities?: PriorityDefinition[];
|
|
15
|
+
assigneeOptions?: TaskAssigneeOption[];
|
|
16
|
+
taxonomies?: TaxonomyDefinition[];
|
|
17
|
+
}
|
|
18
|
+
export declare function normalizeSearchText(value: unknown): string;
|
|
19
|
+
export declare function normalizeTaskReferenceToken(value: unknown): string | null;
|
|
20
|
+
export declare function normalizeTaskSearchQuery(query: string): NormalizedTaskSearchQuery;
|
|
21
|
+
export declare function getTaskSearchFields(task: TaskItem, context?: TaskSearchContext): string[];
|
|
22
|
+
export declare function taskMatchesSearch(task: TaskItem, query: string | NormalizedTaskSearchQuery, context?: TaskSearchContext): boolean;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { STATUS_OPTIONS } from './constants';
|
|
2
|
+
import { getTaskReferenceLabel } from './taskReferences';
|
|
3
|
+
const DASH_LIKE_PATTERN = /[\u2010-\u2015\u2212]/g;
|
|
4
|
+
export function normalizeSearchText(value) {
|
|
5
|
+
return String(value ?? '')
|
|
6
|
+
.trim()
|
|
7
|
+
.replace(DASH_LIKE_PATTERN, '-')
|
|
8
|
+
.replace(/\s+/g, ' ')
|
|
9
|
+
.toLowerCase();
|
|
10
|
+
}
|
|
11
|
+
export function normalizeTaskReferenceToken(value) {
|
|
12
|
+
const normalized = normalizeSearchText(value);
|
|
13
|
+
if (!normalized)
|
|
14
|
+
return null;
|
|
15
|
+
const match = normalized.match(/^(l\s*t|t)\s*(?:-\s*)?(\d+)$/i);
|
|
16
|
+
if (!match)
|
|
17
|
+
return null;
|
|
18
|
+
const prefix = match[1].replace(/\s+/g, '').toLowerCase();
|
|
19
|
+
return `${prefix}-${match[2]}`;
|
|
20
|
+
}
|
|
21
|
+
export function normalizeTaskSearchQuery(query) {
|
|
22
|
+
const raw = normalizeSearchText(query);
|
|
23
|
+
return {
|
|
24
|
+
raw,
|
|
25
|
+
tokens: raw ? raw.split(' ').filter(Boolean) : [],
|
|
26
|
+
referenceToken: normalizeTaskReferenceToken(query)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function addValue(values, value) {
|
|
30
|
+
const normalized = String(value ?? '').trim();
|
|
31
|
+
if (normalized)
|
|
32
|
+
values.push(normalized);
|
|
33
|
+
}
|
|
34
|
+
function addMappedLabel(values, options, targetValue) {
|
|
35
|
+
const target = String(targetValue ?? '').trim();
|
|
36
|
+
if (!target)
|
|
37
|
+
return;
|
|
38
|
+
const option = options?.find((entry) => String(entry.value) === target);
|
|
39
|
+
addValue(values, option?.label);
|
|
40
|
+
}
|
|
41
|
+
export function getTaskSearchFields(task, context = {}) {
|
|
42
|
+
const values = [];
|
|
43
|
+
addValue(values, task.title);
|
|
44
|
+
addValue(values, task.description);
|
|
45
|
+
addValue(values, task.id);
|
|
46
|
+
addValue(values, getTaskReferenceLabel(task));
|
|
47
|
+
addValue(values, task.category);
|
|
48
|
+
addValue(values, task.type);
|
|
49
|
+
addValue(values, task.priority);
|
|
50
|
+
addValue(values, task.status);
|
|
51
|
+
addValue(values, task.assignee || 'unassigned');
|
|
52
|
+
addValue(values, task.canceledReason);
|
|
53
|
+
addMappedLabel(values, context.categories, task.category);
|
|
54
|
+
addMappedLabel(values, context.types, task.type);
|
|
55
|
+
addMappedLabel(values, context.priorities, task.priority);
|
|
56
|
+
addMappedLabel(values, STATUS_OPTIONS, task.status);
|
|
57
|
+
addMappedLabel(values, context.assigneeOptions, task.assignee || 'unassigned');
|
|
58
|
+
Object.entries(task.taxonomies || {}).forEach(([taxonomyId, taskValue]) => {
|
|
59
|
+
const taxonomy = context.taxonomies?.find((entry) => entry.id === taxonomyId);
|
|
60
|
+
addValue(values, taxonomy?.label);
|
|
61
|
+
const taskValues = Array.isArray(taskValue) ? taskValue : [taskValue];
|
|
62
|
+
taskValues.forEach((value) => {
|
|
63
|
+
addValue(values, value);
|
|
64
|
+
addMappedLabel(values, taxonomy?.options, value);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
return values;
|
|
68
|
+
}
|
|
69
|
+
export function taskMatchesSearch(task, query, context = {}) {
|
|
70
|
+
const normalizedQuery = typeof query === 'string' ? normalizeTaskSearchQuery(query) : query;
|
|
71
|
+
if (!normalizedQuery.raw)
|
|
72
|
+
return true;
|
|
73
|
+
const fields = getTaskSearchFields(task, context);
|
|
74
|
+
const normalizedFields = fields.map(normalizeSearchText).filter(Boolean);
|
|
75
|
+
const joinedFields = normalizedFields.join(' ');
|
|
76
|
+
if (joinedFields.includes(normalizedQuery.raw))
|
|
77
|
+
return true;
|
|
78
|
+
if (normalizedQuery.referenceToken) {
|
|
79
|
+
const referenceMatches = fields
|
|
80
|
+
.map(normalizeTaskReferenceToken)
|
|
81
|
+
.some((referenceToken) => referenceToken?.includes(normalizedQuery.referenceToken));
|
|
82
|
+
if (referenceMatches)
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return normalizedQuery.tokens.length > 0
|
|
86
|
+
&& normalizedQuery.tokens.every((token) => joinedFields.includes(token));
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { normalizeTaskReferenceToken, normalizeTaskSearchQuery, taskMatchesSearch, } from './taskSearch';
|
|
3
|
+
function makeTask(overrides = {}) {
|
|
4
|
+
return {
|
|
5
|
+
id: overrides.id || 'task-1',
|
|
6
|
+
title: overrides.title || 'Task',
|
|
7
|
+
description: overrides.description ?? null,
|
|
8
|
+
status: overrides.status || 'task',
|
|
9
|
+
priority: overrides.priority ?? 2,
|
|
10
|
+
complexity: overrides.complexity ?? 3,
|
|
11
|
+
type: overrides.type || 'feature',
|
|
12
|
+
category: overrides.category || 'default',
|
|
13
|
+
createdAt: overrides.createdAt || '2026-03-23T00:00:00.000Z',
|
|
14
|
+
updatedAt: overrides.updatedAt || null,
|
|
15
|
+
completedAt: overrides.completedAt || null,
|
|
16
|
+
assignee: overrides.assignee || 'unassigned',
|
|
17
|
+
comments: [],
|
|
18
|
+
attachments: [],
|
|
19
|
+
activity: [],
|
|
20
|
+
taskEvents: [],
|
|
21
|
+
referenceLabel: overrides.referenceLabel,
|
|
22
|
+
taxonomies: overrides.taxonomies || {}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
describe('taskSearch', () => {
|
|
26
|
+
it('normalizes common pasted task reference formats', () => {
|
|
27
|
+
expect(normalizeTaskReferenceToken(' T-123 ')).toBe('t-123');
|
|
28
|
+
expect(normalizeTaskReferenceToken('T - 123')).toBe('t-123');
|
|
29
|
+
expect(normalizeTaskReferenceToken('T 123')).toBe('t-123');
|
|
30
|
+
expect(normalizeTaskReferenceToken('t–123')).toBe('t-123');
|
|
31
|
+
expect(normalizeTaskReferenceToken('LT - 45')).toBe('lt-45');
|
|
32
|
+
});
|
|
33
|
+
it('matches canonicalized task references', () => {
|
|
34
|
+
const task = makeTask({ referenceLabel: 'T-123' });
|
|
35
|
+
expect(taskMatchesSearch(task, normalizeTaskSearchQuery('T - 123'))).toBe(true);
|
|
36
|
+
expect(taskMatchesSearch(task, normalizeTaskSearchQuery('t–123'))).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
it('matches all query tokens across task text', () => {
|
|
39
|
+
const task = makeTask({ title: 'Billing cleanup', description: 'Audit the API handlers' });
|
|
40
|
+
expect(taskMatchesSearch(task, 'api billing')).toBe(true);
|
|
41
|
+
expect(taskMatchesSearch(task, 'api invoices')).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
it('matches task metadata labels', () => {
|
|
44
|
+
const task = makeTask({
|
|
45
|
+
category: 'ops',
|
|
46
|
+
type: 'bug',
|
|
47
|
+
priority: 3,
|
|
48
|
+
assignee: 'member-1',
|
|
49
|
+
taxonomies: { track: 'platform' }
|
|
50
|
+
});
|
|
51
|
+
const context = {
|
|
52
|
+
categories: [{ value: 'ops', label: 'Operations', disabled: false }],
|
|
53
|
+
types: [{ value: 'bug', label: 'Bug' }],
|
|
54
|
+
priorities: [{ value: 3, label: 'High' }],
|
|
55
|
+
assigneeOptions: [{ value: 'member-1', label: 'Member One', icon: 'User', color: '#22c55e', kind: 'member' }],
|
|
56
|
+
taxonomies: [{
|
|
57
|
+
id: 'track',
|
|
58
|
+
label: 'Track',
|
|
59
|
+
options: [{ value: 'platform', label: 'Platform' }],
|
|
60
|
+
widgetType: 'select',
|
|
61
|
+
isSystem: false,
|
|
62
|
+
multiSelect: false
|
|
63
|
+
}]
|
|
64
|
+
};
|
|
65
|
+
expect(taskMatchesSearch(task, 'operations', context)).toBe(true);
|
|
66
|
+
expect(taskMatchesSearch(task, 'member one', context)).toBe(true);
|
|
67
|
+
expect(taskMatchesSearch(task, 'track platform', context)).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
package/dist/utils/taskUtils.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
|
2
2
|
import * as Icons from 'lucide-react';
|
|
3
3
|
import { TypeMeta } from './constants';
|
|
4
4
|
import styles from '../Taskforce.module.css';
|
|
5
|
+
import { normalizeTaskReferenceToken } from './taskSearch';
|
|
5
6
|
export const getIconForType = (type, size = 10) => {
|
|
6
7
|
const meta = TypeMeta[type?.toLowerCase()] || { icon: 'FileCode', color: 'indigo-500' };
|
|
7
8
|
const IconComp = Icons[meta.icon] || Icons.FileCode;
|
|
@@ -10,11 +11,17 @@ export const getIconForType = (type, size = 10) => {
|
|
|
10
11
|
export const renderHighlighted = (text, query) => {
|
|
11
12
|
if (!text)
|
|
12
13
|
return null;
|
|
13
|
-
|
|
14
|
+
const normalizedQuery = query.trim();
|
|
15
|
+
if (!normalizedQuery)
|
|
14
16
|
return text;
|
|
15
|
-
const
|
|
17
|
+
const referenceToken = normalizeTaskReferenceToken(normalizedQuery);
|
|
18
|
+
const referenceParts = referenceToken?.match(/^(lt|t)-(\d+)$/);
|
|
19
|
+
const escapedQuery = referenceParts
|
|
20
|
+
? `${referenceParts[1] === 'lt' ? 'l\\s*t' : 't'}\\s*(?:-|\\s)?\\s*${referenceParts[2]}`
|
|
21
|
+
: normalizedQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
22
|
+
const highlightPattern = new RegExp(`^${escapedQuery}$`, 'i');
|
|
16
23
|
const parts = text.split(new RegExp(`(${escapedQuery})`, 'gi'));
|
|
17
|
-
return (_jsx(_Fragment, { children: parts.map((part, i) =>
|
|
24
|
+
return (_jsx(_Fragment, { children: parts.map((part, i) => highlightPattern.test(part)
|
|
18
25
|
? _jsx("mark", { className: styles.highlight, children: part }, i)
|
|
19
26
|
: part) }));
|
|
20
27
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { renderHighlighted } from './taskUtils';
|
|
5
|
+
describe('renderHighlighted', () => {
|
|
6
|
+
it('highlights matches when the query has surrounding spaces', () => {
|
|
7
|
+
const { container } = render(_jsx("p", { children: renderHighlighted('T-123', ' T-123 ') }));
|
|
8
|
+
expect(container.querySelector('mark')?.textContent).toBe('T-123');
|
|
9
|
+
});
|
|
10
|
+
it('highlights task references when the query has pasted formatting', () => {
|
|
11
|
+
const { container } = render(_jsx("p", { children: renderHighlighted('T-123', ' T - 123 ') }));
|
|
12
|
+
expect(container.querySelector('mark')?.textContent).toBe('T-123');
|
|
13
|
+
});
|
|
14
|
+
it('treats whitespace-only queries as empty', () => {
|
|
15
|
+
const { container } = render(_jsx("p", { children: renderHighlighted('T-123', ' ') }));
|
|
16
|
+
expect(container.querySelector('mark')).toBeNull();
|
|
17
|
+
expect(container.textContent).toBe('T-123');
|
|
18
|
+
});
|
|
19
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taskforcehq/taskforce",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.308",
|
|
4
4
|
"description": "Mission Command center for AI-powered task management (beta - under active development)",
|
|
5
5
|
"author": "Taskforce HQ <hello@taskforcehq.ai> (https://taskforcehq.ai)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as c,j as e}from"./vendor-react-CKJs5o3c.js";import{u as se,p as oe,v as ne,w as ce,x as _,y as de,z as pe,t as r,E as fe,B as me,C as K}from"./index-C6k75jr8.js";import{h as ue,B as V,k as U,b as he,$ as ve,a3 as ge}from"./vendor-icons-DuEd_65c.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";function J(d){return!d||typeof d!="object"?null:{used:Math.max(0,Number(d.used||0)),limit:d.limit===null||d.limit===void 0?null:Math.max(0,Number(d.limit||0)),remaining:d.remaining===null||d.remaining===void 0?null:Math.max(0,Number(d.remaining||0))}}function W(d,P){const x=P?.variant==="inline"?`${r.aiProfileSeatScopeIcon} ${r.aiProfileSeatScopeIconInline}`:P?.variant==="detail"?`${r.aiProfileSeatScopeIcon} ${r.aiProfileSeatScopeIconDetail}`:r.aiProfileSeatScopeIcon;return d==="cloud_metered"?e.jsx("span",{className:`${x} ${r.aiProfileSeatScopeIconCloud}`,title:"Cloud MCP","aria-label":"Cloud MCP",children:e.jsx(ve,{size:20})}):d==="local_unmetered"?e.jsx("span",{className:`${x} ${r.aiProfileSeatScopeIconLocal}`,title:"Local MCP","aria-label":"Local MCP",children:e.jsx(ge,{size:20})}):null}function Ie({workspaceId:d,cloudAuthConfigured:P=!1,authSessionResolved:x=!1,isAuthenticated:Y=!1,cloudAiProfileSeatUsage:q=null}){const[S,L]=c.useState([]),[Q,X]=c.useState(null),[M,$]=c.useState(!1),[f,j]=c.useState(null),[I,v]=c.useState(null),[N,A]=c.useState(null),[g,b]=c.useState(null),[E,T]=c.useState(null),[B,D]=c.useState(!1),[Z,m]=c.useState(null),w=c.useCallback(async()=>{if(d){$(!0);try{const a=`/api/taskforce/workspace/assignee-options?workspaceId=${encodeURIComponent(d)}`,i=await fetch(a,{credentials:"include"}),t=i.ok?await i.json().catch(()=>({})):{},s=J(t?.aiProfileSeatUsage),n=Array.isArray(t?.assignees)?t.assignees.filter(l=>l.kind==="agent").map(l=>({id:String(l.value||""),name:String(l.label||l.value||"Unknown Agent"),username:String(l.username||l.value||""),icon:String(l.icon||"Bot"),color:String(l.color||"#6B7280"),avatarUrl:typeof l.avatarUrl=="string"?l.avatarUrl:null,avatarSourceUrl:typeof l.avatarSourceUrl=="string"?l.avatarSourceUrl:null,kind:String(l.kind||"agent"),description:typeof l.description=="string"?l.description:null,role:typeof l.role=="string"?l.role:null,provider:typeof l.provider=="string"?l.provider:null,model:typeof l.model=="string"?l.model:null,surfaceType:oe(l.surfaceType),seatScope:se(l.seatScope),archivedAt:typeof l.archivedAt=="string"?l.archivedAt:null,createdAt:String(l.createdAt||""),updatedAt:String(l.updatedAt||l.createdAt||""),lastActiveAt:typeof l.lastActiveAt=="string"?l.lastActiveAt:null})):[];L(n),X(s),A(l=>l&&!n.some(h=>h.id===l.profileId)?null:l)}finally{$(!1)}}},[d]);c.useEffect(()=>{w()},[w]);const k=c.useMemo(()=>{const a=new Map;for(const i of S){const t=ne(i.surfaceType);a.has(t)||a.set(t,new Map);const s=a.get(t),n=i.name.toLowerCase();s.has(n)||s.set(n,[]),s.get(n).push(i)}return ce.map(i=>({section:i,label:_(i),groups:Array.from(a.get(i)?.entries()||[]).map(([t,s])=>({groupId:`${i}:${t}`,section:i,sectionLabel:_(i),profiles:s,primaryProfile:s[0]}))})).filter(i=>i.groups.length>0)},[S]),u=c.useMemo(()=>k.flatMap(a=>a.groups),[k]),o=c.useMemo(()=>u.find(a=>a.groupId===g)||u[0]||null,[u,g]),p=c.useMemo(()=>S.find(a=>a.id===E)||null,[S,E]);c.useEffect(()=>{if(!u.length){g!==null&&b(null);return}(!g||!u.some(a=>a.groupId===g))&&b(u[0].groupId)},[u,g]);const ee=async(a,i)=>{v(null),A(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:a,mergeId:i})}),s=await t.json().catch(()=>({}));if(!t.ok){v({type:"error",message:String(s?.error||"Failed to merge AI profiles.")});return}j(null),v({type:"success",message:"AI profiles merged."}),await w()}catch{v({type:"error",message:"Failed to merge AI profiles."})}},G=async a=>{if(window.confirm(`Remove ${a.name} from the active roster? This frees an AI profile seat and preserves task and comment history.`)){A(null),v(null);try{const t=await fetch("/api/taskforce/workspace/ai-profiles/archive",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({profileId:a.id,reason:"manual_archive"})}),s=await t.json().catch(()=>({}));if(!t.ok){A({profileId:a.id,message:String(s?.error||"Failed to remove AI profile from roster.")});return}(f?.keepId===a.id||f?.mergeId===a.id)&&j(null),v({type:"success",message:"AI profile removed from active roster."}),await w()}catch{A({profileId:a.id,message:"Failed to remove AI profile from roster."})}}},ae=a=>new Promise((i,t)=>{const s=new FileReader;s.onload=()=>i(String(s.result||"")),s.onerror=()=>t(new Error("Failed to read image file.")),s.readAsDataURL(a)}),re=a=>{const i=String(a?.id||"").trim();i&&L(t=>t.map(s=>s.id===i?{...s,avatarUrl:typeof a.avatarUrl=="string"?a.avatarUrl:null,avatarSourceUrl:typeof a.avatarSourceUrl=="string"?a.avatarSourceUrl:null,updatedAt:typeof a.updatedAt=="string"?a.updatedAt:s.updatedAt}:s))},F=async(a,i,t)=>{const s=await fetch("/api/taskforce/workspace/ai-profiles/avatar",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({profileId:a,avatarUrl:i,...t!==void 0?{avatarSourceUrl:t}:{}})}),n=await s.json().catch(()=>({}));if(!s.ok||!n?.profile)throw new Error(String(n?.error||"Failed to update AI profile avatar."));re(n.profile)},z=async(a,i,t)=>{const s=await ae(a),n=await fetch("/api/taskforce/context-upload",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({workspaceId:d,file:s,originalName:`${t}-${a.name}`,folderPath:`ai-profiles/${i.id}/avatar`})}),l=await n.json().catch(()=>({})),h=typeof l?.path=="string"?l.path:"";if(!n.ok||!h)throw new Error(String(l?.error||"Failed to upload AI profile photo."));return h},ie=async(a,i)=>{if(!p)return!1;if(!a.type.startsWith("image/"))return m("AI profile photo must be an image file."),!1;D(!0),m(null);try{const t=await K(a,{maxBytes:5242880});if(t.exceededLimit)throw new Error(a.type==="image/gif"?"Animated GIF AI profile photos must be 5 MB or smaller.":"AI profile photo must be 5 MB or smaller.");const s=t.file,n=await z(s,p,"cropped");let l;if(i){const h=await K(i,{maxBytes:5242880});if(h.exceededLimit)throw new Error(i.type==="image/gif"?"Animated GIF AI profile photos must be 5 MB or smaller.":"AI profile source photo must be 5 MB or smaller.");l=i.type==="image/gif"?n:await z(h.file,p,"source")}return await F(p.id,n,l),!0}catch(t){return m(String(t?.message||"Failed to update AI profile photo.")),!1}finally{D(!1)}},te=async()=>{if(p){D(!0),m(null);try{await F(p.id,null,null)}catch(a){m(String(a?.message||"Failed to remove AI profile photo."))}finally{D(!1)}}},y=a=>{const i=String(a||"").trim();if(!i)return"Unknown";const t=Date.parse(i);return Number.isFinite(t)?new Date(t).toLocaleString(void 0,{dateStyle:"medium",timeStyle:"short"}):i},H=c.useMemo(()=>{if(!o)return{attributes:[],dates:[]};const a=o.primaryProfile,i=t=>t||"Not set";return{attributes:[{label:"Category",value:a.surfaceType?de(a.surfaceType):""},{label:"Connection",value:a.seatScope?pe(a.seatScope):""},{label:"Provider",value:a.provider||""},{label:"Model",value:a.model||""}].map(t=>({...t,value:i(t.value)})),dates:[{label:"Recruited",value:y(a.createdAt)},{label:"Last updated",value:y(a.updatedAt)},{label:"Last active",value:y(a.lastActiveAt)},{label:"Status",value:a.archivedAt?"Retired":"Active"}].map(t=>({...t,value:i(t.value)}))}},[o]),le=!!o&&o.profiles.length>1,O=P&&x&&!Y,C=P?J(q):Q,R=O?"Log into account for Cloud agents":C?`${C.used}/${C.limit===null?"Unlimited":C.limit}`:null;return e.jsxs("section",{className:r.agentsModuleRoot,children:[e.jsx("div",{className:r.agentsModuleContent,children:e.jsx("div",{className:`${r.settingGroup} ${r.agentsModuleGroup}`,children:e.jsxs("div",{children:[e.jsx("h4",{className:r.settingSubTitle,children:"Registered AI Profiles"}),e.jsx("p",{className:`${r.settingsHint} ${r.marginBottom12}`,children:"AI agents register profiles when connecting via MCP. Merge duplicates created when a token was lost, or remove inactive profiles from the active roster to free seats while preserving history."}),R&&e.jsx("div",{className:r.aiProfileSeatSummary,children:O?R:`Registered Cloud Agents: ${R}`}),M&&e.jsxs("div",{className:r.settingsHint,children:[e.jsx(ue,{size:13,className:r.spinner})," Loading profiles…"]}),!M&&S.length===0&&e.jsx("div",{className:r.settingsHint,children:"No AI profiles registered yet."}),!M&&k.length>0&&e.jsxs("div",{className:r.aiProfilesExplorer,children:[e.jsx("div",{className:`${r.aiProfilesListPane} tf-scrollbar tf-scrollbar--track-transparent`,children:e.jsx("div",{className:r.aiProfilesList,children:k.map(a=>e.jsxs("div",{className:r.aiProfilesSection,children:[e.jsx("div",{className:r.aiProfilesSectionHeader,children:a.label}),a.groups.map(i=>{const t=o?.groupId===i.groupId,s=i.primaryProfile;return e.jsxs("div",{role:"button",tabIndex:0,className:`${r.aiProfileGroup} ${i.profiles.length>1?r.aiProfileGroupDuplicate:""} ${t?r.aiProfileGroupSelected:""}`,onClick:()=>b(i.groupId),onKeyDown:n=>{n.key!=="Enter"&&n.key!==" "||(n.preventDefault(),b(i.groupId))},"aria-pressed":t,children:[W(s.seatScope),e.jsxs("div",{className:r.aiProfileGroupHeader,children:[e.jsx("span",{className:r.aiProfileGroupAvatar,style:{color:s.color},children:s.avatarUrl?e.jsx("img",{src:s.avatarUrl,alt:""}):e.jsx(V,{size:22})}),e.jsxs("span",{className:r.aiProfileGroupIdentity,children:[e.jsx("span",{className:r.aiProfileName,children:s.name}),e.jsxs("span",{className:r.aiProfileHandle,children:["@",s.username]}),e.jsx("span",{className:r.aiProfileRole,children:s.role||"Role not set"}),i.profiles.length>1&&e.jsxs("span",{className:r.aiProfileDuplicateBadge,children:[e.jsx(U,{size:11})," ",i.profiles.length," duplicates"]})]})]})]},i.groupId)})]},a.section))})}),o&&e.jsx("div",{className:r.aiProfileDetailPane,children:e.jsxs("div",{className:r.aiProfileDetailCard,children:[W(o.primaryProfile.seatScope,{variant:"detail"}),e.jsxs("div",{className:r.aiProfileDetailHero,children:[e.jsx(fe,{label:"Edit AI profile photo",imageUrl:o.primaryProfile.avatarUrl,fallback:e.jsx(V,{size:38}),accentColor:o.primaryProfile.color,size:176,width:153,height:207,radius:6,editBadgeSize:28,editIconSize:14,className:r.aiProfileDetailAvatar,onClick:()=>{m(null),T(o.primaryProfile.id)}}),e.jsxs("div",{className:r.aiProfileDetailHeading,children:[e.jsx("div",{className:r.aiProfileDetailTitleRow,children:e.jsx("h5",{className:r.aiProfileDetailTitle,children:o.primaryProfile.name})}),e.jsxs("div",{className:r.aiProfileDetailMetaRow,children:[e.jsxs("span",{className:r.aiProfileDetailHandle,children:["@",o.primaryProfile.username]}),o.profiles.length>1&&e.jsxs("span",{className:r.aiProfileDetailLinkedCount,children:[o.profiles.length," linked"]})]}),e.jsxs("div",{className:r.aiProfileDetailRole,children:["Role: ",o.primaryProfile.role||"Not set"]}),o.primaryProfile.description&&e.jsx("div",{className:r.aiProfileDetailDescription,children:o.primaryProfile.description})]})]}),e.jsxs("div",{className:r.aiProfileDetailDataList,children:[e.jsx("div",{className:r.aiProfileDetailDataGroup,children:H.attributes.map(a=>e.jsxs("div",{className:r.aiProfileDetailDataRow,children:[e.jsx("span",{className:r.aiProfileDetailDataLabel,children:a.label}),e.jsx("span",{className:r.aiProfileDetailDataValue,children:a.value})]},a.label))}),e.jsx("div",{className:`${r.aiProfileDetailDataGroup} ${r.aiProfileDetailDateGroup}`,children:H.dates.map(a=>e.jsxs("div",{className:r.aiProfileDetailDataRow,children:[e.jsx("span",{className:r.aiProfileDetailDataLabel,children:a.label}),a.label==="Status"?e.jsx("span",{className:r.aiProfileDetailDataValue,children:e.jsxs("select",{className:r.aiProfileStatusSelect,"aria-label":"Agent roster status",value:o.primaryProfile.archivedAt?"retired":"active",onChange:i=>{i.target.value==="retire"&&G(o.primaryProfile)},children:[e.jsx("option",{value:"active",children:"Active"}),o.primaryProfile.archivedAt?e.jsx("option",{value:"retired",children:"Retired"}):e.jsx("option",{value:"retire",children:"Retire from roster"})]})}):e.jsx("span",{className:r.aiProfileDetailDataValue,children:a.value})]},a.label))})]}),le?e.jsxs("div",{className:r.aiProfileInstanceSection,children:[e.jsxs("div",{className:r.aiProfileInstanceSectionHeader,children:[e.jsx("span",{children:"Profile instances"}),e.jsx("span",{className:r.settingsHint,children:"Choose a keeper here if duplicates need to be merged."})]}),e.jsx("div",{className:r.aiProfileInstanceList,children:o.profiles.map(a=>e.jsxs("div",{className:r.aiProfileInstanceCard,children:[e.jsxs("div",{className:r.aiProfileInstanceTopRow,children:[e.jsxs("div",{children:[e.jsxs("div",{className:r.aiProfileInstanceName,children:["@",a.username]}),e.jsx("div",{className:r.aiProfileIdChip,children:a.id})]}),f?.keepId===a.id&&e.jsx("span",{className:r.aiProfileKeepBadge,children:"Keeping"})]}),e.jsxs("div",{className:r.aiProfileInstanceMeta,children:[e.jsxs("span",{children:["Created ",y(a.createdAt)]}),e.jsxs("span",{children:["Updated ",y(a.updatedAt)]})]}),f?.keepId!==a.id&&e.jsx("button",{className:r.secondaryHeaderBtn,title:"Keep this profile, merge others into it",onClick:()=>{const i=o.profiles.find(t=>t.id!==a.id)?.id;i&&j({keepId:a.id,mergeId:i})},children:"Keep this"}),N?.profileId===a.id&&e.jsxs("div",{className:r.aiProfileInlineError,role:"alert",children:[e.jsx(U,{size:12}),e.jsx("span",{children:N.message})]}),e.jsx("button",{className:r.aiProfileDangerTextButton,title:"Remove this profile from the active roster",onClick:()=>{G(a)},children:"Remove from roster"})]},a.id))}),f&&o.profiles.some(a=>a.id===f.keepId)&&e.jsxs("div",{className:r.aiProfileMergeActions,children:[e.jsx("button",{className:r.dangerBtn,onClick:()=>{ee(f.keepId,f.mergeId)},children:"Merge duplicates"}),e.jsx("button",{className:r.secondaryHeaderBtn,onClick:()=>j(null),children:"Cancel"})]})]}):e.jsxs("div",{className:r.aiProfileIdFooter,children:[e.jsx("div",{className:r.aiProfileIdFooterValue,children:o.primaryProfile.id}),N?.profileId===o.primaryProfile.id&&e.jsxs("div",{className:r.aiProfileInlineError,role:"alert",children:[e.jsx(U,{size:12}),e.jsx("span",{children:N.message})]})]})]})})]}),I&&e.jsxs("div",{className:`${I.type==="success"?r.successMessage:r.errorMessage} ${r.marginTop12}`,children:[I.type==="success"?e.jsx(he,{size:14}):e.jsx(U,{size:14}),I.message]})]})})}),e.jsx(me,{isOpen:!!p,theme:"dark",title:"Edit AI Profile Photo",currentImageUrl:p?.avatarUrl||"",editorImageUrl:p?.avatarSourceUrl||"",fallbackInitial:(p?.name||"AI").charAt(0).toUpperCase(),accept:"image/png,image/jpeg,image/webp,image/gif",busy:B,hasPendingImage:!1,canRemove:!!p?.avatarUrl,error:Z,notice:null,onClose:()=>{B||(T(null),m(null))},onApplyImage:ie,onRemoveImage:te})]})}export{Ie as AgentsModule};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
._shell_sf7jc_1{align-self:stretch;width:100%;box-sizing:border-box;display:grid;grid-template-columns:minmax(220px,280px) minmax(0,1fr) minmax(280px,340px);gap:16px;height:100%;min-height:0;padding:16px;background:radial-gradient(circle at top left,color-mix(in srgb,var(--brand-primary) 8%,transparent),transparent 26%),linear-gradient(180deg,color-mix(in srgb,var(--surface-page) 84%,var(--surface-panel)) 0%,var(--surface-page) 100%)}._panel_sf7jc_16{min-height:0;border-radius:18px;box-shadow:0 18px 38px #0f172a14}._sessionPanel_sf7jc_22,._detailPanel_sf7jc_23{display:flex;flex-direction:column;overflow:hidden}._sessionContextBar_sf7jc_29{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:58px;padding:12px 16px;border-bottom:1px solid var(--border-default);background:color-mix(in srgb,var(--surface-inset) 88%,transparent)}._sessionContextLeft_sf7jc_40,._sessionContextRight_sf7jc_41{display:flex;align-items:center;min-width:0}._sessionContextLeft_sf7jc_40{flex:1}._sessionContextRight_sf7jc_41{justify-content:flex-end;min-width:118px}._sessionContextLabel_sf7jc_56{font-size:.84rem;font-weight:600;color:var(--text-secondary)}._sessionContextSpacer_sf7jc_62{width:118px;height:32px}._canvasPanel_sf7jc_67{display:flex;flex-direction:column;min-height:0;overflow:hidden}._canvasWorkspace_sf7jc_74{display:grid;grid-template-columns:auto minmax(0,1fr);min-height:0;flex:1}._canvasMain_sf7jc_81{display:flex;flex-direction:column;min-width:0;min-height:0}._panelHeader_sf7jc_88{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:16px;border-bottom:1px solid var(--border-default)}._panelHeaderText_sf7jc_97{min-width:0}._panelTitle_sf7jc_101{font-size:1rem}._canvasHeading_sf7jc_105{min-width:0}._sessionActions_sf7jc_109{display:inline-flex;align-items:center;gap:8px;margin-left:auto;flex-shrink:0}._sessionList_sf7jc_117,._annotationList_sf7jc_118{display:flex;flex:1 1 auto;flex-direction:column;gap:10px;min-height:0;padding:14px;overflow:auto;scrollbar-gutter:stable}._annotationList_sf7jc_118{gap:12px}._markerHelpModalBody_sf7jc_133{display:flex;flex-direction:column;gap:14px}._openImageModalBody_sf7jc_139{display:flex;flex-direction:column;gap:12px}._openImageField_sf7jc_145{width:100%}._openImageActions_sf7jc_149{display:flex;justify-content:flex-end;gap:8px}._markerHelpItem_sf7jc_155{display:flex;flex-direction:column;gap:6px;padding:12px 14px;border-radius:14px}._markerHelpHeader_sf7jc_163{display:flex;align-items:baseline;justify-content:space-between;gap:12px}._markerHelpItem_sf7jc_155 p{margin:0;line-height:1.45}._markerHelpExample_sf7jc_175{font-weight:600}._sessionCard_sf7jc_179,._annotationCard_sf7jc_180{display:flex;flex-direction:column;gap:8px;padding:12px;border-radius:14px;text-align:left;transition:border-color var(--transition-base),box-shadow var(--transition-base),transform var(--transition-base)}._sessionCard_sf7jc_179{gap:10px}._sessionEmptyState_sf7jc_197{display:flex;flex-direction:column;gap:8px;padding:8px 2px 0}._sessionCardButton_sf7jc_204,._annotationCardButton_sf7jc_214{display:flex;flex-direction:column;gap:8px;padding:0;border:0;background:transparent;text-align:left}._sessionCardBody_sf7jc_224{display:flex;flex-direction:column;gap:8px}._sessionCardActive_sf7jc_230,._annotationCardActive_sf7jc_231{border-color:var(--button-tile-active-border);box-shadow:0 0 0 1px transparent,0 12px 28px var(--brand-primary-glow-soft)}._sessionCard_sf7jc_179:hover,._annotationCard_sf7jc_180:hover{transform:translateY(-1px)}._annotationMeta_sf7jc_241{display:flex;align-items:center;justify-content:space-between;gap:8px}._annotationInstructionPreview_sf7jc_248{display:-webkit-box;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.45}._annotationPreviewFooter_sf7jc_256{display:flex;align-items:center;justify-content:flex-start;min-height:16px}._annotationInstructionEditor_sf7jc_263,._annotationTypeField_sf7jc_269{display:flex;flex-direction:column;gap:8px}._annotationInstructionButton_sf7jc_275{display:block;width:100%;padding:0;border:0;background:transparent;text-align:left}._annotationInstructionField_sf7jc_284{min-height:92px}._sessionMeta_sf7jc_288{display:flex;flex-direction:column;align-items:flex-start;gap:4px}._sessionTitle_sf7jc_295,._annotationTitle_sf7jc_296{font-size:.98rem;font-weight:700;color:var(--text-primary)}._sessionTimestamp_sf7jc_302,._annotationKind_sf7jc_303{text-transform:capitalize}._annotationInstructionTypeIcon_sf7jc_307{display:inline-flex;align-items:center;justify-content:center}._sessionInstructionPreview_sf7jc_313{line-height:1.45;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}._sessionInstructionEditor_sf7jc_321{display:flex;flex-direction:column;gap:8px}._sessionCardFooter_sf7jc_327{display:flex;align-items:center;justify-content:flex-end;gap:8px}._toolRail_sf7jc_334{display:flex;flex-wrap:wrap;gap:8px;padding:0;border-bottom:1px solid var(--border-default);background:color-mix(in srgb,var(--surface-inset) 92%,transparent)}._canvasToolRail_sf7jc_343{display:flex;flex-direction:column;align-items:center;gap:8px;padding:0;border-right:1px solid var(--border-default);background:color-mix(in srgb,var(--surface-inset) 94%,transparent)}._toolGroup_sf7jc_353{display:inline-flex;flex-wrap:wrap;gap:8px;align-items:center;padding:4px;border:1px solid var(--border-default);border-radius:16px;background:var(--surface-panel)}._toolbarCluster_sf7jc_364{display:inline-flex;flex-wrap:wrap;gap:8px;align-items:center}._toolbarViewportCluster_sf7jc_371{margin-left:44px}._toolbarSeparator_sf7jc_375{width:1px;align-self:stretch;background:var(--border-default)}._toolBtn_sf7jc_381{white-space:nowrap}._toolRailButton_sf7jc_385{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border-radius:14px}._toolbarButton_sf7jc_395{min-height:44px;border-radius:14px}._toolBtnActive_sf7jc_400{background:var(--button-tile-active-bg);border-color:var(--button-tile-active-border);color:var(--button-tile-active-text);box-shadow:0 0 0 1px transparent,0 8px 24px var(--brand-primary-glow-soft)}._toolbarActions_sf7jc_407{display:inline-flex;flex-wrap:wrap;align-items:center;gap:8px;flex:1 1 auto;min-width:0}._toolbarSelectionActions_sf7jc_416{display:inline-flex;align-items:center;gap:8px;min-width:44px;flex-wrap:wrap}._toolbarColorPicker_sf7jc_424{position:relative}._colorPickerButton_sf7jc_428{min-width:44px;padding:0}._colorPickerSwatch_sf7jc_433{width:24px;height:24px;border-radius:var(--radius-sm);border:2px solid color-mix(in srgb,var(--surface-panel) 88%,transparent);box-shadow:inset 0 0 0 1px #0f172a1f}._colorPickerPopover_sf7jc_441{position:absolute;top:calc(100% + 8px);left:0;z-index:3;display:grid;grid-template-columns:repeat(3,1fr);gap:8px;padding:10px;border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--surface-panel);box-shadow:0 18px 32px #0f172a24}._colorOption_sf7jc_456{width:24px;height:24px;padding:0;border:2px solid transparent;border-radius:var(--radius-sm);cursor:pointer;box-shadow:inset 0 0 0 1px #0f172a24}._colorOptionActive_sf7jc_466{border-color:color-mix(in srgb,var(--surface-panel) 78%,var(--brand-primary));box-shadow:inset 0 0 0 1px #0f172a24,0 0 0 2px color-mix(in srgb,var(--brand-primary) 24%,transparent)}._toolbarUtilities_sf7jc_473{margin-left:auto;display:inline-flex;flex-wrap:wrap;align-items:center;gap:8px}._toolbarGeometryFields_sf7jc_481{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}._toolbarGeometryField_sf7jc_481{display:inline-flex;align-items:center;gap:6px}._toolbarGeometryLabel_sf7jc_494{margin:0;text-transform:uppercase;white-space:nowrap}._toolbarGeometryInput_sf7jc_500{width:68px;text-align:right}._iconButton_sf7jc_505{flex-shrink:0}._primaryBtn_sf7jc_509,._ghostBtn_sf7jc_510,._payloadBtn_sf7jc_511,._backToTaskBtn_sf7jc_512{white-space:nowrap}._backToTaskBtn_sf7jc_512{padding:7px 10px;color:var(--text-secondary);font-size:.88rem;font-weight:600}._backToTaskBtn_sf7jc_512:hover,._backToTaskBtn_sf7jc_512:focus-visible{color:var(--text-primary)}._payloadBtn_sf7jc_511{font-weight:700}._canvasScroller_sf7jc_532{position:relative;flex:1;min-height:0;overflow:auto;display:block;scrollbar-gutter:stable;background:linear-gradient(45deg,color-mix(in srgb,var(--surface-inset) 66%,var(--border-default)) 25%,transparent 25%),linear-gradient(-45deg,color-mix(in srgb,var(--surface-inset) 66%,var(--border-default)) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,color-mix(in srgb,var(--surface-inset) 66%,var(--border-default)) 75%),linear-gradient(-45deg,transparent 75%,color-mix(in srgb,var(--surface-inset) 66%,var(--border-default)) 75%);background-size:22px 22px;background-position:0 0,0 11px,11px -11px,-11px 0}._canvasFrame_sf7jc_548{position:relative;display:block;width:max-content;min-width:100%}._canvasMedia_sf7jc_555{position:relative;display:block;flex-shrink:0;margin:0 auto;overflow:hidden}._canvasStatusOverlay_sf7jc_563{position:absolute;inset:0;z-index:2;display:flex;align-items:center;justify-content:center;padding:24px;background:color-mix(in srgb,var(--surface-scrim) 68%,transparent);pointer-events:none}._canvasStatusCard_sf7jc_575{display:inline-flex;align-items:center;gap:10px;padding:12px 16px;border:1px solid color-mix(in srgb,var(--border-strong) 44%,white);border-radius:14px;background:#0f172ac7;color:#fff;box-shadow:0 18px 30px #0f172a38;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}._canvasImage_sf7jc_588{display:block;width:100%;height:100%;-webkit-user-select:none;user-select:none}._overlay_sf7jc_595{position:absolute;inset:0;cursor:crosshair}._overlaySelect_sf7jc_601{cursor:default}._overlayPan_sf7jc_605{cursor:grab}._overlaySvg_sf7jc_609{position:absolute;inset:0;display:block;width:100%;height:100%;pointer-events:none}._overlayHitLayer_sf7jc_618{position:absolute;inset:0;display:block;width:100%;height:100%;pointer-events:auto}._arrowHitArea_sf7jc_627{stroke:transparent;stroke-width:24;pointer-events:stroke;cursor:move}._canvasHandleHit_sf7jc_634{fill:transparent;pointer-events:all;cursor:grab;outline:none}._canvasResizeHandleHit_sf7jc_641{cursor:nwse-resize}._canvasHandleVisible_sf7jc_645{--canvas-handle-fill: var(--brand-primary);--canvas-handle-outline: var(--surface-elevated);--canvas-handle-shadow: 0 6px 12px rgba(15, 23, 42, .18);fill:var(--canvas-handle-fill);stroke:var(--canvas-handle-outline);stroke-width:3;vector-effect:non-scaling-stroke;filter:drop-shadow(var(--canvas-handle-shadow));pointer-events:none}._canvasHandleHit_sf7jc_634:focus-visible+._canvasHandleVisible_sf7jc_645{stroke:color-mix(in srgb,var(--brand-primary) 72%,var(--surface-elevated));stroke-width:4}._pin_sf7jc_662,._note_sf7jc_663{position:absolute;transform:translate(-50%,-50%);display:inline-flex;align-items:center;justify-content:center;min-width:28px;min-height:28px;border:2px solid var(--surface-elevated);border-radius:999px;box-shadow:0 8px 18px #0f172a38;color:#fff;font-size:.74rem;font-weight:800;pointer-events:auto}._annotationNumberBadge_sf7jc_680{position:absolute;display:inline-flex;align-items:center;justify-content:center;min-width:28px;min-height:28px;padding:0 8px;border:2px solid var(--surface-elevated);border-radius:999px;box-shadow:0 8px 18px #0f172a38;color:#fff;font-size:.74rem;font-weight:800;line-height:1;pointer-events:none;z-index:2}._note_sf7jc_663{min-width:38px;padding:0 10px;border-radius:999px}._box_sf7jc_705{position:absolute;border:2px solid;border-radius:var(--radius-sm);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--surface-elevated) 34%,transparent);background:color-mix(in srgb,var(--surface-elevated) 10%,transparent);pointer-events:auto}._boxNumberBadge_sf7jc_714{top:0;left:0;transform:translate(-28%,-36%)}._arrowNumberBadge_sf7jc_720{transform:translate(-50%,-50%)}._boxSurface_sf7jc_724{position:absolute;inset:0;border:0;background:transparent;cursor:move}._boxSurface_sf7jc_724:focus-visible{outline:2px solid color-mix(in srgb,var(--brand-primary) 34%,var(--surface-elevated));outline-offset:2px}._selected_sf7jc_737{outline:3px solid color-mix(in srgb,var(--surface-elevated) 92%,transparent);outline-offset:1px}._fieldGroup_sf7jc_742{display:flex;flex-direction:column;gap:8px;padding:14px 16px}._detailPanel_sf7jc_23 ._fieldGroup_sf7jc_742{padding-top:12px;padding-bottom:12px}._textInput_sf7jc_754,._textArea_sf7jc_755,._select_sf7jc_737{width:100%;box-sizing:border-box;font:inherit}._sessionTitleInput_sf7jc_762{font-size:1rem;font-weight:600}._sessionInstructionField_sf7jc_767{min-height:112px}._textArea_sf7jc_755{min-height:110px;resize:vertical}._detailEmpty_sf7jc_776,._emptyState_sf7jc_777,._errorState_sf7jc_778{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;min-height:240px;padding:24px;text-align:center}._payloadModalBody_sf7jc_789{display:flex;flex-direction:column;gap:12px;min-height:0}._payloadModalToolbar_sf7jc_796{display:flex;align-items:center;justify-content:space-between;gap:12px}._payloadViewToggle_sf7jc_803,._payloadModalActions_sf7jc_804{display:inline-flex;flex-wrap:wrap;align-items:center;gap:8px}._payloadModalPreview_sf7jc_811{margin:0;min-height:360px;max-height:min(70vh,720px);padding:16px;overflow:auto;scrollbar-gutter:stable;white-space:pre-wrap;word-break:break-word;font-size:.78rem;line-height:1.55;border-radius:14px;color:var(--text-body)}._statusBar_sf7jc_826{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-top:1px solid var(--border-default);color:var(--text-secondary);font-size:.82rem;background:color-mix(in srgb,var(--surface-panel) 88%,var(--surface-inset))}._annotationSummary_sf7jc_838{font-size:.82rem;line-height:1.5}@media(max-width:1180px){._shell_sf7jc_1{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(320px,1fr) auto;gap:12px;padding:12px}._sessionContextBar_sf7jc_29,._panelHeader_sf7jc_88,._toolRail_sf7jc_334,._statusBar_sf7jc_826,._payloadModalToolbar_sf7jc_796,._markerHelpHeader_sf7jc_163{flex-wrap:wrap}._sessionContextRight_sf7jc_41,._sessionContextLeft_sf7jc_40,._canvasHeading_sf7jc_105{min-width:0}._sessionContextRight_sf7jc_41{width:100%;justify-content:flex-start}._toolbarActions_sf7jc_407{width:100%;margin-left:0}._toolbarUtilities_sf7jc_473{margin-left:0}._toolGroup_sf7jc_353{max-width:100%}._canvasWorkspace_sf7jc_74{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr)}._toolbarViewportCluster_sf7jc_371{margin-left:0}._canvasToolRail_sf7jc_343{flex-direction:row;justify-content:flex-start;overflow-x:auto;padding:0;border-right:0;border-bottom:1px solid var(--border-default)}._statusBar_sf7jc_826{align-items:flex-start}._detailEmpty_sf7jc_776,._emptyState_sf7jc_777,._errorState_sf7jc_778{min-height:180px}}
|