@taskforcehq/taskforce 0.3.314 → 0.3.316

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/components/features/AnnotatedAttachmentWorkspace.js +51 -11
  2. package/dist/core/PlanEntitlementService.js +6 -1
  3. package/dist/core/Taskforce.d.ts +2 -0
  4. package/dist/core/Taskforce.js +16 -2
  5. package/dist/hooks/useTaskforce.js +13 -6
  6. package/dist/mcp/canonicalAssetHelpers.js +33 -19
  7. package/dist/mcp/runtime.js +39 -9
  8. package/dist/mcp/taskAttachmentHelpers.js +29 -7
  9. package/dist/mcp/toolCatalog.d.ts +7 -0
  10. package/dist/server/index.js +1 -1
  11. package/dist/server/routes/documents.js +2 -1
  12. package/dist/server/routes.js +10 -6
  13. package/dist/storage/documentIntegrity.js +1 -6
  14. package/dist/storage/documentPurge.js +11 -12
  15. package/dist/sync/workspaceRepair.js +11 -9
  16. package/dist/ui/.well-known/mcp-registry-auth +1 -0
  17. package/dist/ui/assets/{AgentsModule-jZnjzh-I.js → AgentsModule-CNBWCIXk.js} +1 -1
  18. package/dist/ui/assets/AnnotatedAttachmentWorkspace-9qxB0e6A.js +3 -0
  19. package/dist/ui/assets/{ContextAttachmentManager-OnjnW5fC.js → ContextAttachmentManager-CRyuFYlg.js} +1 -1
  20. package/dist/ui/assets/{DocumentWorkspace-Btxo9quS.js → DocumentWorkspace-Dx7wb9NF.js} +1 -1
  21. package/dist/ui/assets/{EntityActivityTimeline-C8BjU7yO.js → EntityActivityTimeline-Cmal2OrJ.js} +1 -1
  22. package/dist/ui/assets/{InitiativesModule-DYBB7WD-.js → InitiativesModule-wzbYPQQL.js} +1 -1
  23. package/dist/ui/assets/{PlansPage-p0gvF4qR.js → PlansPage-BAnSfbTf.js} +1 -1
  24. package/dist/ui/assets/{TaskContextUpload-EGeOdrPm.js → TaskContextUpload-DENyMyUk.js} +1 -1
  25. package/dist/ui/assets/{TaskSettings-CJpF1CMF.js → TaskSettings-DASuVwpY.js} +1 -1
  26. package/dist/ui/assets/{WorkflowsModule-IQOmxPXX.js → WorkflowsModule-DJMEA_yt.js} +1 -1
  27. package/dist/ui/assets/documentReferences-BhNx80zO.js +1 -0
  28. package/dist/ui/assets/{index-DUt7ifSO.js → index-CWg2olz9.js} +5 -5
  29. package/dist/ui/index.html +1 -1
  30. package/dist/utils/pathContainment.d.ts +7 -0
  31. package/dist/utils/pathContainment.js +53 -0
  32. package/dist/utils/pathSafety.d.ts +6 -0
  33. package/dist/utils/pathSafety.js +73 -0
  34. package/package.json +3 -1
  35. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CGFrFF0m.js +0 -3
  36. package/dist/ui/assets/documentReferences-DXW5aT08.js +0 -1
@@ -8,7 +8,7 @@
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-DUt7ifSO.js"></script>
11
+ <script type="module" crossorigin src="/taskforce/assets/index-CWg2olz9.js"></script>
12
12
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-react-CKJs5o3c.js">
13
13
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-icons-CLnehDTw.js">
14
14
  <link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-markdown-BUxTU7dS.js">
@@ -0,0 +1,7 @@
1
+ export declare function isPathWithinRoot(candidatePath: string, rootDir: string): boolean;
2
+ export declare function resolvePathWithinRoot(rootDir: string, candidatePath: string, options?: {
3
+ allowMissingLeaf?: boolean;
4
+ }): string | null;
5
+ export declare function relativePathWithinRoot(rootDir: string, candidatePath: string, options?: {
6
+ allowMissingLeaf?: boolean;
7
+ }): string | null;
@@ -0,0 +1,53 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ function resolveRealPath(candidatePath, allowMissingLeaf = false) {
4
+ const resolved = path.resolve(candidatePath);
5
+ if (!allowMissingLeaf) {
6
+ try {
7
+ return fs.realpathSync.native(resolved);
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
13
+ const missingSegments = [];
14
+ let current = resolved;
15
+ while (true) {
16
+ try {
17
+ const real = fs.realpathSync.native(current);
18
+ return path.join(real, ...missingSegments);
19
+ }
20
+ catch {
21
+ const parent = path.dirname(current);
22
+ if (parent === current)
23
+ return null;
24
+ missingSegments.unshift(path.basename(current));
25
+ current = parent;
26
+ }
27
+ }
28
+ }
29
+ export function isPathWithinRoot(candidatePath, rootDir) {
30
+ const root = path.resolve(rootDir);
31
+ const candidate = path.resolve(candidatePath);
32
+ const relative = path.relative(root, candidate);
33
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
34
+ }
35
+ export function resolvePathWithinRoot(rootDir, candidatePath, options) {
36
+ const rootResolved = path.resolve(rootDir);
37
+ const candidateResolved = path.isAbsolute(candidatePath)
38
+ ? path.resolve(candidatePath)
39
+ : path.resolve(rootResolved, candidatePath);
40
+ if (!isPathWithinRoot(candidateResolved, rootResolved))
41
+ return null;
42
+ const rootReal = resolveRealPath(rootResolved) || rootResolved;
43
+ const candidateReal = resolveRealPath(candidateResolved, options?.allowMissingLeaf === true);
44
+ if (!candidateReal)
45
+ return null;
46
+ return isPathWithinRoot(candidateReal, rootReal) ? candidateResolved : null;
47
+ }
48
+ export function relativePathWithinRoot(rootDir, candidatePath, options) {
49
+ const resolved = resolvePathWithinRoot(rootDir, candidatePath, options);
50
+ if (!resolved)
51
+ return null;
52
+ return path.relative(path.resolve(rootDir), resolved).split(path.sep).join('/');
53
+ }
@@ -0,0 +1,6 @@
1
+ export declare function isPathInsideRoot(candidatePath: string, rootDir: string): boolean;
2
+ export declare function resolvePathInsideRoot(candidatePath: string, rootDir: string): string | null;
3
+ export declare function normalizeRelativeStorageKey(value: unknown): string | null;
4
+ export declare function resolveStorageKeyPathInsideRoot(basePath: string, storageKeyRaw: unknown): string | null;
5
+ export declare function resolveExistingStorageKeyFilePathInsideRoot(basePath: string, storageKeyRaw: unknown): string | null;
6
+ export declare function resolveStorageKeyWritePathInsideRoot(basePath: string, storageKeyRaw: unknown): string | null;
@@ -0,0 +1,73 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ export function isPathInsideRoot(candidatePath, rootDir) {
4
+ const resolvedRoot = path.resolve(rootDir);
5
+ const resolvedCandidate = path.resolve(candidatePath);
6
+ const relative = path.relative(resolvedRoot, resolvedCandidate);
7
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
8
+ }
9
+ export function resolvePathInsideRoot(candidatePath, rootDir) {
10
+ const resolvedCandidate = path.resolve(candidatePath);
11
+ return isPathInsideRoot(resolvedCandidate, rootDir) ? resolvedCandidate : null;
12
+ }
13
+ export function normalizeRelativeStorageKey(value) {
14
+ const normalized = String(value || '').trim().replace(/\\/g, '/');
15
+ if (!normalized || normalized.includes('\0'))
16
+ return null;
17
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(normalized))
18
+ return null;
19
+ if (path.posix.isAbsolute(normalized))
20
+ return null;
21
+ const segments = normalized.split('/');
22
+ if (segments.some((segment) => segment === '..' || segment === '.'))
23
+ return null;
24
+ return normalized;
25
+ }
26
+ export function resolveStorageKeyPathInsideRoot(basePath, storageKeyRaw) {
27
+ const storageKey = normalizeRelativeStorageKey(storageKeyRaw);
28
+ if (!storageKey)
29
+ return null;
30
+ return resolvePathInsideRoot(path.join(basePath, storageKey), basePath);
31
+ }
32
+ export function resolveExistingStorageKeyFilePathInsideRoot(basePath, storageKeyRaw) {
33
+ const resolvedPath = resolveStorageKeyPathInsideRoot(basePath, storageKeyRaw);
34
+ if (!resolvedPath)
35
+ return null;
36
+ try {
37
+ if (!fs.existsSync(resolvedPath) || !fs.statSync(resolvedPath).isFile())
38
+ return null;
39
+ const storageRoot = fs.existsSync(basePath) ? fs.realpathSync(basePath) : path.resolve(basePath);
40
+ const realPath = fs.realpathSync(resolvedPath);
41
+ return isPathInsideRoot(realPath, storageRoot) ? realPath : null;
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ export function resolveStorageKeyWritePathInsideRoot(basePath, storageKeyRaw) {
48
+ const resolvedPath = resolveStorageKeyPathInsideRoot(basePath, storageKeyRaw);
49
+ if (!resolvedPath)
50
+ return null;
51
+ try {
52
+ const resolvedRoot = path.resolve(basePath);
53
+ const storageRoot = fs.existsSync(basePath) ? fs.realpathSync(basePath) : resolvedRoot;
54
+ if (fs.existsSync(resolvedPath)) {
55
+ if (!fs.statSync(resolvedPath).isFile())
56
+ return null;
57
+ const realPath = fs.realpathSync(resolvedPath);
58
+ return isPathInsideRoot(realPath, storageRoot) ? resolvedPath : null;
59
+ }
60
+ let existingParent = path.dirname(resolvedPath);
61
+ while (!fs.existsSync(existingParent)) {
62
+ const nextParent = path.dirname(existingParent);
63
+ if (nextParent === existingParent || !isPathInsideRoot(nextParent, resolvedRoot))
64
+ return null;
65
+ existingParent = nextParent;
66
+ }
67
+ const realParent = fs.realpathSync(existingParent);
68
+ return isPathInsideRoot(realParent, storageRoot) ? resolvedPath : null;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskforcehq/taskforce",
3
- "version": "0.3.314",
3
+ "version": "0.3.316",
4
4
  "description": "Shared task and planning workspace for humans collaborating with AI (public beta)",
5
5
  "author": "Taskforce HQ <hello@taskforcehq.ai> (https://taskforcehq.ai)",
6
6
  "license": "MIT",
@@ -64,6 +64,8 @@
64
64
  "hooks:install": "git config core.hooksPath .githooks",
65
65
  "build": "shx rm -rf dist && tsc && shx cp src/Taskforce.module.css dist/Taskforce.module.css && shx mkdir -p dist/styles && shx cp src/styles/fonts.css dist/styles/fonts.css && shx mkdir -p dist/assets/fonts && shx cp src/assets/fonts/*.woff2 dist/assets/fonts/ && shx mkdir -p dist/resources && shx cp -r src/resources/* dist/resources/ && shx rm -f dist/resources/templates/workflows/release.yaml && shx mkdir -p dist/public && shx cp -r src/assets/images/* dist/public/ && shx cp -r src/assets/favicon/* dist/public/ && shx chmod +x dist/cli.js && npm run build:ui",
66
66
  "mcp": "node dist/mcp/server.js",
67
+ "gen:mcp-contract": "node --import tsx scripts/generate-mcp-contract.ts",
68
+ "check:mcp-contract": "node --import tsx scripts/generate-mcp-contract.ts --check",
67
69
  "gen:workflows": "node scripts/generate-workflow-templates.mjs",
68
70
  "check:workflows": "node scripts/generate-workflow-templates.mjs --check",
69
71
  "db:export:postgres": "node scripts/export-sqlite-to-postgres.mjs",
@@ -1,3 +0,0 @@
1
- import{j as t,r as s,R as ta}from"./vendor-react-CKJs5o3c.js";import{R as An,f as ge,e as $t,g as Mn,t as Da,M as aa}from"./index-DUt7ifSO.js";import{a7 as za,x as En,w as Ln,p as Fn,P as Hn,c as na,T as Ua,r as On,h as Ga,an as Dn,R as zn,ao as Un,ap as Gn,aq as Yn,ar as oa,C as ia,as as Wn,at as Ja,au as Za,av as Qa,aw as en,g as Kn,f as Xn,b as la}from"./vendor-icons-CLnehDTw.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";function Vn({copied:r,disabled:l=!1,label:f,onClick:p,title:b="Copy image reference",ariaLabel:x,className:L=""}){return t.jsx(An,{copied:r,disabled:l,label:"",onClick:p,title:b,ariaLabel:x,className:L,children:f})}const qn="_shell_17mlo_1",Jn="_shellWithImageTray_17mlo_18",Zn="_shellImageTrayOpen_17mlo_22",Qn="_imageTrayPanel_17mlo_26",es="_imageTrayPanelOpen_17mlo_50",ts="_imageTrayHeader_17mlo_58",as="_imageTrayTitle_17mlo_73",ns="_imageTraySearch_17mlo_85",ss="_imageTraySearchIcon_17mlo_91",rs="_imageTraySearchInput_17mlo_100",os="_imageTrayList_17mlo_117",is="_imageTrayState_17mlo_129",ls="_imageTrayStateError_17mlo_130",cs="_imageTrayItem_17mlo_142",ds="_imageTrayItemActive_17mlo_164",us="_imageTrayThumb_17mlo_174",ms="_imageTrayItemBody_17mlo_191",fs="_imageTrayItemTitle_17mlo_199",hs="_imageTrayItemTask_17mlo_200",ps="_imageTrayItemMetaDetails_17mlo_201",gs="_imageTrayItemMeta_17mlo_201",ys="_imageTrayItemReference_17mlo_239",bs="_panel_17mlo_246",xs="_sessionPanel_17mlo_252",vs="_detailPanel_17mlo_253",_s="_sessionContextBar_17mlo_259",Is="_sessionContextLeft_17mlo_270",ks="_sessionContextRight_17mlo_271",ws="_sessionContextLabel_17mlo_286",Ss="_sessionContextSpacer_17mlo_292",Ns="_canvasPanel_17mlo_297",Cs="_canvasWorkspace_17mlo_304",js="_canvasMain_17mlo_311",Ts="_panelHeader_17mlo_318",$s="_panelHeaderText_17mlo_327",Rs="_panelTitle_17mlo_331",Ps="_canvasHeading_17mlo_335",Bs="_sessionActions_17mlo_339",As="_sessionList_17mlo_347",Ms="_annotationList_17mlo_348",Es="_markerHelpModalBody_17mlo_363",Ls="_openImageModalBody_17mlo_369",Fs="_openImageField_17mlo_375",Hs="_openImageActions_17mlo_379",Os="_markerHelpItem_17mlo_385",Ds="_markerHelpHeader_17mlo_393",zs="_markerHelpExample_17mlo_405",Us="_sessionCard_17mlo_409",Gs="_annotationCard_17mlo_410",Ys="_sessionEmptyState_17mlo_427",Ws="_sessionCardButton_17mlo_434",Ks="_annotationCardButton_17mlo_444",Xs="_sessionCardBody_17mlo_454",Vs="_sessionCardActive_17mlo_460",qs="_annotationCardActive_17mlo_461",Js="_annotationMeta_17mlo_471",Zs="_annotationInstructionPreview_17mlo_478",Qs="_annotationPreviewFooter_17mlo_486",er="_annotationInstructionEditor_17mlo_493",tr="_annotationTypeField_17mlo_499",ar="_annotationInstructionButton_17mlo_505",nr="_annotationInstructionField_17mlo_514",sr="_sessionMeta_17mlo_518",rr="_sessionTitle_17mlo_525",or="_annotationTitle_17mlo_526",ir="_sessionTimestamp_17mlo_532",lr="_annotationKind_17mlo_533",cr="_annotationInstructionTypeIcon_17mlo_537",dr="_sessionInstructionPreview_17mlo_543",ur="_sessionInstructionEditor_17mlo_551",mr="_sessionCardFooter_17mlo_557",fr="_toolRail_17mlo_564",hr="_canvasToolRail_17mlo_573",pr="_toolbarCluster_17mlo_594",gr="_toolbarViewportCluster_17mlo_601",yr="_toolbarSeparator_17mlo_605",br="_toolBtn_17mlo_611",xr="_toolRailButton_17mlo_615",vr="_toolbarButton_17mlo_625",_r="_toolBtnActive_17mlo_630",Ir="_toolbarActions_17mlo_637",kr="_toolbarSelectionActions_17mlo_646",wr="_toolbarColorPicker_17mlo_654",Sr="_colorPickerButton_17mlo_658",Nr="_colorPickerSwatch_17mlo_663",Cr="_colorPickerPopover_17mlo_671",jr="_colorOption_17mlo_686",Tr="_colorOptionActive_17mlo_696",$r="_toolbarUtilities_17mlo_703",Rr="_toolbarGeometryFields_17mlo_711",Pr="_toolbarGeometryField_17mlo_711",Br="_toolbarGeometryLabel_17mlo_724",Ar="_toolbarGeometryInput_17mlo_730",Mr="_iconButton_17mlo_735",Er="_ghostBtn_17mlo_740",Lr="_payloadBtn_17mlo_741",Fr="_backToTaskBtn_17mlo_742",Hr="_canvasScroller_17mlo_762",Or="_canvasFrame_17mlo_778",Dr="_canvasMedia_17mlo_785",zr="_canvasStatusOverlay_17mlo_793",Ur="_canvasStatusCard_17mlo_805",Gr="_canvasImage_17mlo_818",Yr="_overlay_17mlo_825",Wr="_overlaySelect_17mlo_831",Kr="_overlayPan_17mlo_835",Xr="_overlaySvg_17mlo_839",Vr="_overlayHitLayer_17mlo_848",qr="_arrowHitArea_17mlo_857",Jr="_canvasHandleHit_17mlo_864",Zr="_canvasResizeHandleHit_17mlo_871",Qr="_canvasHandleVisible_17mlo_875",eo="_pin_17mlo_892",to="_note_17mlo_893",ao="_annotationNumberBadge_17mlo_910",no="_box_17mlo_935",so="_boxNumberBadge_17mlo_944",ro="_arrowNumberBadge_17mlo_950",oo="_boxSurface_17mlo_954",io="_selected_17mlo_967",lo="_textInput_17mlo_984",co="_textArea_17mlo_985",uo="_select_17mlo_967",mo="_sessionTitleInput_17mlo_992",fo="_sessionInstructionField_17mlo_997",ho="_detailEmpty_17mlo_1006",po="_emptyState_17mlo_1007",go="_payloadModalBody_17mlo_1019",yo="_payloadModalToolbar_17mlo_1026",bo="_payloadViewToggle_17mlo_1033",xo="_payloadModalActions_17mlo_1034",vo="_payloadModalPreview_17mlo_1041",_o="_statusBar_17mlo_1056",Io="_annotationSummary_17mlo_1068",a={shell:qn,shellWithImageTray:Jn,shellImageTrayOpen:Zn,imageTrayPanel:Qn,imageTrayPanelOpen:es,imageTrayHeader:ts,imageTrayTitle:as,imageTraySearch:ns,imageTraySearchIcon:ss,imageTraySearchInput:rs,imageTrayList:os,imageTrayState:is,imageTrayStateError:ls,imageTrayItem:cs,imageTrayItemActive:ds,imageTrayThumb:us,imageTrayItemBody:ms,imageTrayItemTitle:fs,imageTrayItemTask:hs,imageTrayItemMetaDetails:ps,imageTrayItemMeta:gs,imageTrayItemReference:ys,panel:bs,sessionPanel:xs,detailPanel:vs,sessionContextBar:_s,sessionContextLeft:Is,sessionContextRight:ks,sessionContextLabel:ws,sessionContextSpacer:Ss,canvasPanel:Ns,canvasWorkspace:Cs,canvasMain:js,panelHeader:Ts,panelHeaderText:$s,panelTitle:Rs,canvasHeading:Ps,sessionActions:Bs,sessionList:As,annotationList:Ms,markerHelpModalBody:Es,openImageModalBody:Ls,openImageField:Fs,openImageActions:Hs,markerHelpItem:Os,markerHelpHeader:Ds,markerHelpExample:zs,sessionCard:Us,annotationCard:Gs,sessionEmptyState:Ys,sessionCardButton:Ws,annotationCardButton:Ks,sessionCardBody:Xs,sessionCardActive:Vs,annotationCardActive:qs,annotationMeta:Js,annotationInstructionPreview:Zs,annotationPreviewFooter:Qs,annotationInstructionEditor:er,annotationTypeField:tr,annotationInstructionButton:ar,annotationInstructionField:nr,sessionMeta:sr,sessionTitle:rr,annotationTitle:or,sessionTimestamp:ir,annotationKind:lr,annotationInstructionTypeIcon:cr,sessionInstructionPreview:dr,sessionInstructionEditor:ur,sessionCardFooter:mr,toolRail:fr,canvasToolRail:hr,toolbarCluster:pr,toolbarViewportCluster:gr,toolbarSeparator:yr,toolBtn:br,toolRailButton:xr,toolbarButton:vr,toolBtnActive:_r,toolbarActions:Ir,toolbarSelectionActions:kr,toolbarColorPicker:wr,colorPickerButton:Sr,colorPickerSwatch:Nr,colorPickerPopover:Cr,colorOption:jr,colorOptionActive:Tr,toolbarUtilities:$r,toolbarGeometryFields:Rr,toolbarGeometryField:Pr,toolbarGeometryLabel:Br,toolbarGeometryInput:Ar,iconButton:Mr,ghostBtn:Er,payloadBtn:Lr,backToTaskBtn:Fr,canvasScroller:Hr,canvasFrame:Or,canvasMedia:Dr,canvasStatusOverlay:zr,canvasStatusCard:Ur,canvasImage:Gr,overlay:Yr,overlaySelect:Wr,overlayPan:Kr,overlaySvg:Xr,overlayHitLayer:Vr,arrowHitArea:qr,canvasHandleHit:Jr,canvasResizeHandleHit:Zr,canvasHandleVisible:Qr,pin:eo,note:to,annotationNumberBadge:ao,box:no,boxNumberBadge:so,arrowNumberBadge:ro,boxSurface:oo,selected:io,textInput:lo,textArea:co,select:uo,sessionTitleInput:mo,sessionInstructionField:fo,detailEmpty:ho,emptyState:po,payloadModalBody:go,payloadModalToolbar:yo,payloadViewToggle:bo,payloadModalActions:xo,payloadModalPreview:vo,statusBar:_o,annotationSummary:Io},Ya=[{value:"review",label:"Review"},{value:"change",label:"Change"},{value:"question",label:"Question"}],Wa=[{value:"select",label:"Select",icon:Wn},{value:"pin",label:"Pin",icon:Ja},{value:"box",label:"Box",icon:Za},{value:"arrow",label:"Arrow",icon:Qa},{value:"text-note",label:"Note",icon:en}],ko={pin:Ja,box:Za,arrow:Qa,"text-note":en},wo={pin:"Pin",box:"Box",arrow:"Arrow","text-note":"Note"},So={review:oa,change:la,question:ia,issue:ia,idea:oa},Rt={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."'}},lt={question:"#0f766e",change:"#2563eb",issue:"#dc2626",idea:"#d97706",review:"#7c3aed"},No=["#7c3aed","#2563eb","#0f766e","#dc2626","#d97706","#111827"],ce="review";function Ye(r){const l=String(r.displayName||"").trim();return l?`${l} review`:"Annotated session"}function tn(){return`annotation-${Math.random().toString(36).slice(2,10)}`}function I(r){return!Number.isFinite(r)||r<=0?0:r>=1?1:r}function Ne(r){return I(Math.max(.02,r))}function Ge(r){return r?[String(r.taskId||"").trim(),String(r.assetId||"").trim(),String(r.path||"").trim()].join("::"):""}function Co(r){if(!(r instanceof HTMLElement))return!1;const l=r.tagName.toLowerCase();return r.isContentEditable?!0:l==="input"||l==="textarea"||l==="select"}function Se(r){return r.map((l,f)=>({...l,order:f}))}function sa(r){if(!r)return"Unsaved";const l=new Date(r);return Number.isNaN(l.getTime())?"Unsaved":l.toLocaleString()}function jo(r){const l=typeof r=="number"&&Number.isFinite(r)?Math.max(0,r):0;return l<1024?`${l}B`:l<1024*1024?`${(l/1024).toFixed(1)}KB`:`${(l/(1024*1024)).toFixed(1)}MB`}function To(r){if(!r)return"";const l=new Date(r);if(Number.isNaN(l.getTime()))return"";const p=new Date().getTime()-l.getTime(),b=Math.floor(p/(1e3*60*60*24));return b<=0?"Today":b===1?"Yesterday":b<7?`${b}d ago`:b<30?`${Math.floor(b/7)}w ago`:l.toLocaleDateString()}function Pt(r){const l=String(r.createdByActor?.label||"").trim();return l||null}function $o(r){const l=String(r||"").trim().replace(/\s+/g," ");return l?l.length>110?`${l.slice(0,107)}...`:l:""}function Ka(r,l,f,p=lt[ce]){const b={id:tn(),order:0,instruction:"",markerType:ce,color:p};if(r==="pin")return{...b,kind:r,x:l.x,y:l.y};if(r==="text-note")return{...b,kind:r,x:l.x,y:l.y};if(r==="box"){const L=f||l;return{...b,kind:r,x:I(Math.min(l.x,L.x)),y:I(Math.min(l.y,L.y)),width:Ne(Math.abs(L.x-l.x)),height:Ne(Math.abs(L.y-l.y))}}const x=f||l;return{...b,kind:"arrow",x:l.x,y:l.y,x2:x.x,y2:x.y}}function ra(r){return r.color?r.color:lt[r.markerType||ce]}function Ro(r,l){const f=Math.max(l.width,1),p=Math.max(l.height,1),b=r.x*f,x=r.y*p,L=r.x2*f,ne=r.y2*p,se=L-b,ct=ne-x,Ce=Math.hypot(se,ct)||1,We=se/Ce,je=ct/Ce,G=Math.max(10,Math.min(16,Ce-2)),ye=G*.62,Te=L-We*G,$=ne-je*G,dt=-je,be=We;return{shaftX1:b,shaftY1:x,shaftX2:Te,shaftY2:$,headPoints:[`${L},${ne}`,`${Te+dt*ye},${$+be*ye}`,`${Te-dt*ye},${$-be*ye}`].join(" ")}}function Po(r,l){return{...r,markerType:l,color:r.color||lt[l]}}function Bo(r,l){return{...r,id:tn(),order:l}}function Ao(r,l){const f=String(r||"").trim()||(l?Ye(l):"Annotated session");return/\bcopy$/i.test(f)?`${f} 2`:`${f} copy`}function Mo(r,l,f){if(l===f||l<0||f<0||l>=r.length||f>=r.length)return r;const p=[...r],[b]=p.splice(l,1);return b?(p.splice(f,0,b),Se(p)):r}function ae(r){return String(Math.round(I(r)*1e3)/10)}function Eo(r){const l=Number.parseFloat(r);return Number.isFinite(l)?I(l/100):null}function Lo(r,l,f){return r.kind==="pin"||r.kind==="text-note"?l==="x"||l==="y"?{...r,[l]:I(f)}:r:r.kind==="box"?l==="x"||l==="y"?{...r,[l]:I(f)}:l==="width"||l==="height"?{...r,[l]:Ne(f)}:r:l==="x"||l==="y"||l==="x2"||l==="y2"?{...r,[l]:I(f)}:r}function Fo(r){return r.kind==="pin"||r.kind==="text-note"?[{key:"x",label:"X",value:ae(r.x)},{key:"y",label:"Y",value:ae(r.y)}]:r.kind==="box"?[{key:"x",label:"X",value:ae(r.x)},{key:"y",label:"Y",value:ae(r.y)},{key:"width",label:"Width",value:ae(r.width)},{key:"height",label:"Height",value:ae(r.height)}]:[{key:"x",label:"Start X",value:ae(r.x)},{key:"y",label:"Start Y",value:ae(r.y)},{key:"x2",label:"End X",value:ae(r.x2)},{key:"y2",label:"End Y",value:ae(r.y2)}]}function Ho(r){if(!r)return null;const l=Math.round(r.x*100),f=Math.round(r.y*100),p=Math.round(r.width*100),b=Math.round(r.height*100);return`crop ${l}%, ${f}% size ${p}% x ${b}%`}function Oo(r){return Number.isFinite(r)?Math.min(4,Math.max(.25,Number(r.toFixed(2)))):1}function Xa(r){const l=[`Annotated attachment: ${r.title||r.image.displayName}`,`Image: ${r.image.displayName}`,`Image Reference: ${r.image.referenceLabel||r.image.assetId}`,`Task ID: ${r.taskId}`,r.globalInstruction?`Global instruction: ${r.globalInstruction}`:"Global instruction: None provided.","Markers:"];return r.annotations.length===0?(l.push("0. No markers."),l.join(`
2
- `)):(r.annotations.forEach((f,p)=>{const b=f.markerType||ce,x=Ho(f.cropHint);l.push(`${p+1}. ${f.kind} (${b})`),l.push(`Instruction: ${f.instruction||"No marker instruction."}`),x&&l.push(`Region: ${x}`)}),l.join(`
3
- `))}function Bt(r){return{title:r.title,globalInstruction:r.globalInstruction,annotations:Se(r.annotations)}}function Va(r){return JSON.stringify(Bt(r))}function qa(r){const l=JSON.parse(r);return Bt({title:String(l?.title||""),globalInstruction:String(l?.globalInstruction||""),annotations:Array.isArray(l?.annotations)?l.annotations:[]})}function Do(r,l){return Bt({title:r?.title||l,globalInstruction:r?.globalInstruction||"",annotations:r?.annotations||[]})}function Vo({runtimeMode:r="local",apiBaseUrl:l="",cloudAuthBaseUrl:f="",workspaceId:p="default",sessionLoadReady:b=!0,requestedTarget:x=null,requestedSessionId:L=null,requestedOpenVersion:ne=0,imageTrayOpen:se,onCloseImageTray:ct,resolveTaskReferenceLabel:Ce,resolveImageReferenceLabel:We,onRequestedTargetHandled:je,onOpenTarget:G,onContextChange:ye,onBackToTask:Te}){const $=r==="cloud"&&(f||l)||"",[dt,be]=s.useState(x),[ut,$e]=s.useState([]),[v,Re]=s.useState(null),[At,Ke]=s.useState(!1),[Pe,Xe]=s.useState(""),[Be,Ve]=s.useState(""),[j,re]=s.useState([]),[S,F]=s.useState(null),[mt,ca]=s.useState(lt[ce]),[ft,ht]=s.useState(!1),[T,Ae]=s.useState("select"),[an,xe]=s.useState(!1),[pt,da]=s.useState(!1),[V,q]=s.useState(!1),[ua,g]=s.useState(null),[Y,R]=s.useState("saved"),[zo,D]=s.useState(null),[gt,ma]=s.useState(!1),[oe,Mt]=s.useState(null),[de,Et]=s.useState(!1),[nn,Lt]=s.useState(!1),[Ft,fa]=s.useState("json"),[yt,Ht]=s.useState(!1),[bt,Ot]=s.useState(!1),[sn,ha]=s.useState(!1),[rn,xt]=s.useState(!1),[Dt,vt]=s.useState(""),[qe,pa]=s.useState(!1),[zt,on]=s.useState([]),[_t,ln]=s.useState(""),[cn,ga]=s.useState(!1),[ya,ba]=s.useState(null),[ue,Je]=s.useState(!1),[Ut,Ze]=s.useState(!1),[P,Gt]=s.useState(1),[W,Qe]=s.useState(!1),[It,xa]=s.useState(!1),[kt,et]=s.useState({width:0,height:0}),va=s.useRef(null),Me=s.useRef(null),Yt=s.useRef(null),_a=s.useRef(null),me=s.useRef(L),ie=s.useRef(""),wt=s.useRef(0),Wt=s.useRef(""),ve=s.useRef(0),St=s.useRef(null),Ia=s.useRef(0),fe=s.useRef(!1),tt=s.useRef(null),Kt=s.useRef(null),_e=s.useRef(null),H=s.useRef(""),M=s.useRef(""),Nt=s.useRef(null),J=s.useRef(null),Ee=s.useRef(!1),Ct=s.useRef(null),at=s.useRef(null),nt=s.useRef(null),Ie=s.useRef(null),Le=s.useRef(null),Fe=s.useRef(null),He=s.useRef(null),he=s.useRef(null),Z=s.useRef(null),ka=s.useRef(null),wa=s.useRef(null),B=s.useMemo(()=>ut.find(e=>e.id===v)||null,[v,ut]),Q=s.useMemo(()=>`workspaceId=${encodeURIComponent(String(p||"default").trim()||"default")}`,[p]),A=s.useMemo(()=>({"x-taskforce-workspace-id":String(p||"default").trim()||"default"}),[p]),z=s.useMemo(()=>j.find(e=>e.id===S)||null,[j,S]),ee=s.useMemo(()=>Ge(x),[x]),le=typeof G=="function",d=le?x:dt,jt=s.useMemo(()=>{const e=String(d?.taskId||"").trim();if(!e)return"";const n=Ce?.(e).trim()||"";if(n)return n;const o=String(d?.taskReferenceLabel||"").trim();return o&&o!==e?o:n||e},[d?.taskId,d?.taskReferenceLabel,Ce]),st=s.useMemo(()=>{const e=String(d?.assetId||"").trim();if(!e)return"";const n=We?.(e).trim()||"";return n||String(d?.imageReferenceLabel||"").trim()},[d?.assetId,d?.imageReferenceLabel,We]),Xt=s.useMemo(()=>j.findIndex(e=>e.id===S),[j,S]),Vt=z?.color||mt,Sa=s.useMemo(()=>Bt({title:Pe,globalInstruction:Be,annotations:j}),[j,Be,Pe]),Oe=s.useMemo(()=>Va(Sa),[Sa]),Na=Oe!==M.current,y=s.useMemo(()=>({width:Math.max(kt.width*P,0),height:Math.max(kt.height*P,0)}),[kt.height,kt.width,P]),De=s.useMemo(()=>({visibleRadius:7,hitRadius:11}),[]),qt=s.useMemo(()=>`0 0 ${Math.max(y.width,1)} ${Math.max(y.height,1)}`,[y.height,y.width]),rt=typeof se=="boolean",Ca=s.useMemo(()=>{const e=_t.trim().toLowerCase();return e?zt.filter(n=>[n.displayName,n.originalFilename,n.imageReferenceLabel,n.taskReferenceLabel,n.assetId].filter(Boolean).join(" ").toLowerCase().includes(e)):zt},[zt,_t]),ke=s.useMemo(()=>{const e=Me.current;return e?P>1||y.width>e.clientWidth+1||y.height>e.clientHeight+1:P>1},[y.height,y.width,P]),Jt=`${Math.round(P*100)}%`,Zt=s.useMemo(()=>{const e=new Map;return j.forEach((n,o)=>{e.set(n.id,o+1)}),e},[j]);s.useEffect(()=>{me.current=L},[L]),s.useEffect(()=>{const e=_a.current;if(!e||!S)return;e.focus();const n=e.value.length;e.setSelectionRange(n,n)},[S]),s.useEffect(()=>{_e.current=v},[v]),s.useEffect(()=>{Ke(!1)},[v]),s.useEffect(()=>{if(!z){ht(!1);return}ca(z.color||lt[z.markerType||ce])},[z]);const Tt=s.useCallback(async e=>{const n=typeof performance<"u"?performance.now():Date.now(),o=await ge(`/api/taskforce/annotated-attachments/sessions?${Q}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...A},body:JSON.stringify({taskId:e.taskId,baseImageAssetId:e.assetId,title:Ye(e),globalInstruction:"",annotations:[]})},$),i=await o.json().catch(()=>({}));if(!o.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 $t("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 o.headers?.get=="function"&&o.headers.get("server-timing")||null}),c},[A,$,Q]),ja=s.useCallback(e=>new Promise((n,o)=>{const i=new FileReader;i.onload=()=>n(typeof i.result=="string"?i.result:""),i.onerror=()=>o(i.error||new Error("Failed to read clipboard image.")),i.readAsDataURL(e)}),[]),N=s.useCallback(()=>{tt.current!==null&&(window.clearTimeout(tt.current),tt.current=null),nt.current!==null&&(window.clearTimeout(nt.current),nt.current=null)},[]),ze=s.useCallback(e=>{Xe(e.title),Ve(e.globalInstruction),re(e.annotations),F(n=>n&&e.annotations.some(o=>o.id===n)?n:e.annotations[0]?.id||null)},[]),pe=s.useCallback((e,n)=>{const o=Ye(n||d||{assetId:e.baseImageAssetId,displayName:"Annotated session"}),i=Do(e,o),c=Va(i);fe.current=!0,N(),ze(i),M.current=c,H.current=c,Nt.current=null,Ee.current=!1,Ct.current=null,at.current=null,D(null),R("saved"),q(!1)},[d,ze,N]),ot=s.useCallback(e=>{fe.current=!0,N(),$e([]),Re(null),xe(!1),Ke(!1),Xe(Ye(e)),Ve(""),re([]),F(null),M.current="",H.current="",Nt.current=null,Ee.current=!1,Ct.current=null,at.current=null,Mt(null),Lt(!1),D(null),R("saved"),q(!1)},[N]),we=s.useCallback(async(e,n,o)=>{const i=_e.current;if(!i)return!0;if(e===M.current)return J.current||(D(null),R("saved")),!0;if(J.current){if(Ee.current=!0,!o?.waitForInFlight||!await J.current)return!1;const m=H.current;return m===M.current?!0:we(m,n,o)}N();const c=qa(e);Ct.current=i,Nt.current=e,q(!0),g(null),D(null),R("saving");const h=(async()=>{try{const u=await ge(`/api/taskforce/annotated-attachments/sessions/${encodeURIComponent(i)}?${Q}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json",...A},body:JSON.stringify(c)},$),m=await u.json().catch(()=>({}));if(!u.ok)throw new Error(String(m?.error||"Failed to save session."));const _=m?.session;if(!_?.id)throw new Error("Failed to save session.");$e(ea=>{const Ha=ea.findIndex(Bn=>Bn.id===_.id);if(Ha===-1)return[_,...ea];const Oa=[...ea];return Oa[Ha]=_,Oa}),M.current=e,at.current=null,D(null);const w=_e.current===_.id,C=H.current,X=C!==e,it=Ee.current||X;return Ee.current=!1,w&&!it?(fe.current=!0,ze(c),R("saved")):!it&&C===M.current?R("saved"):R("pending"),!0}catch(u){const m=u instanceof Error?u.message:"Failed to save session.";return g(m),D(m),R("error"),at.current!==e&&_e.current===i&&H.current===e&&(at.current=e,nt.current=window.setTimeout(()=>{nt.current=null,!(_e.current!==i||H.current!==e)&&we(e,n,{waitForInFlight:!0})},1500)),!1}finally{Nt.current=null,J.current=null,Ct.current=null,q(!1)}})();J.current=h;const k=await h;if(k){const u=H.current;if(u!==M.current)return we(u,n,o)}return k},[ze,N,A,$,Q]),Ta=s.useCallback(e=>{if(_e.current){if(H.current===M.current){D(null),J.current||R("saved");return}Y!=="saving"&&R("pending"),N(),tt.current=window.setTimeout(()=>{tt.current=null,we(H.current,"structure")},e)}},[N,we,Y]),K=s.useCallback(e=>{Kt.current=e},[]),E=s.useCallback(async e=>{N();const n=H.current;return!_e.current||n===M.current?(D(null),J.current||R("saved"),!0):we(n,e,{waitForInFlight:!0})},[N,we]),dn=s.useCallback(()=>{const e=M.current;e&&(N(),fe.current=!0,ze(qa(e)),D(null),R("saved"),g(null))},[ze,N]),U=s.useCallback(async(e,n)=>{const o=typeof performance<"u"?performance.now():Date.now(),i=JSON.stringify({targetKey:Ge(e),requestedSessionId:n?.requestedSessionId??null,autoCreateIfEmpty:n?.autoCreateIfEmpty===!0});if(St.current===i)return;const c=ve.current+1;ve.current=c,St.current=i,da(!0),g(null);try{const h=new URLSearchParams;p&&h.set("workspaceId",p),e.taskId&&h.set("taskId",e.taskId),h.set("imageAssetId",e.assetId);const k=await ge(`/api/taskforce/annotated-attachments/sessions?${h.toString()}`,{credentials:"include",headers:A,cache:"no-store"},$),u=await k.json().catch(()=>({}));if(!k.ok)throw new Error(String(u?.error||"Failed to load annotated attachment sessions."));const m=Array.isArray(u?.sessions)?u.sessions:[];if($t("annotated_sessions_loaded",{assetId:e.assetId,taskId:e.taskId||null,requestedSessionId:n?.requestedSessionId??null,autoCreateIfEmpty:n?.autoCreateIfEmpty===!0,sessionCount:m.length,durationMs:Math.round((typeof performance<"u"?performance.now():Date.now())-o),serverTiming:typeof k.headers?.get=="function"&&k.headers.get("server-timing")||null}),m.length===0&&n?.autoCreateIfEmpty&&String(e.assetId||"").trim()){const C=n?.requestedSessionId??me.current;if(me.current=null,C&&g("The previously selected annotation session could not be restored."),ve.current!==c)return;const X=await Tt(e);if($t("annotated_sessions_auto_created_after_empty_load",{assetId:e.assetId,taskId:e.taskId||null,sessionId:X.id,totalDurationMs:Math.round((typeof performance<"u"?performance.now():Date.now())-o)}),ve.current!==c)return;$e([X]),Re(X.id),xe(!1),pe(X,e);return}if(ve.current!==c)return;$e(m),xe(m.length===0);const _=n?.requestedSessionId??me.current;me.current=null;const w=m.find(C=>C.id===_)||m[0]||null;_&&!w&&g("The previously selected annotation session could not be restored."),Re(w?.id||null),w?pe(w,e):(fe.current=!0,N(),Xe(Ye(e)),Ve(""),re([]),F(null),M.current="",H.current="",D(null),R("saved"))}catch(h){if(ve.current!==c)return;g(h instanceof Error?h.message:"Failed to load sessions.")}finally{St.current===i&&(St.current=null),ve.current===c&&da(!1)}},[N,Tt,A,$,pe,p]),$a=s.useCallback(async()=>{ga(!0),ba(null);try{const e=new URLSearchParams;e.set("workspaceId",String(p||"default").trim()||"default");const n=await ge(`/api/taskforce/annotated-attachments/images?${e.toString()}`,{credentials:"include",headers:A,cache:"no-store"},$),o=await n.json().catch(()=>({}));if(!n.ok)throw new Error(String(o?.error||"Failed to load images."));on(Array.isArray(o?.images)?o.images:[])}catch(e){ba(e instanceof Error?e.message:"Failed to load images.")}finally{ga(!1)}},[A,$,p]),un=s.useCallback(async()=>{if(typeof navigator>"u"||!navigator.clipboard||typeof navigator.clipboard.read!="function"){g("Clipboard image paste is not supported in this environment.");return}xa(!0),g(null),Ia.current=Date.now()+2e3;try{const n=(await navigator.clipboard.read()).find(_=>_.types.some(w=>w.startsWith("image/"))),o=n?.types.find(_=>_.startsWith("image/"))||"";if(!n||!o)throw new Error("No image found on the clipboard.");const i=await n.getType(o),c=await ja(i);if(!c)throw new Error("Failed to read clipboard image.");const h=o==="image/jpeg"?"jpg":o==="image/webp"?"webp":o==="image/gif"?"gif":"png",k=await fetch("/api/taskforce/context-upload",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...A},body:JSON.stringify({file:c,originalName:`pasted-image.${h}`,workspaceId:String(p||"default").trim()||"default"})}),u=await k.json().catch(()=>({}));if(!k.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 m={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"};me.current=null,le?(ie.current="",G?.(m,{sessionId:null})):(be(m),ie.current=Ge(m),U(m,{autoCreateIfEmpty:!0}))}catch(e){g(e instanceof Error?e.message:"Failed to paste image.")}finally{xa(!1)}},[le,U,G,ja,A,p]),mn=s.useCallback(async()=>{const e=Dt.trim();if(!e){g("Enter an image reference to open.");return}pa(!0),g(null);try{const n=new URLSearchParams({workspaceId:String(p||"default").trim()||"default"}),o=await ge(`/api/taskforce/annotated-attachments/images/${encodeURIComponent(e)}?${n.toString()}`,{method:"GET",credentials:"include",headers:A}),i=await o.json().catch(()=>({}));if(!o.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};me.current=null,xt(!1),vt(""),le?(ie.current="",G?.(c,{sessionId:null})):(be(c),ie.current=Ge(c),U(c,{autoCreateIfEmpty:!0}))}catch(n){g(n instanceof Error?n.message:"Failed to open image reference.")}finally{pa(!1)}},[le,U,G,Dt,A,p]),fn=s.useCallback(async e=>{if(!e.assetId||!e.path||!await E("session-switch"))return;const o={assetId:e.assetId,path:e.path,displayName:String(e.displayName||e.originalFilename||"Image attachment").trim()||"Image attachment",...e.taskId?{taskId:e.taskId}:{},...e.taskReferenceLabel?{taskReferenceLabel:e.taskReferenceLabel}:{},...e.imageReferenceLabel?{imageReferenceLabel:e.imageReferenceLabel}:{}};me.current=null,g(null),le?(ie.current="",G?.(o,{sessionId:null})):(be(o),ie.current=Ge(o),ot(o),U(o,{autoCreateIfEmpty:!0}))},[E,le,U,G,ot]),Ra=s.useCallback(e=>{$e(n=>{const o=n.findIndex(c=>c.id===e.id);if(o===-1)return[e,...n];const i=[...n];return i[o]=e,i}),Re(e.id),pe(e,d)},[d,pe]),Pa=s.useCallback(e=>{$e(n=>{const o=n.filter(c=>c.id!==e),i=o[0]||null;return Re(i?.id||null),i?pe(i,d):(fe.current=!0,N(),Xe(Ye(d||{displayName:"Annotated session"})),Ve(""),re([]),F(null),M.current="",H.current="",D(null),R("saved")),o})},[d,N,pe]);s.useEffect(()=>{if(!x)return;if(le||be(n=>n&&Ge(n)===ee&&n.taskReferenceLabel===x.taskReferenceLabel&&n.imageReferenceLabel===x.imageReferenceLabel&&n.displayName===x.displayName?n:x),!b){ee&&(ee!==ie.current||ne!==wt.current)&&ee!==Wt.current&&(ot(x),Wt.current=ee,wt.current=ne,$t("annotated_sessions_load_deferred",{assetId:x.assetId,taskId:x.taskId||null,requestedTargetKey:ee})),je?.();return}ee&&(ee!==ie.current||ne!==wt.current)&&(ot(x),ie.current=ee,wt.current=ne,Wt.current="",U(x,{autoCreateIfEmpty:!0})),je?.()},[le,U,je,ne,x,ee,ot,L,b]),s.useEffect(()=>{d&&ye?.({target:d,sessionId:v})},[d,ye,v]),s.useEffect(()=>{if(!d||typeof document>"u"||!b)return;const e=()=>{Date.now()<Ia.current||H.current!==M.current||V||pt||U(d,{requestedSessionId:v})},n=()=>{document.visibilityState==="visible"&&e()};return document.addEventListener("visibilitychange",n),()=>{document.removeEventListener("visibilitychange",n)}},[d,U,pt,V,v,b]),s.useEffect(()=>{if(!d?.path){Je(!1),Ze(!1),et({width:0,height:0});return}Je(!0),Ze(!1),et({width:0,height:0}),Gt(1),Qe(!1)},[d?.path]),s.useEffect(()=>{if(!ue)return;const e=Yt.current;!e||!e.complete||e.naturalWidth<=0||e.naturalHeight<=0||(et({width:e.naturalWidth,height:e.naturalHeight}),Je(!1),Ze(!1))},[d?.path,ue]),s.useEffect(()=>{ke||Qe(!1)},[ke]),s.useEffect(()=>{Mt(null)},[v]),s.useEffect(()=>{if(H.current=Oe,fe.current){fe.current=!1;return}if(!v){N(),D(null),R("saved");return}if(Oe===M.current){N(),J.current||(D(null),R("saved"));return}const e=Kt.current;if(Kt.current=null,Y==="error"&&e===null)return;const n=e??600;if(J.current){Ee.current=!0,R("pending");return}Ta(n)},[N,Oe,Y,Ta,v]),s.useEffect(()=>{v&&(J.current||Oe===M.current&&Y!=="error"&&Y!=="saved"&&(D(null),R("saved")))},[Oe,Y,v]),s.useEffect(()=>{!rt||!se||$a()},[$a,se,rt]),s.useEffect(()=>{if(typeof window>"u")return;const e=n=>{H.current!==M.current&&(n.preventDefault(),n.returnValue="")};return window.addEventListener("beforeunload",e),()=>window.removeEventListener("beforeunload",e)},[]),s.useEffect(()=>{if(typeof document>"u")return;const e=()=>{document.visibilityState==="hidden"&&E("visibility-hidden")};return document.addEventListener("visibilitychange",e),()=>document.removeEventListener("visibilitychange",e)},[E]),s.useEffect(()=>{if(!ft||typeof document>"u")return;const e=n=>{const o=n.target;o instanceof Node&&(ka.current?.contains(o)||ht(!1))};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[ft]),s.useEffect(()=>{if(!At||typeof document>"u")return;const e=n=>{const o=n.target;o instanceof Node&&(wa.current?.contains(o)||Ke(!1))};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[At]),s.useEffect(()=>()=>{N()},[N]),s.useEffect(()=>{if(!de)return;const e=window.setTimeout(()=>Et(!1),2e3);return()=>window.clearTimeout(e)},[de]),s.useEffect(()=>{if(!yt)return;const e=window.setTimeout(()=>Ht(!1),2e3);return()=>window.clearTimeout(e)},[yt]),s.useEffect(()=>{if(!bt)return;const e=window.setTimeout(()=>Ot(!1),2e3);return()=>window.clearTimeout(e)},[bt]);const te=s.useCallback((e,n,o=300)=>{re(i=>Se(i.map(c=>c.id===e?n(c):c))),K(o)},[K]),hn=s.useCallback(e=>{z&&(te(z.id,n=>({...n,color:e})),ca(e),ht(!1))},[z,te]),Ba=s.useCallback(async()=>{if(!(!d||!await E("session-switch"))){q(!0),g(null);try{const n=await Tt(d);xe(!1),await U(d,{requestedSessionId:n.id,autoCreateIfEmpty:!1}),Ae("select")}catch(n){g(n instanceof Error?n.message:"Failed to create session."),xe(!0)}finally{q(!1)}}},[d,Tt,E,U]),pn=s.useCallback(e=>{if(e!=="select"&&!B){Qe(!1),g("No session exists for this image yet."),xe(!0);return}g(null),xe(!1),Qe(!1),Ae(e)},[B]),gn=s.useCallback(async()=>{if(!(!d||!B||!await E("duplicate"))){q(!0),g(null);try{const n=Se(j.map((h,k)=>Bo(h,k))),o=await ge(`/api/taskforce/annotated-attachments/sessions?${Q}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...A},body:JSON.stringify({taskId:d.taskId,baseImageAssetId:d.assetId,title:Ao(Pe||B.title,d),globalInstruction:Be,annotations:n})},$),i=await o.json().catch(()=>({}));if(!o.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.");Ra(c),Ae("select")}catch(n){g(n instanceof Error?n.message:"Failed to duplicate session.")}finally{q(!1)}}},[d,j,Be,Pe,Ra,E,A,$,B,Q]),yn=s.useCallback(async()=>E("manual"),[E]),Aa=s.useCallback(async()=>{if(v){ma(!0),g(null);try{const e=await ge(`/api/taskforce/annotated-attachments/sessions/${encodeURIComponent(v)}/payload?${Q}`,{credentials:"include",headers:A},$),n=await e.json().catch(()=>({}));if(!e.ok)throw new Error(String(n?.error||"Failed to load payload preview."));Mt(n?.payload||null)}catch(e){g(e instanceof Error?e.message:"Failed to load payload preview.")}finally{ma(!1)}}},[A,$,v,Q]),bn=s.useCallback(()=>{v&&E("payload-preview").then(e=>{e&&(Lt(!0),Aa())})},[E,Aa,v]),Ma=s.useCallback(async e=>{if(!oe||!navigator.clipboard||typeof navigator.clipboard.writeText!="function"){g("Clipboard copy is not available in this browser.");return}try{const n=e==="json"?JSON.stringify(oe,null,2):Xa(oe);await navigator.clipboard.writeText(n),Et(e)}catch(n){g(n instanceof Error?n.message:"Failed to copy payload content."),Et(!1)}},[oe]),xn=s.useCallback(async()=>{if(!jt||!navigator.clipboard||typeof navigator.clipboard.writeText!="function"){g("Clipboard copy is not available in this browser.");return}try{await navigator.clipboard.writeText(jt),Ht(!0)}catch(e){g(e instanceof Error?e.message:"Failed to copy task id."),Ht(!1)}},[jt]),vn=s.useCallback(async()=>{if(!st||!navigator.clipboard||typeof navigator.clipboard.writeText!="function"){g("Clipboard copy is not available in this browser.");return}try{await navigator.clipboard.writeText(st),Ot(!0)}catch(e){g(e instanceof Error?e.message:"Failed to copy image reference."),Ot(!1)}},[st]),_n=s.useCallback(async()=>{if(!v||!await E("delete-session"))return;const n=(B?.title||"Untitled session").trim()||"Untitled session";if(window.confirm(`Delete the annotated attachment session "${n}"?`)){q(!0),g(null);try{const o=await ge(`/api/taskforce/annotated-attachments/sessions/${encodeURIComponent(v)}?${Q}`,{method:"DELETE",credentials:"include",headers:A},$),i=await o.json().catch(()=>({}));if(!o.ok)throw new Error(String(i?.error||"Failed to delete session."));Pa(v),Ae("select")}catch(o){g(o instanceof Error?o.message:"Failed to delete session.")}finally{q(!1)}}},[E,Pa,A,$,B,v,Q]),Qt=s.useCallback(()=>{S&&(re(e=>Se(e.filter(n=>n.id!==S))),F(null),K(300))},[K,S]),Ue=s.useCallback(e=>{const n=Oo(e),o=Me.current;if(!o||n===P){Gt(n);return}const i=(o.scrollLeft+o.clientWidth/2)*(n/P)-o.clientWidth/2,c=(o.scrollTop+o.clientHeight/2)*(n/P)-o.clientHeight/2;Gt(n),window.requestAnimationFrame(()=>{o.scrollLeft=Math.max(0,i),o.scrollTop=Math.max(0,c)})},[P]),In=s.useCallback(()=>{Ue(P+.25)},[Ue,P]),kn=s.useCallback(()=>{Ue(P-.25)},[Ue,P]),wn=s.useCallback(()=>{Ue(1);const e=Me.current;e&&window.requestAnimationFrame(()=>{e.scrollLeft=0,e.scrollTop=0})},[Ue]),Ea=s.useCallback(e=>{S&&re(n=>{const o=n.findIndex(c=>c.id===S);if(o===-1)return n;const i=e==="up"?o-1:o+1;return i<0||i>=n.length?n:(K(300),Mo(n,o,i))})},[K,S]);s.useEffect(()=>{if(!S)return;const e=n=>{n.key==="Delete"&&(Co(n.target)||(n.preventDefault(),Qt()))};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[Qt,S]);const O=s.useCallback(e=>{const n=va.current?.getBoundingClientRect();return!n||n.width<=0||n.height<=0?null:{x:I((e.clientX-n.left)/n.width),y:I((e.clientY-n.top)/n.height)}},[]),Sn=s.useCallback(e=>{if(W&&ke){const i=Me.current;if(!i)return;Z.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(!v)return;if(T==="select"){F(null);return}const n=O(e);if(!n)return;if(T==="pin"||T==="text-note"){const i=Ka(T,n,n,mt);re(c=>Se([...c,i])),F(i.id),K(300),Ae("select");return}Ie.current=n;const o=Ka(T,n,n,mt);he.current={annotationId:o.id,kind:T},e.currentTarget.setPointerCapture?.(e.pointerId),re(i=>Se([...i,o])),F(o.id),K(300)},[ke,mt,K,W,O,v,T]),Nn=s.useCallback(e=>{if(Z.current?.pointerId===e.pointerId){e.currentTarget.releasePointerCapture?.(e.pointerId);return}if(!Ie.current||!he.current||T!=="box"&&T!=="arrow")return;const n=O(e);Ie.current=null,he.current=null,e.currentTarget.releasePointerCapture?.(e.pointerId),n&&Ae("select")},[O,T]),La=s.useCallback((e,n)=>{if(T!=="select"||W)return;const o=O(e);o&&(Le.current={annotationId:n.id,originPointer:o,originAnnotation:n},F(n.id),e.stopPropagation())},[W,O,T]),Cn=s.useCallback(e=>{if(Z.current?.pointerId===e.pointerId){const u=Me.current;if(!u)return;const m=e.clientX-Z.current.startX,_=e.clientY-Z.current.startY;u.scrollLeft=Z.current.scrollLeft-m,u.scrollTop=Z.current.scrollTop-_;return}if(he.current&&Ie.current){const u=O(e);if(!u)return;const{annotationId:m,kind:_}=he.current,w=Ie.current;te(m,C=>_==="box"&&C.kind==="box"?{...C,x:I(Math.min(w.x,u.x)),y:I(Math.min(w.y,u.y)),width:Ne(Math.abs(u.x-w.x)),height:Ne(Math.abs(u.y-w.y))}:_==="arrow"&&C.kind==="arrow"?{...C,x:w.x,y:w.y,x2:u.x,y2:u.y}:C);return}if(Fe.current){const u=O(e);if(!u)return;const{annotationId:m,originPointer:_,originAnnotation:w}=Fe.current,C=u.x-_.x,X=u.y-_.y;te(m,()=>({...w,width:Ne(w.width+C),height:Ne(w.height+X)}));return}if(He.current){const u=O(e);if(!u)return;const{annotationId:m,endpoint:_,originPointer:w,originAnnotation:C}=He.current,X=u.x-w.x,it=u.y-w.y;te(m,()=>_==="tail"?{...C,x:I(C.x+X),y:I(C.y+it)}:{...C,x2:I(C.x2+X),y2:I(C.y2+it)});return}if(!Le.current)return;const n=O(e);if(!n)return;const{annotationId:o,originPointer:i,originAnnotation:c}=Le.current,h=n.x-i.x,k=n.y-i.y;te(o,()=>c.kind==="pin"||c.kind==="text-note"?{...c,x:I(c.x+h),y:I(c.y+k)}:c.kind==="box"?{...c,x:I(c.x+h),y:I(c.y+k)}:{...c,x:I(c.x+h),y:I(c.y+k),x2:I(c.x2+h),y2:I(c.y2+k)})},[O,te]),jn=s.useCallback(()=>{Ie.current=null,Le.current=null,Fe.current=null,He.current=null,he.current=null,Z.current=null},[]),Tn=s.useCallback(()=>{Ie.current=null,Le.current=null,Fe.current=null,He.current=null,he.current=null,Z.current=null},[]),$n=s.useCallback(()=>{Le.current=null,Fe.current=null,He.current=null,he.current=null,Z.current=null},[]),Rn=s.useCallback((e,n)=>{if(T!=="select"||W)return;const o=O(e);o&&(Fe.current={annotationId:n.id,originPointer:o,originAnnotation:n},F(n.id),e.stopPropagation())},[W,O,T]),Fa=s.useCallback((e,n,o)=>{if(T!=="select"||W)return;const i=O(e);i&&(He.current={annotationId:n.id,originPointer:i,originAnnotation:n,endpoint:o},F(n.id),e.stopPropagation())},[W,O,T]),Pn=B?.updatedAt?sa(B.updatedAt):"Not saved yet";return t.jsxs("div",{className:`${a.shell} ${rt?a.shellWithImageTray:""} ${rt&&se?a.shellImageTrayOpen:""}`.trim(),children:[rt?t.jsxs("aside",{className:`${a.imageTrayPanel} ${se?a.imageTrayPanelOpen:""}`.trim(),"aria-label":"Image tray","aria-hidden":!se,children:[t.jsxs("div",{className:a.imageTrayHeader,children:[t.jsxs("span",{className:a.imageTrayTitle,children:[t.jsx(za,{size:14}),"Images"]}),t.jsx("button",{type:"button",className:"tf-control-icon",onClick:ct,title:"Collapse image tray","aria-label":"Collapse image tray",children:t.jsx(En,{size:16})})]}),t.jsxs("div",{className:a.imageTraySearch,children:[t.jsx(Ln,{size:12,className:a.imageTraySearchIcon}),t.jsx("input",{type:"text",placeholder:"Search images...",value:_t,onChange:e=>ln(e.target.value),className:a.imageTraySearchInput})]}),t.jsx("div",{className:`tf-scrollbar ${a.imageTrayList}`,children:cn?t.jsx("div",{className:a.imageTrayState,children:"Loading images..."}):ya?t.jsx("div",{className:a.imageTrayStateError,children:ya}):Ca.length===0?t.jsx("div",{className:a.imageTrayState,children:_t.trim()?"No images match your search.":"No images found."}):Ca.map(e=>{const n=d?.assetId===e.assetId,o=typeof e.attachmentCount=="number"&&Number.isFinite(e.attachmentCount)?Math.max(0,e.attachmentCount):0,i=typeof e.sessionCount=="number"&&Number.isFinite(e.sessionCount)?Math.max(0,e.sessionCount):0,c=o>1?`${o} tasks linked`:o===1?e.taskReferenceLabel||"1 task linked":"Unattached",h=String(e.displayName||e.originalFilename||"Image attachment").trim()||"Image attachment";return t.jsxs("button",{type:"button",className:`${a.imageTrayItem} ${n?a.imageTrayItemActive:""}`.trim(),onClick:()=>{fn(e)},title:h,children:[t.jsx("span",{className:a.imageTrayThumb,children:t.jsx("img",{src:e.path,alt:"",loading:"lazy"})}),t.jsxs("span",{className:a.imageTrayItemBody,children:[t.jsx("span",{className:a.imageTrayItemTitle,children:h}),t.jsx("span",{className:a.imageTrayItemTask,children:c}),t.jsxs("span",{className:a.imageTrayItemMeta,children:[t.jsxs("span",{className:a.imageTrayItemMetaDetails,children:[jo(e.sizeBytes),e.updatedAt?t.jsxs(t.Fragment,{children:[" · ",To(e.updatedAt)]}):null,i>0?t.jsxs(t.Fragment,{children:[" · ",i," session",i===1?"":"s"]}):null]}),e.imageReferenceLabel?t.jsx("span",{className:a.imageTrayItemReference,children:e.imageReferenceLabel}):null]})]})]},e.assetId)})})]}):null,t.jsxs("section",{className:`tf-surface-panel ${a.panel} ${a.sessionPanel}`,children:[t.jsx("div",{className:a.sessionContextBar,children:d?.taskId?t.jsxs(t.Fragment,{children:[t.jsx("div",{className:a.sessionContextLeft,children:Te?t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.backToTaskBtn}`,onClick:()=>{E("back-to-task").then(e=>{e&&d.taskId&&Te(d.taskId)})},"aria-label":"Back to task",title:"Back to task",children:t.jsx(Fn,{size:16})}):t.jsx("div",{className:a.sessionContextSpacer,"aria-hidden":"true"})}),t.jsx("div",{className:a.sessionContextRight,children:t.jsx(Mn,{copied:yt,onClick:()=>{xn()},title:"Copy task id",ariaLabel:yt?"Copied task id":"Copy task id",label:jt})})]}):t.jsxs(t.Fragment,{children:[t.jsx("div",{className:a.sessionContextLeft,children:t.jsx("span",{className:`tf-label-micro ${a.sessionContextLabel}`,children:"Unattached Image"})}),t.jsx("div",{className:a.sessionContextRight,children:t.jsx("div",{className:a.sessionContextSpacer,"aria-hidden":"true"})})]})}),t.jsxs("div",{className:a.panelHeader,children:[t.jsxs("div",{className:a.panelHeaderText,children:[t.jsx("div",{className:`tf-heading-card ${a.panelTitle}`,children:"Sessions"}),d?null:t.jsx("div",{className:"tf-text-secondary",children:"Open an image attachment to begin"})]}),t.jsx("div",{className:a.sessionActions,children:t.jsx("button",{type:"button",className:`tf-control-icon ${a.iconButton}`,onClick:()=>{Ba()},disabled:!d||V,"aria-label":"Create session",title:"Create session",children:t.jsx(Hn,{size:16})})})]}),d?pt?t.jsx("div",{className:a.emptyState,children:t.jsx("p",{className:"tf-empty-copy",children:"Loading sessions…"})}):t.jsx("div",{className:`tf-scrollbar ${a.sessionList}`,children:ut.length===0?t.jsxs("div",{className:a.sessionEmptyState,children:[t.jsx("div",{className:`tf-heading-card ${a.sessionTitle}`,children:"No sessions yet"}),t.jsx("div",{className:`tf-text-secondary ${a.annotationSummary}`,children:"Create the first annotation session for this image."})]}):ut.map(e=>{const n=v===e.id,o=n&&At,i=$o(n?Be:e.globalInstruction),c=(n?Pe:e.title)||"Untitled session";return t.jsxs("div",{className:`tf-surface-elevated ${a.sessionCard} ${n?a.sessionCardActive:""}`,ref:n?wa:void 0,onBlur:o?h=>{const k=h.relatedTarget;k instanceof Node&&h.currentTarget.contains(k)||Ke(!1)}:void 0,children:[o?t.jsxs("div",{className:a.sessionCardBody,children:[t.jsxs("div",{className:a.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 ${a.textInput} ${a.sessionTitleInput}`,value:Pe,onChange:h=>{Xe(h.target.value),K(600)},placeholder:"Session title"}),t.jsx("span",{className:`tf-text-meta ${a.sessionTimestamp}`,children:sa(e.updatedAt)})]}),Pt(e)?t.jsxs("div",{className:`tf-text-secondary ${a.sessionCreator}`,children:["Created by ",Pt(e)]}):null,t.jsxs("div",{className:a.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 ${a.textArea} ${a.sessionInstructionField}`,value:Be,onChange:h=>{Ve(h.target.value),K(600)},placeholder:"Add overall instructions, context, or framing for this session."})]}),t.jsxs("div",{className:`tf-text-secondary ${a.annotationSummary}`,children:[e.annotations.length," annotation",e.annotations.length===1?"":"s"]})]}):t.jsxs("button",{type:"button",className:a.sessionCardButton,onClick:()=>{if(n){Ke(!0);return}E("session-switch").then(h=>{h&&(Re(e.id),pe(e,d))})},"aria-pressed":n,children:[t.jsxs("div",{className:a.sessionMeta,children:[t.jsx("span",{className:`tf-heading-card ${a.sessionTitle}`,children:c}),t.jsx("span",{className:`tf-text-meta ${a.sessionTimestamp}`,children:sa(e.updatedAt)})]}),Pt(e)?t.jsxs("div",{className:`tf-text-secondary ${a.sessionCreator}`,children:["Created by ",Pt(e)]}):null,i?t.jsx("div",{className:`tf-text-secondary ${a.sessionInstructionPreview}`,children:i}):null,t.jsxs("div",{className:`tf-text-secondary ${a.annotationSummary}`,children:[e.annotations.length," annotation",e.annotations.length===1?"":"s"]})]}),n?t.jsx("div",{className:a.sessionCardFooter,children:t.jsxs(t.Fragment,{children:[t.jsx("button",{type:"button",className:`tf-button-ghost ${a.toolRailButton} ${a.iconButton}`,onClick:()=>{gn()},disabled:V,"aria-label":"Duplicate session",title:`Duplicate session "${c}"`,children:t.jsx(na,{size:16})}),t.jsx("button",{type:"button",className:`tf-button-ghost ${a.toolRailButton} ${a.iconButton}`,onClick:()=>{_n()},disabled:V,"aria-label":"Delete session",title:`Delete session "${c}"`,children:t.jsx(Ua,{size:16})})]})}):null]},e.id)})}):t.jsxs("div",{className:a.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 ${a.panel} ${a.canvasPanel}`,children:[t.jsxs("div",{className:a.panelHeader,children:[t.jsxs("div",{className:a.canvasHeading,children:[t.jsx("div",{className:"tf-heading-card",children:d?.displayName||"Annotated attachment"}),t.jsx("div",{className:"tf-text-secondary",children:B?`${j.length} markers · ${Pn}`:"Pick or create a session"})]}),st?t.jsx(Vn,{copied:bt,onClick:()=>{vn()},title:"Copy image reference",ariaLabel:bt?"Copied image reference":"Copy image reference",label:st}):null]}),t.jsxs("div",{className:a.toolRail,children:[t.jsxs("div",{className:`${a.toolbarCluster} ${a.toolbarViewportCluster}`,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>{yn()},disabled:!B||V||!Na&&Y!=="error","aria-label":V?"Saving session":"Save session",title:V?"Saving session":"Save session",children:t.jsx(On,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>xt(!0),disabled:qe,"aria-label":"Open image",title:"Open image",children:t.jsx(za,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>{un()},disabled:It,"aria-label":It?"Pasting image":"Paste image",title:It?"Pasting image":"Paste image",children:It?t.jsx(Ga,{size:18,className:Da.spin}):t.jsx(Dn,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>{B&&dn()},disabled:!B||!Na,"aria-label":"Reset unsaved changes",title:"Reset unsaved changes",children:t.jsx(zn,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost ${a.toolRailButton} ${a.iconButton}`,onClick:kn,disabled:!d||ue||P<=.25,"aria-label":"Zoom out",children:t.jsx(Un,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:wn,disabled:!d||ue||P===1,"aria-label":`Reset zoom to 100 percent (currently ${Jt})`,title:`Reset zoom to 100% (currently ${Jt})`,children:t.jsx("span",{children:Jt})}),t.jsx("button",{type:"button",className:`tf-button-ghost ${a.toolRailButton} ${a.iconButton}`,onClick:In,disabled:!d||ue||P>=4,"aria-label":"Zoom in",children:t.jsx(Gn,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.toolBtn} ${a.toolbarButton} ${W?a.toolBtnActive:""}`,onClick:()=>Qe(e=>!e),disabled:!d||ue||!ke,"aria-label":"Pan canvas",title:"Pan canvas",children:t.jsx(Yn,{size:18})})]}),t.jsx("span",{className:a.toolbarSeparator,"aria-hidden":"true"}),t.jsxs("div",{className:a.toolbarActions,children:[t.jsx("div",{className:a.toolbarSelectionActions,children:z?t.jsxs(t.Fragment,{children:[t.jsxs("div",{ref:ka,className:a.toolbarColorPicker,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton} ${a.colorPickerButton}`,onClick:()=>ht(e=>!e),"aria-label":"Marker color","aria-expanded":ft,title:"Marker color",children:t.jsx("span",{className:a.colorPickerSwatch,style:{backgroundColor:Vt},"aria-hidden":"true"})}),ft?t.jsx("div",{className:a.colorPickerPopover,role:"menu","aria-label":"Marker color options",children:No.map(e=>t.jsx("button",{type:"button",className:`${a.colorOption} ${Vt===e?a.colorOptionActive:""}`,style:{backgroundColor:e},onClick:()=>hn(e),"aria-label":`Use marker color ${e}`,"aria-pressed":Vt===e},e))}):null]}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:Qt,"aria-label":"Delete marker",title:"Delete selected marker",children:t.jsx(Ua,{size:18})}),t.jsx("div",{className:a.toolbarGeometryFields,"aria-label":"Marker geometry percent controls",children:Fo(z).map(e=>t.jsxs("label",{className:a.toolbarGeometryField,children:[t.jsx("span",{className:`tf-text-meta ${a.toolbarGeometryLabel}`,children:e.label}),t.jsx("input",{className:`tf-field-shell ${a.toolbarGeometryInput}`,type:"number",min:0,max:100,step:.1,value:e.value,onChange:n=>{const o=Eo(n.target.value);o!==null&&te(z.id,i=>Lo(i,e.key,o))},"aria-label":`${e.label} percent`})]},e.key))})]}):null}),t.jsxs("div",{className:a.toolbarUtilities,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.payloadBtn} ${a.toolbarButton}`,onClick:bn,disabled:!B||gt,"aria-label":gt?"Loading payload preview":"Preview payload",title:gt?"Loading payload preview":"Preview payload",children:t.jsx(oa,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>ha(!0),"aria-label":"How to use marker tools",title:"How to use marker tools",children:t.jsx(ia,{size:18})})]})]})]}),t.jsxs("div",{className:a.canvasWorkspace,children:[t.jsx("div",{className:a.canvasToolRail,"aria-label":"Annotation tools",children:Wa.map(e=>{const n=e.icon;return t.jsx("button",{type:"button",className:`tf-button-ghost ${a.toolRailButton} ${T===e.value?a.toolBtnActive:""}`,onClick:()=>pn(e.value),disabled:!d,title:e.label,"aria-label":`${e.label} tool. ${Rt[e.value].short}`,children:t.jsx(n,{size:18})},e.value)})}),t.jsx("div",{className:a.canvasMain,children:t.jsx("div",{ref:Me,className:`tf-scrollbar ${a.canvasScroller}`,children:d?t.jsxs(t.Fragment,{children:[ue&&!Ut?t.jsx("div",{className:a.canvasStatusOverlay,"aria-live":"polite",children:t.jsxs("div",{className:a.canvasStatusCard,children:[t.jsx(Ga,{size:20,className:Da.spinner}),t.jsx("span",{children:"Loading image…"})]})}):null,Ut?t.jsx("div",{className:a.canvasStatusOverlay,"aria-live":"polite",children:t.jsx("div",{className:a.canvasStatusCard,children:t.jsx("span",{children:"Image failed to load."})})}):null,t.jsx("div",{className:a.canvasFrame,children:t.jsxs("div",{className:a.canvasMedia,style:{width:y.width?`${y.width}px`:void 0,height:y.height?`${y.height}px`:void 0},children:[t.jsx("img",{ref:Yt,src:d.path,alt:d.displayName,className:a.canvasImage,onLoad:()=>{const e=Yt.current;et({width:e?.naturalWidth||0,height:e?.naturalHeight||0}),Je(!1),Ze(!1)},onError:()=>{et({width:0,height:0}),Je(!1),Ze(!0)}}),!ue&&!Ut&&B?t.jsxs("div",{ref:va,className:`${a.overlay} ${T==="select"?a.overlaySelect:""} ${W&&ke?a.overlayPan:""}`,onPointerDown:Sn,onPointerMove:Cn,onPointerUp:e=>{Nn(e),$n()},"data-testid":"annotated-attachment-overlay",onPointerLeave:jn,onPointerCancel:Tn,children:[t.jsx("svg",{className:a.overlaySvg,viewBox:qt,preserveAspectRatio:"none","aria-hidden":"true",children:j.filter(e=>e.kind==="arrow").map(e=>{const n=Ro(e,y),o=ra(e),i=S===e.id;return t.jsxs(ta.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:o,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:o,strokeWidth:i?10:6,strokeLinecap:"round"}),t.jsx("polygon",{points:n.headPoints,fill:o})]},e.id)})}),t.jsx("svg",{className:a.overlayHitLayer,viewBox:qt,preserveAspectRatio:"none",children:j.filter(e=>e.kind==="arrow").map(e=>t.jsxs(ta.Fragment,{children:[t.jsx("line",{x1:e.x*y.width,y1:e.y*y.height,x2:e.x2*y.width,y2:e.y2*y.height,className:a.arrowHitArea,"data-testid":`annotated-arrow-hit-${e.id}`,"aria-label":`Arrow marker ${Zt.get(e.id)||0}`,onPointerDown:n=>La(n,e),onClick:n=>{n.stopPropagation(),F(e.id)}}),S===e.id?t.jsxs(t.Fragment,{children:[t.jsx("circle",{cx:e.x*y.width,cy:e.y*y.height,r:De.hitRadius,className:a.canvasHandleHit,role:"button",tabIndex:0,"aria-label":"Move arrow tail",onPointerDown:n=>Fa(n,e,"tail")}),t.jsx("circle",{cx:e.x*y.width,cy:e.y*y.height,r:De.visibleRadius,className:a.canvasHandleVisible,"aria-hidden":"true"}),t.jsx("circle",{cx:e.x2*y.width,cy:e.y2*y.height,r:De.hitRadius,className:a.canvasHandleHit,role:"button",tabIndex:0,"aria-label":"Move arrow tip",onPointerDown:n=>Fa(n,e,"tip")}),t.jsx("circle",{cx:e.x2*y.width,cy:e.y2*y.height,r:De.visibleRadius,className:a.canvasHandleVisible,"aria-hidden":"true"})]}):null]},`hit-${e.id}`))}),j.filter(e=>e.kind==="arrow").map(e=>t.jsx("div",{className:`${a.annotationNumberBadge} ${a.arrowNumberBadge}`,style:{left:`${(e.x+e.x2)/2*100}%`,top:`${(e.y+e.y2)/2*100}%`,backgroundColor:ra(e)},"aria-hidden":"true",children:Zt.get(e.id)||0},`arrow-number-${e.id}`)),j.filter(e=>e.kind!=="arrow").map(e=>{const n=ra(e),o=Zt.get(e.id)||0,i={onPointerDown:c=>La(c,e),onClick:c=>{c.stopPropagation(),F(e.id)}};return e.kind==="pin"?t.jsx("button",{type:"button",...i,className:`${a.pin} ${S===e.id?a.selected:""}`,style:{left:`${e.x*100}%`,top:`${e.y*100}%`,backgroundColor:n},children:o},e.id):e.kind==="text-note"?t.jsx("button",{type:"button",...i,className:`${a.note} ${S===e.id?a.selected:""}`,style:{left:`${e.x*100}%`,top:`${e.y*100}%`,backgroundColor:n},children:o},e.id):t.jsxs("div",{className:`${a.box} ${S===e.id?a.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:`${a.annotationNumberBadge} ${a.boxNumberBadge}`,style:{backgroundColor:n},"aria-hidden":"true",children:o}),t.jsx("button",{type:"button",...i,className:a.boxSurface,"aria-label":`Box marker ${o}`})]},e.id)}),t.jsx("svg",{className:a.overlaySvg,viewBox:qt,preserveAspectRatio:"none",children:j.filter(e=>e.kind==="box"&&S===e.id).map(e=>t.jsxs(ta.Fragment,{children:[t.jsx("circle",{cx:(e.x+e.width)*y.width,cy:(e.y+e.height)*y.height,r:De.hitRadius,className:`${a.canvasHandleHit} ${a.canvasResizeHandleHit}`,role:"button",tabIndex:0,"aria-label":"Resize box marker",onPointerDown:n=>Rn(n,e)}),t.jsx("circle",{cx:(e.x+e.width)*y.width,cy:(e.y+e.height)*y.height,r:De.visibleRadius,className:a.canvasHandleVisible,"aria-hidden":"true"})]},`box-handle-${e.id}`))})]}):null]})})]}):t.jsx("div",{className:a.emptyState,children:t.jsx("p",{className:"tf-empty-copy",children:"Select an image attachment to annotate."})})})})]}),t.jsxs("div",{className:a.statusBar,children:[t.jsx("span",{children:Y==="error"?"Save failed. Retry now.":Y==="saving"||Y==="pending"?"Saving…":"Saved"}),ua?t.jsx("span",{children:ua}):t.jsx("span",{children:B?W&&ke?"Drag on the image to pan.":T==="select"?"Select a marker to edit it.":`Click on the image to place a ${T}.`:"Create a session to begin placing markers on this image."}),d&&!B&&an?t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>{Ba()},disabled:V||pt,children:V?"Creating…":"Create one now"}):null]})]}),t.jsx("section",{className:`tf-surface-panel ${a.panel} ${a.detailPanel}`,children:B?t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:a.panelHeader,children:[t.jsxs("div",{className:a.panelHeaderText,children:[t.jsx("div",{className:`tf-heading-card ${a.panelTitle}`,children:"Markers"}),t.jsxs("div",{className:"tf-text-secondary",children:[j.length," in this session"]})]}),t.jsxs("div",{className:a.sessionActions,children:[t.jsx("button",{type:"button",className:`tf-control-icon ${a.iconButton}`,onClick:()=>Ea("up"),disabled:!z||Xt<=0,"aria-label":"Move marker up",children:t.jsx(Kn,{size:16})}),t.jsx("button",{type:"button",className:`tf-control-icon ${a.iconButton}`,onClick:()=>Ea("down"),disabled:!z||Xt===-1||Xt>=j.length-1,"aria-label":"Move marker down",children:t.jsx(Xn,{size:16})})]})]}),t.jsx("div",{className:`tf-scrollbar ${a.annotationList}`,children:j.length===0?t.jsx("div",{className:a.detailEmpty,children:t.jsx("p",{className:"tf-empty-copy",children:"Add a marker on the image to begin."})}):j.map((e,n)=>{const o=ko[e.kind],i=wo[e.kind],c=`${i} ${n+1}`,h=So[e.markerType||ce],k=Ya.find(m=>m.value===(e.markerType||ce))?.label||"Review",u=S===e.id;return t.jsxs("div",{className:`tf-surface-elevated ${a.annotationCard} ${u?a.annotationCardActive:""}`,children:[t.jsx("button",{type:"button",className:a.annotationCardButton,onClick:()=>F(e.id),"aria-label":i,children:t.jsxs("div",{className:a.annotationMeta,children:[t.jsx("span",{className:`tf-heading-card ${a.annotationTitle}`,children:c}),t.jsx("span",{className:`tf-text-meta ${a.annotationKind}`,"aria-hidden":"true",children:t.jsx(o,{size:16})})]})}),u?t.jsxs("div",{className:a.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?_a:null,className:`tf-field-shell ${a.textArea} ${a.annotationInstructionField}`,value:e.instruction,onChange:m=>{te(e.id,_=>({..._,instruction:m.target.value}),600)},onBlur:()=>{E("text")},placeholder:"What should the AI focus on for this marker?"}),t.jsxs("div",{className:a.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 ${a.select}`,value:e.markerType||ce,onChange:m=>{te(e.id,_=>Po(_,m.target.value))},children:Ya.map(m=>t.jsx("option",{value:m.value,children:m.label},m.value))})]})]}):t.jsx("button",{type:"button",className:a.annotationInstructionButton,onClick:()=>F(e.id),"aria-label":`Edit ${i} instruction`,children:t.jsxs("div",{className:a.annotationInstructionEditor,children:[t.jsx("div",{className:`tf-text-secondary ${a.annotationInstructionPreview}`,children:e.instruction.trim()||"No marker instruction yet."}),t.jsx("div",{className:a.annotationPreviewFooter,children:t.jsx("span",{className:`tf-text-meta ${a.annotationInstructionTypeIcon}`,"aria-label":`Instruction type: ${k}`,title:k,children:t.jsx(h,{size:16})})})]})})]},e.id)})})]}):t.jsxs("div",{className:a.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(aa,{isOpen:rn,onClose:()=>{qe||(xt(!1),vt(""))},title:"Open Image",size:"sm",children:t.jsxs("form",{className:a.openImageModalBody,onSubmit:e=>{e.preventDefault(),mn()},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 ${a.textInput} ${a.openImageField}`,value:Dt,onChange:e=>vt(e.target.value),placeholder:"I-24",autoFocus:!0}),t.jsxs("div",{className:a.openImageActions,children:[t.jsx("button",{type:"button",className:"tf-button-ghost tf-button-compact",onClick:()=>{xt(!1),vt("")},disabled:qe,children:"Cancel"}),t.jsx("button",{type:"submit",className:"tf-button-primary tf-button-compact",disabled:qe,children:qe?"Opening…":"Open"})]})]})}),t.jsx(aa,{isOpen:sn,onClose:()=>ha(!1),title:"How To Use Markers",size:"md",children:t.jsx("div",{className:a.markerHelpModalBody,children:Wa.filter(e=>e.value!=="select").map(e=>t.jsxs("div",{className:`tf-surface-inset ${a.markerHelpItem}`,children:[t.jsxs("div",{className:a.markerHelpHeader,children:[t.jsx("strong",{className:"tf-heading-card",children:e.label}),t.jsx("span",{className:"tf-text-meta",children:Rt[e.value].short})]}),t.jsx("p",{className:"tf-text-secondary",children:Rt[e.value].detail}),t.jsx("p",{className:`tf-text-body ${a.markerHelpExample}`,children:Rt[e.value].example})]},e.value))})}),t.jsx(aa,{isOpen:nn,onClose:()=>Lt(!1),title:"AI Payload",size:"lg",children:t.jsxs("div",{className:a.payloadModalBody,children:[t.jsxs("div",{className:a.payloadModalToolbar,children:[t.jsxs("div",{className:a.payloadViewToggle,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.toolBtn} ${Ft==="json"?a.toolBtnActive:""}`,onClick:()=>fa("json"),children:"JSON"}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.toolBtn} ${Ft==="brief"?a.toolBtnActive:""}`,onClick:()=>fa("brief"),children:"AI Brief"})]}),t.jsxs("div",{className:a.payloadModalActions,children:[t.jsxs("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn}`,onClick:()=>{Ma("json")},disabled:!oe,"aria-label":de==="json"?"Copied JSON":"Copy JSON",title:de==="json"?"Copied JSON":"Copy JSON",children:[de==="json"?t.jsx(la,{size:16}):t.jsx(na,{size:16}),t.jsx("span",{children:"JSON"})]}),t.jsxs("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn}`,onClick:()=>{Ma("brief")},disabled:!oe,"aria-label":de==="brief"?"Copied AI Brief":"Copy AI Brief",title:de==="brief"?"Copied AI Brief":"Copy AI Brief",children:[de==="brief"?t.jsx(la,{size:16}):t.jsx(na,{size:16}),t.jsx("span",{children:"AI Brief"})]})]})]}),gt?t.jsx("div",{className:a.detailEmpty,children:t.jsx("p",{className:"tf-empty-copy",children:"Loading payload…"})}):oe?t.jsx("pre",{className:`tf-surface-inset tf-scrollbar ${a.payloadModalPreview}`,children:Ft==="json"?JSON.stringify(oe,null,2):Xa(oe)}):t.jsx("div",{className:a.detailEmpty,children:t.jsx("p",{className:"tf-empty-copy",children:"Load a payload preview to inspect the current session contract."})})]})})]})}export{Vo as AnnotatedAttachmentWorkspaceShell};
@@ -1 +0,0 @@
1
- import{a2 as t}from"./index-DUt7ifSO.js";const n="D-";function E(e){return t(n,e)}export{E as g};