@taskforcehq/taskforce 0.3.313 → 0.3.315
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compat/workspaceSyncCompat.js +2 -0
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +51 -11
- package/dist/core/PlanEntitlementService.d.ts +2 -0
- package/dist/core/PlanEntitlementService.js +26 -9
- package/dist/core/Taskforce.d.ts +5 -0
- package/dist/core/Taskforce.js +16 -2
- package/dist/core/types.d.ts +1 -0
- package/dist/hooks/sync/recovery.d.ts +1 -0
- package/dist/hooks/sync/recovery.js +1 -0
- package/dist/hooks/sync/transfers.d.ts +2 -0
- package/dist/hooks/sync/transfers.js +3 -0
- package/dist/hooks/useSyncOrchestrator.js +9 -1
- package/dist/hooks/useTaskforce.js +13 -6
- package/dist/hooks/useWorkspaceSyncController.js +3 -0
- package/dist/mcp/canonicalAssetHelpers.js +33 -19
- package/dist/mcp/documentAssetRegistrar.js +6 -6
- package/dist/mcp/runtime.js +83 -11
- package/dist/mcp/taskAttachmentHelpers.js +29 -7
- package/dist/migrations/taskSchemaMigrations.js +4 -0
- package/dist/server/index.js +1 -1
- package/dist/server/routes/admin.js +10 -0
- package/dist/server/routes/billing.js +3 -0
- package/dist/server/routes/documents.js +2 -1
- package/dist/server/routes.js +10 -6
- package/dist/storage/documentIntegrity.js +1 -6
- package/dist/storage/documentPurge.js +11 -12
- package/dist/sync/workspaceRepair.js +11 -9
- package/dist/sync/workspaceSyncState.d.ts +1 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/ui/.well-known/mcp-registry-auth +1 -0
- package/dist/ui/assets/{AgentsModule-BULqIo8e.js → AgentsModule-CNBWCIXk.js} +1 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-9qxB0e6A.js +3 -0
- package/dist/ui/assets/{ContextAttachmentManager-DoMe2OpY.js → ContextAttachmentManager-CRyuFYlg.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-DumBWhvb.js → DocumentWorkspace-Dx7wb9NF.js} +1 -1
- package/dist/ui/assets/{EntityActivityTimeline-sHk8Nzbq.js → EntityActivityTimeline-Cmal2OrJ.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-3X9UNzHh.js → InitiativesModule-wzbYPQQL.js} +1 -1
- package/dist/ui/assets/{PlansPage-C3A3EYaV.js → PlansPage-BAnSfbTf.js} +1 -1
- package/dist/ui/assets/{TaskContextUpload-C8G7pWkX.js → TaskContextUpload-DENyMyUk.js} +1 -1
- package/dist/ui/assets/{TaskSettings-DM1o9Wla.js → TaskSettings-DASuVwpY.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-CW0crzvD.js → WorkflowsModule-DJMEA_yt.js} +1 -1
- package/dist/ui/assets/documentReferences-BhNx80zO.js +1 -0
- package/dist/ui/assets/index-CWg2olz9.js +5 -0
- package/dist/ui/index.html +1 -1
- package/dist/utils/pathContainment.d.ts +7 -0
- package/dist/utils/pathContainment.js +53 -0
- package/dist/utils/pathSafety.d.ts +6 -0
- package/dist/utils/pathSafety.js +73 -0
- package/package.json +3 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-DHr4rHK9.js +0 -3
- package/dist/ui/assets/documentReferences-BZB3nMRb.js +0 -1
- package/dist/ui/assets/index-BWFQ9mGn.js +0 -5
|
@@ -93,6 +93,7 @@ export function normalizeWorkspaceSyncStateCompat(rawState) {
|
|
|
93
93
|
enabled: Boolean(state.enabled ?? state.cloudSyncEnabled),
|
|
94
94
|
phase: 'idle',
|
|
95
95
|
setupIntent: normalizeSetupIntent(state.setupIntent),
|
|
96
|
+
startupFullReconcileCompletedAt: normalizeTimestamp(state.startupFullReconcileCompletedAt),
|
|
96
97
|
pullCursor: normalizeTimestamp(state.pullCursor),
|
|
97
98
|
lastPullAt: normalizeTimestamp(state.lastPullAt),
|
|
98
99
|
lastPushAt: normalizeTimestamp(state.lastPushAt),
|
|
@@ -116,6 +117,7 @@ export function normalizeWorkspaceSyncStateCompat(rawState) {
|
|
|
116
117
|
|| Boolean(state.enabled ?? state.cloudSyncEnabled) !== normalized.enabled
|
|
117
118
|
|| String(state.phase || '').trim() !== normalized.phase
|
|
118
119
|
|| normalizeSetupIntent(state.setupIntent) !== normalized.setupIntent
|
|
120
|
+
|| normalizeTimestamp(state.startupFullReconcileCompletedAt) !== normalized.startupFullReconcileCompletedAt
|
|
119
121
|
|| normalizeTimestamp(state.pullCursor) !== normalized.pullCursor
|
|
120
122
|
|| normalizeTimestamp(state.lastPullAt) !== normalized.lastPullAt
|
|
121
123
|
|| normalizeTimestamp(state.lastPushAt) !== normalized.lastPushAt
|
|
@@ -118,6 +118,14 @@ function isEditableKeyboardTarget(target) {
|
|
|
118
118
|
return true;
|
|
119
119
|
return tagName === 'input' || tagName === 'textarea' || tagName === 'select';
|
|
120
120
|
}
|
|
121
|
+
function isInteractiveKeyboardTarget(target) {
|
|
122
|
+
if (!(target instanceof HTMLElement))
|
|
123
|
+
return false;
|
|
124
|
+
if (isEditableKeyboardTarget(target))
|
|
125
|
+
return true;
|
|
126
|
+
const tagName = target.tagName.toLowerCase();
|
|
127
|
+
return tagName === 'button' || tagName === 'a' || target.getAttribute('role') === 'button';
|
|
128
|
+
}
|
|
121
129
|
function normalizeOrder(annotations) {
|
|
122
130
|
return annotations.map((annotation, index) => ({
|
|
123
131
|
...annotation,
|
|
@@ -438,6 +446,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
438
446
|
const [imageLoadError, setImageLoadError] = useState(false);
|
|
439
447
|
const [zoomLevel, setZoomLevel] = useState(1);
|
|
440
448
|
const [panMode, setPanMode] = useState(false);
|
|
449
|
+
const [spacePanActive, setSpacePanActive] = useState(false);
|
|
441
450
|
const [pastingImage, setPastingImage] = useState(false);
|
|
442
451
|
const [naturalImageSize, setNaturalImageSize] = useState({ width: 0, height: 0 });
|
|
443
452
|
const overlayRef = useRef(null);
|
|
@@ -542,6 +551,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
542
551
|
|| canvasMediaSize.height > (scroller.clientHeight + 1)));
|
|
543
552
|
}, [canvasMediaSize.height, canvasMediaSize.width, zoomLevel]);
|
|
544
553
|
const zoomLabel = `${Math.round(zoomLevel * 100)}%`;
|
|
554
|
+
const isPanActive = canPan && (panMode || spacePanActive);
|
|
545
555
|
const annotationOrderNumbers = useMemo(() => {
|
|
546
556
|
const orderMap = new Map();
|
|
547
557
|
draftAnnotations.forEach((annotation, index) => {
|
|
@@ -1232,6 +1242,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1232
1242
|
setNaturalImageSize({ width: 0, height: 0 });
|
|
1233
1243
|
setZoomLevel(1);
|
|
1234
1244
|
setPanMode(false);
|
|
1245
|
+
setSpacePanActive(false);
|
|
1235
1246
|
}, [activeTarget?.path]);
|
|
1236
1247
|
useEffect(() => {
|
|
1237
1248
|
if (!imageLoading)
|
|
@@ -1250,6 +1261,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1250
1261
|
if (canPan)
|
|
1251
1262
|
return;
|
|
1252
1263
|
setPanMode(false);
|
|
1264
|
+
setSpacePanActive(false);
|
|
1253
1265
|
}, [canPan]);
|
|
1254
1266
|
useEffect(() => {
|
|
1255
1267
|
setPayloadPreview(null);
|
|
@@ -1657,6 +1669,34 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1657
1669
|
window.addEventListener('keydown', handleKeyDown);
|
|
1658
1670
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
1659
1671
|
}, [deleteSelectedAnnotation, selectedAnnotationId]);
|
|
1672
|
+
useEffect(() => {
|
|
1673
|
+
const handleKeyDown = (event) => {
|
|
1674
|
+
if (event.code !== 'Space')
|
|
1675
|
+
return;
|
|
1676
|
+
if (!canPan)
|
|
1677
|
+
return;
|
|
1678
|
+
if (isInteractiveKeyboardTarget(event.target))
|
|
1679
|
+
return;
|
|
1680
|
+
event.preventDefault();
|
|
1681
|
+
setSpacePanActive(true);
|
|
1682
|
+
};
|
|
1683
|
+
const handleKeyUp = (event) => {
|
|
1684
|
+
if (event.code !== 'Space')
|
|
1685
|
+
return;
|
|
1686
|
+
setSpacePanActive(false);
|
|
1687
|
+
};
|
|
1688
|
+
const handleWindowBlur = () => {
|
|
1689
|
+
setSpacePanActive(false);
|
|
1690
|
+
};
|
|
1691
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
1692
|
+
window.addEventListener('keyup', handleKeyUp);
|
|
1693
|
+
window.addEventListener('blur', handleWindowBlur);
|
|
1694
|
+
return () => {
|
|
1695
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
1696
|
+
window.removeEventListener('keyup', handleKeyUp);
|
|
1697
|
+
window.removeEventListener('blur', handleWindowBlur);
|
|
1698
|
+
};
|
|
1699
|
+
}, [canPan]);
|
|
1660
1700
|
const readPoint = useCallback((event) => {
|
|
1661
1701
|
const rect = overlayRef.current?.getBoundingClientRect();
|
|
1662
1702
|
if (!rect || rect.width <= 0 || rect.height <= 0)
|
|
@@ -1667,7 +1707,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1667
1707
|
};
|
|
1668
1708
|
}, []);
|
|
1669
1709
|
const handleCanvasPointerDown = useCallback((event) => {
|
|
1670
|
-
if (
|
|
1710
|
+
if (isPanActive) {
|
|
1671
1711
|
const scroller = canvasScrollerRef.current;
|
|
1672
1712
|
if (!scroller)
|
|
1673
1713
|
return;
|
|
@@ -1709,7 +1749,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1709
1749
|
setDraftAnnotations((current) => normalizeOrder([...current, annotation]));
|
|
1710
1750
|
setSelectedAnnotationId(annotation.id);
|
|
1711
1751
|
markDraftChanged(300);
|
|
1712
|
-
}, [
|
|
1752
|
+
}, [defaultMarkerColor, isPanActive, markDraftChanged, readPoint, selectedSessionId, toolMode]);
|
|
1713
1753
|
const handleCanvasPointerUp = useCallback((event) => {
|
|
1714
1754
|
if (panRef.current?.pointerId === event.pointerId) {
|
|
1715
1755
|
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
@@ -1726,7 +1766,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1726
1766
|
setToolMode('select');
|
|
1727
1767
|
}, [readPoint, toolMode]);
|
|
1728
1768
|
const startDragAnnotation = useCallback((event, annotation) => {
|
|
1729
|
-
if (toolMode !== 'select' ||
|
|
1769
|
+
if (toolMode !== 'select' || isPanActive)
|
|
1730
1770
|
return;
|
|
1731
1771
|
const point = readPoint(event);
|
|
1732
1772
|
if (!point)
|
|
@@ -1738,7 +1778,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1738
1778
|
};
|
|
1739
1779
|
setSelectedAnnotationId(annotation.id);
|
|
1740
1780
|
event.stopPropagation();
|
|
1741
|
-
}, [
|
|
1781
|
+
}, [isPanActive, readPoint, toolMode]);
|
|
1742
1782
|
const handleCanvasPointerMove = useCallback((event) => {
|
|
1743
1783
|
if (panRef.current?.pointerId === event.pointerId) {
|
|
1744
1784
|
const scroller = canvasScrollerRef.current;
|
|
@@ -1872,7 +1912,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1872
1912
|
panRef.current = null;
|
|
1873
1913
|
}, []);
|
|
1874
1914
|
const startResizeBox = useCallback((event, annotation) => {
|
|
1875
|
-
if (toolMode !== 'select' ||
|
|
1915
|
+
if (toolMode !== 'select' || isPanActive)
|
|
1876
1916
|
return;
|
|
1877
1917
|
const point = readPoint(event);
|
|
1878
1918
|
if (!point)
|
|
@@ -1884,9 +1924,9 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1884
1924
|
};
|
|
1885
1925
|
setSelectedAnnotationId(annotation.id);
|
|
1886
1926
|
event.stopPropagation();
|
|
1887
|
-
}, [
|
|
1927
|
+
}, [isPanActive, readPoint, toolMode]);
|
|
1888
1928
|
const startArrowHandleDrag = useCallback((event, annotation, endpoint) => {
|
|
1889
|
-
if (toolMode !== 'select' ||
|
|
1929
|
+
if (toolMode !== 'select' || isPanActive)
|
|
1890
1930
|
return;
|
|
1891
1931
|
const point = readPoint(event);
|
|
1892
1932
|
if (!point)
|
|
@@ -1899,7 +1939,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1899
1939
|
};
|
|
1900
1940
|
setSelectedAnnotationId(annotation.id);
|
|
1901
1941
|
event.stopPropagation();
|
|
1902
|
-
}, [
|
|
1942
|
+
}, [isPanActive, readPoint, toolMode]);
|
|
1903
1943
|
const activeSessionTimestamp = selectedSession?.updatedAt ? formatTimestamp(selectedSession.updatedAt) : 'Not saved yet';
|
|
1904
1944
|
return (_jsxs("div", { className: `${styles.shell} ${showImageTray ? styles.shellWithImageTray : ''} ${showImageTray && imageTrayOpen ? styles.shellImageTrayOpen : ''}`.trim(), children: [showImageTray ? (_jsxs("aside", { className: `${styles.imageTrayPanel} ${imageTrayOpen ? styles.imageTrayPanelOpen : ''}`.trim(), "aria-label": "Image tray", "aria-hidden": !imageTrayOpen, children: [_jsxs("div", { className: styles.imageTrayHeader, children: [_jsxs("span", { className: styles.imageTrayTitle, children: [_jsx(FolderOpen, { size: 14 }), "Images"] }), _jsx("button", { type: "button", className: "tf-control-icon", onClick: onCloseImageTray, title: "Collapse image tray", "aria-label": "Collapse image tray", children: _jsx(ChevronLeft, { size: 16 }) })] }), _jsxs("div", { className: styles.imageTraySearch, children: [_jsx(Search, { size: 12, className: styles.imageTraySearchIcon }), _jsx("input", { type: "text", placeholder: "Search images...", value: imageTraySearchQuery, onChange: (event) => setImageTraySearchQuery(event.target.value), className: styles.imageTraySearchInput })] }), _jsx("div", { className: `tf-scrollbar ${styles.imageTrayList}`, children: imageTrayLoading ? (_jsx("div", { className: styles.imageTrayState, children: "Loading images..." })) : imageTrayError ? (_jsx("div", { className: styles.imageTrayStateError, children: imageTrayError })) : filteredImageTrayItems.length === 0 ? (_jsx("div", { className: styles.imageTrayState, children: imageTraySearchQuery.trim() ? 'No images match your search.' : 'No images found.' })) : filteredImageTrayItems.map((item) => {
|
|
1905
1945
|
const isSelected = activeTarget?.assetId === item.assetId;
|
|
@@ -1957,7 +1997,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1957
1997
|
if (!selectedSession)
|
|
1958
1998
|
return;
|
|
1959
1999
|
restoreLastSavedDraft();
|
|
1960
|
-
}, disabled: !selectedSession || !hasUnsavedChanges, "aria-label": "Reset unsaved changes", title: "Reset unsaved changes", children: _jsx(RotateCcw, { size: 18 }) }), _jsx("button", { type: "button", className: `tf-button-ghost ${styles.toolRailButton} ${styles.iconButton}`, onClick: zoomOut, disabled: !activeTarget || imageLoading || zoomLevel <= 0.25, "aria-label": "Zoom out", children: _jsx(ZoomOut, { size: 18 }) }), _jsx("button", { type: "button", className: `tf-button-ghost tf-button-compact ${styles.ghostBtn} ${styles.toolbarButton}`, onClick: resetZoom, disabled: !activeTarget || imageLoading || zoomLevel === 1, "aria-label": `Reset zoom to 100 percent (currently ${zoomLabel})`, title: `Reset zoom to 100% (currently ${zoomLabel})`, children: _jsx("span", { children: zoomLabel }) }), _jsx("button", { type: "button", className: `tf-button-ghost ${styles.toolRailButton} ${styles.iconButton}`, onClick: zoomIn, disabled: !activeTarget || imageLoading || zoomLevel >= 4, "aria-label": "Zoom in", children: _jsx(ZoomIn, { size: 18 }) }), _jsx("button", { type: "button", className: `tf-button-ghost tf-button-compact ${styles.toolBtn} ${styles.toolbarButton} ${
|
|
2000
|
+
}, disabled: !selectedSession || !hasUnsavedChanges, "aria-label": "Reset unsaved changes", title: "Reset unsaved changes", children: _jsx(RotateCcw, { size: 18 }) }), _jsx("button", { type: "button", className: `tf-button-ghost ${styles.toolRailButton} ${styles.iconButton}`, onClick: zoomOut, disabled: !activeTarget || imageLoading || zoomLevel <= 0.25, "aria-label": "Zoom out", children: _jsx(ZoomOut, { size: 18 }) }), _jsx("button", { type: "button", className: `tf-button-ghost tf-button-compact ${styles.ghostBtn} ${styles.toolbarButton}`, onClick: resetZoom, disabled: !activeTarget || imageLoading || zoomLevel === 1, "aria-label": `Reset zoom to 100 percent (currently ${zoomLabel})`, title: `Reset zoom to 100% (currently ${zoomLabel})`, children: _jsx("span", { children: zoomLabel }) }), _jsx("button", { type: "button", className: `tf-button-ghost ${styles.toolRailButton} ${styles.iconButton}`, onClick: zoomIn, disabled: !activeTarget || imageLoading || zoomLevel >= 4, "aria-label": "Zoom in", children: _jsx(ZoomIn, { size: 18 }) }), _jsx("button", { type: "button", className: `tf-button-ghost tf-button-compact ${styles.toolBtn} ${styles.toolbarButton} ${isPanActive ? styles.toolBtnActive : ''}`, onClick: () => setPanMode((current) => !current), disabled: !activeTarget || imageLoading || !canPan, "aria-label": "Pan canvas", title: "Pan canvas", children: _jsx(Hand, { size: 18 }) })] }), _jsx("span", { className: styles.toolbarSeparator, "aria-hidden": "true" }), _jsxs("div", { className: styles.toolbarActions, children: [_jsx("div", { className: styles.toolbarSelectionActions, children: selectedAnnotation ? (_jsxs(_Fragment, { children: [_jsxs("div", { ref: colorPickerRef, className: styles.toolbarColorPicker, children: [_jsx("button", { type: "button", className: `tf-button-ghost tf-button-compact ${styles.ghostBtn} ${styles.toolbarButton} ${styles.colorPickerButton}`, onClick: () => setColorPickerOpen((current) => !current), "aria-label": "Marker color", "aria-expanded": colorPickerOpen, title: "Marker color", children: _jsx("span", { className: styles.colorPickerSwatch, style: { backgroundColor: selectedMarkerColor }, "aria-hidden": "true" }) }), colorPickerOpen ? (_jsx("div", { className: styles.colorPickerPopover, role: "menu", "aria-label": "Marker color options", children: MARKER_COLOR_SWATCHES.map((color) => (_jsx("button", { type: "button", className: `${styles.colorOption} ${selectedMarkerColor === color ? styles.colorOptionActive : ''}`, style: { backgroundColor: color }, onClick: () => applyMarkerColor(color), "aria-label": `Use marker color ${color}`, "aria-pressed": selectedMarkerColor === color }, color))) })) : null] }), _jsx("button", { type: "button", className: `tf-button-ghost tf-button-compact ${styles.ghostBtn} ${styles.toolbarButton}`, onClick: deleteSelectedAnnotation, "aria-label": "Delete marker", title: "Delete selected marker", children: _jsx(Trash2, { size: 18 }) }), _jsx("div", { className: styles.toolbarGeometryFields, "aria-label": "Marker geometry percent controls", children: getGeometryFields(selectedAnnotation).map((field) => (_jsxs("label", { className: styles.toolbarGeometryField, children: [_jsx("span", { className: `tf-text-meta ${styles.toolbarGeometryLabel}`, children: field.label }), _jsx("input", { className: `tf-field-shell ${styles.toolbarGeometryInput}`, type: "number", min: 0, max: 100, step: 0.1, value: field.value, onChange: (event) => {
|
|
1961
2001
|
const nextValue = parsePercentField(event.target.value);
|
|
1962
2002
|
if (nextValue === null)
|
|
1963
2003
|
return;
|
|
@@ -1980,7 +2020,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
1980
2020
|
setNaturalImageSize({ width: 0, height: 0 });
|
|
1981
2021
|
setImageLoading(false);
|
|
1982
2022
|
setImageLoadError(true);
|
|
1983
|
-
} }), !imageLoading && !imageLoadError && selectedSession ? (_jsxs("div", { ref: overlayRef, className: `${styles.overlay} ${toolMode === 'select' ? styles.overlaySelect : ''} ${
|
|
2023
|
+
} }), !imageLoading && !imageLoadError && selectedSession ? (_jsxs("div", { ref: overlayRef, className: `${styles.overlay} ${toolMode === 'select' ? styles.overlaySelect : ''} ${isPanActive ? styles.overlayPan : ''}`, onPointerDown: handleCanvasPointerDown, onPointerMove: handleCanvasPointerMove, onPointerUp: (event) => {
|
|
1984
2024
|
handleCanvasPointerUp(event);
|
|
1985
2025
|
handleCanvasPointerEnd();
|
|
1986
2026
|
}, "data-testid": "annotated-attachment-overlay", onPointerLeave: handleCanvasPointerLeave, onPointerCancel: handleCanvasPointerCancel, children: [_jsx("svg", { className: styles.overlaySvg, viewBox: arrowOverlayViewBox, preserveAspectRatio: "none", "aria-hidden": "true", children: draftAnnotations.filter((annotation) => annotation.kind === 'arrow').map((annotation) => {
|
|
@@ -2022,7 +2062,7 @@ export function AnnotatedAttachmentWorkspaceShell({ runtimeMode = 'local', apiBa
|
|
|
2022
2062
|
? 'Save failed. Retry now.'
|
|
2023
2063
|
: saveState === 'saving' || saveState === 'pending'
|
|
2024
2064
|
? 'Saving…'
|
|
2025
|
-
: 'Saved' }), error ? _jsx("span", { children: error }) : _jsx("span", { children: !selectedSession ? 'Create a session to begin placing markers on this image.' :
|
|
2065
|
+
: 'Saved' }), error ? _jsx("span", { children: error }) : _jsx("span", { children: !selectedSession ? 'Create a session to begin placing markers on this image.' : isPanActive ? 'Drag on the image to pan.' : toolMode === 'select' ? 'Select a marker to edit it.' : `Click on the image to place a ${toolMode}.` }), activeTarget && !selectedSession && showCreateSessionPrompt ? (_jsx("button", { type: "button", className: `tf-button-ghost tf-button-compact ${styles.ghostBtn} ${styles.toolbarButton}`, onClick: () => void createSession(), disabled: saving || loading, children: saving ? 'Creating…' : 'Create one now' })) : null] })] }), _jsx("section", { className: `tf-surface-panel ${styles.panel} ${styles.detailPanel}`, children: selectedSession ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.panelHeader, children: [_jsxs("div", { className: styles.panelHeaderText, children: [_jsx("div", { className: `tf-heading-card ${styles.panelTitle}`, children: "Markers" }), _jsxs("div", { className: "tf-text-secondary", children: [draftAnnotations.length, " in this session"] })] }), _jsxs("div", { className: styles.sessionActions, children: [_jsx("button", { type: "button", className: `tf-control-icon ${styles.iconButton}`, onClick: () => moveSelectedAnnotation('up'), disabled: !selectedAnnotation || selectedAnnotationIndex <= 0, "aria-label": "Move marker up", children: _jsx(ArrowUp, { size: 16 }) }), _jsx("button", { type: "button", className: `tf-control-icon ${styles.iconButton}`, onClick: () => moveSelectedAnnotation('down'), disabled: !selectedAnnotation || selectedAnnotationIndex === -1 || selectedAnnotationIndex >= draftAnnotations.length - 1, "aria-label": "Move marker down", children: _jsx(ArrowDown, { size: 16 }) })] })] }), _jsx("div", { className: `tf-scrollbar ${styles.annotationList}`, children: draftAnnotations.length === 0 ? (_jsx("div", { className: styles.detailEmpty, children: _jsx("p", { className: "tf-empty-copy", children: "Add a marker on the image to begin." }) })) : draftAnnotations.map((annotation, index) => {
|
|
2026
2066
|
const AnnotationKindIcon = ANNOTATION_KIND_ICON[annotation.kind];
|
|
2027
2067
|
const annotationKindLabel = ANNOTATION_KIND_LABELS[annotation.kind];
|
|
2028
2068
|
const annotationSequenceLabel = `${annotationKindLabel} ${index + 1}`;
|
|
@@ -184,6 +184,7 @@ export declare class PlanEntitlementService {
|
|
|
184
184
|
createCustomPlanFeatureCatalogEntry(input: {
|
|
185
185
|
label: string;
|
|
186
186
|
description?: string | null;
|
|
187
|
+
publicVisible?: boolean | null;
|
|
187
188
|
publicLabel?: string | null;
|
|
188
189
|
publicDescription?: string | null;
|
|
189
190
|
publicDescriptionVisible?: boolean | null;
|
|
@@ -194,6 +195,7 @@ export declare class PlanEntitlementService {
|
|
|
194
195
|
featureKey: string;
|
|
195
196
|
label?: string | null;
|
|
196
197
|
description?: string | null;
|
|
198
|
+
publicVisible?: boolean | null;
|
|
197
199
|
publicLabel?: string | null;
|
|
198
200
|
publicDescription?: string | null;
|
|
199
201
|
publicDescriptionVisible?: boolean | null;
|
|
@@ -92,7 +92,7 @@ export class PlanEntitlementService {
|
|
|
92
92
|
}
|
|
93
93
|
listCustomPlanFeatureCatalogEntries() {
|
|
94
94
|
const rows = this.db.prepare(`
|
|
95
|
-
SELECT feature_key, label, description, public_label, public_description, public_description_visible, public_display_order
|
|
95
|
+
SELECT feature_key, label, description, public_visible, public_label, public_description, public_description_visible, public_display_order
|
|
96
96
|
FROM plan_feature_catalog_overrides
|
|
97
97
|
WHERE tenant_id = ? AND is_custom = 1
|
|
98
98
|
ORDER BY updated_at ASC, created_at ASC, feature_key ASC
|
|
@@ -108,6 +108,7 @@ export class PlanEntitlementService {
|
|
|
108
108
|
label,
|
|
109
109
|
description: this.normalizeRequiredCatalogLabel(row.description),
|
|
110
110
|
isCustom: true,
|
|
111
|
+
publicVisible: this.normalizeOptionalCatalogDescriptionVisible(row.public_visible),
|
|
111
112
|
publicLabel: this.normalizeOptionalCatalogMarketingCopy(row.public_label),
|
|
112
113
|
publicDescription: this.normalizeOptionalCatalogMarketingCopy(row.public_description),
|
|
113
114
|
publicDescriptionVisible: this.normalizeOptionalCatalogDescriptionVisible(row.public_description_visible),
|
|
@@ -123,7 +124,7 @@ export class PlanEntitlementService {
|
|
|
123
124
|
if (!featureKey)
|
|
124
125
|
return null;
|
|
125
126
|
const row = this.db.prepare(`
|
|
126
|
-
SELECT feature_key, label, description, public_label, public_description, public_description_visible, public_display_order
|
|
127
|
+
SELECT feature_key, label, description, public_visible, public_label, public_description, public_description_visible, public_display_order
|
|
127
128
|
FROM plan_feature_catalog_overrides
|
|
128
129
|
WHERE tenant_id = ? AND feature_key = ? AND is_custom = 1
|
|
129
130
|
LIMIT 1
|
|
@@ -138,6 +139,7 @@ export class PlanEntitlementService {
|
|
|
138
139
|
label,
|
|
139
140
|
description: this.normalizeRequiredCatalogLabel(row.description),
|
|
140
141
|
isCustom: true,
|
|
142
|
+
publicVisible: this.normalizeOptionalCatalogDescriptionVisible(row.public_visible),
|
|
141
143
|
publicLabel: this.normalizeOptionalCatalogMarketingCopy(row.public_label),
|
|
142
144
|
publicDescription: this.normalizeOptionalCatalogMarketingCopy(row.public_description),
|
|
143
145
|
publicDescriptionVisible: this.normalizeOptionalCatalogDescriptionVisible(row.public_description_visible),
|
|
@@ -148,7 +150,7 @@ export class PlanEntitlementService {
|
|
|
148
150
|
}
|
|
149
151
|
listPlanFeatureCatalogOverrideMap() {
|
|
150
152
|
const rows = this.db.prepare(`
|
|
151
|
-
SELECT feature_key, public_label, public_description, public_description_visible, public_display_order, is_custom
|
|
153
|
+
SELECT feature_key, public_visible, public_label, public_description, public_description_visible, public_display_order, is_custom
|
|
152
154
|
FROM plan_feature_catalog_overrides
|
|
153
155
|
WHERE tenant_id = ?
|
|
154
156
|
`).all(this.tenantId);
|
|
@@ -164,6 +166,7 @@ export class PlanEntitlementService {
|
|
|
164
166
|
continue;
|
|
165
167
|
}
|
|
166
168
|
overrides.set(featureKey, {
|
|
169
|
+
publicVisible: this.normalizeOptionalCatalogDescriptionVisible(row.public_visible),
|
|
167
170
|
publicLabel: this.normalizeOptionalCatalogMarketingCopy(row.public_label),
|
|
168
171
|
publicDescription: this.normalizeOptionalCatalogMarketingCopy(row.public_description),
|
|
169
172
|
publicDescriptionVisible: this.normalizeOptionalCatalogDescriptionVisible(row.public_description_visible),
|
|
@@ -255,12 +258,17 @@ export class PlanEntitlementService {
|
|
|
255
258
|
};
|
|
256
259
|
}
|
|
257
260
|
if (normalizedFeatureKey === AI_PROFILES_FEATURE_KEY) {
|
|
261
|
+
const limit = this.readPositiveIntegerConfig(config[AI_PROFILES_LIMIT_CONFIG_KEY], 1);
|
|
258
262
|
if (access === 'limited') {
|
|
259
|
-
const limit = this.readPositiveIntegerConfig(config[AI_PROFILES_LIMIT_CONFIG_KEY], 1);
|
|
260
263
|
return {
|
|
261
264
|
[AI_PROFILES_LIMIT_CONFIG_KEY]: limit ?? 1
|
|
262
265
|
};
|
|
263
266
|
}
|
|
267
|
+
if (limit !== null) {
|
|
268
|
+
return {
|
|
269
|
+
[AI_PROFILES_LIMIT_CONFIG_KEY]: limit
|
|
270
|
+
};
|
|
271
|
+
}
|
|
264
272
|
return {};
|
|
265
273
|
}
|
|
266
274
|
return config && typeof config === 'object' ? { ...config } : {};
|
|
@@ -1650,6 +1658,7 @@ export class PlanEntitlementService {
|
|
|
1650
1658
|
label: feature.label,
|
|
1651
1659
|
description: feature.description,
|
|
1652
1660
|
isCustom: false,
|
|
1661
|
+
publicVisible: overrides.get(feature.featureKey)?.publicVisible ?? feature.publicVisible ?? null,
|
|
1653
1662
|
publicLabel: overrides.get(feature.featureKey)?.publicLabel ?? feature.publicLabel ?? null,
|
|
1654
1663
|
publicDescription: overrides.get(feature.featureKey)?.publicDescription ?? feature.publicDescription ?? null,
|
|
1655
1664
|
publicDescriptionVisible: overrides.get(feature.featureKey)?.publicDescriptionVisible ?? feature.publicDescriptionVisible ?? null,
|
|
@@ -1669,6 +1678,7 @@ export class PlanEntitlementService {
|
|
|
1669
1678
|
const featureKey = this.generateUniqueCustomPlanFeatureKey(label);
|
|
1670
1679
|
const publicLabel = this.normalizeOptionalCatalogMarketingCopy(input.publicLabel);
|
|
1671
1680
|
const publicDescription = this.normalizeOptionalCatalogMarketingCopy(input.publicDescription);
|
|
1681
|
+
const publicVisible = this.normalizeOptionalCatalogDescriptionVisible(input.publicVisible);
|
|
1672
1682
|
const publicDescriptionVisible = this.normalizeOptionalCatalogDescriptionVisible(input.publicDescriptionVisible);
|
|
1673
1683
|
const publicDisplayOrder = this.normalizeOptionalCatalogDisplayOrder(input.publicDisplayOrder);
|
|
1674
1684
|
const now = new Date().toISOString();
|
|
@@ -1679,6 +1689,7 @@ export class PlanEntitlementService {
|
|
|
1679
1689
|
label,
|
|
1680
1690
|
description,
|
|
1681
1691
|
is_custom,
|
|
1692
|
+
public_visible,
|
|
1682
1693
|
public_label,
|
|
1683
1694
|
public_description,
|
|
1684
1695
|
public_description_visible,
|
|
@@ -1686,13 +1697,14 @@ export class PlanEntitlementService {
|
|
|
1686
1697
|
created_at,
|
|
1687
1698
|
updated_at
|
|
1688
1699
|
)
|
|
1689
|
-
VALUES (?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)
|
|
1690
|
-
`).run(this.tenantId, featureKey, label, description, publicLabel, publicDescription, publicDescriptionVisible === null ? null : (publicDescriptionVisible ? 1 : 0), publicDisplayOrder, now, now);
|
|
1700
|
+
VALUES (?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?)
|
|
1701
|
+
`).run(this.tenantId, featureKey, label, description, publicVisible === null ? null : (publicVisible ? 1 : 0), publicLabel, publicDescription, publicDescriptionVisible === null ? null : (publicDescriptionVisible ? 1 : 0), publicDisplayOrder, now, now);
|
|
1691
1702
|
return this.getCustomPlanFeatureCatalogEntry(featureKey) || {
|
|
1692
1703
|
featureKey,
|
|
1693
1704
|
label,
|
|
1694
1705
|
description,
|
|
1695
1706
|
isCustom: true,
|
|
1707
|
+
publicVisible,
|
|
1696
1708
|
publicLabel,
|
|
1697
1709
|
publicDescription,
|
|
1698
1710
|
publicDescriptionVisible,
|
|
@@ -1737,11 +1749,12 @@ export class PlanEntitlementService {
|
|
|
1737
1749
|
throw new TaskforceRuleError('Feature label is required', 400, 'PLAN_FEATURE_LABEL_REQUIRED');
|
|
1738
1750
|
const publicLabel = this.normalizeOptionalCatalogMarketingCopy(input.publicLabel);
|
|
1739
1751
|
const publicDescription = this.normalizeOptionalCatalogMarketingCopy(input.publicDescription);
|
|
1752
|
+
const publicVisible = this.normalizeOptionalCatalogDescriptionVisible(input.publicVisible);
|
|
1740
1753
|
const publicDescriptionVisible = this.normalizeOptionalCatalogDescriptionVisible(input.publicDescriptionVisible);
|
|
1741
1754
|
const publicDisplayOrder = this.normalizeOptionalCatalogDisplayOrder(input.publicDisplayOrder);
|
|
1742
1755
|
const now = new Date().toISOString();
|
|
1743
1756
|
const tx = this.db.transaction(() => {
|
|
1744
|
-
if (!customFeature && !publicLabel && !publicDescription && publicDescriptionVisible === null && publicDisplayOrder === null) {
|
|
1757
|
+
if (!customFeature && publicVisible === null && !publicLabel && !publicDescription && publicDescriptionVisible === null && publicDisplayOrder === null) {
|
|
1745
1758
|
this.db.prepare(`
|
|
1746
1759
|
DELETE FROM plan_feature_catalog_overrides
|
|
1747
1760
|
WHERE tenant_id = ? AND feature_key = ?
|
|
@@ -1755,6 +1768,7 @@ export class PlanEntitlementService {
|
|
|
1755
1768
|
label,
|
|
1756
1769
|
description,
|
|
1757
1770
|
is_custom,
|
|
1771
|
+
public_visible,
|
|
1758
1772
|
public_label,
|
|
1759
1773
|
public_description,
|
|
1760
1774
|
public_description_visible,
|
|
@@ -1762,17 +1776,18 @@ export class PlanEntitlementService {
|
|
|
1762
1776
|
created_at,
|
|
1763
1777
|
updated_at
|
|
1764
1778
|
)
|
|
1765
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1779
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1766
1780
|
ON CONFLICT (tenant_id, feature_key) DO UPDATE SET
|
|
1767
1781
|
label = excluded.label,
|
|
1768
1782
|
description = excluded.description,
|
|
1769
1783
|
is_custom = excluded.is_custom,
|
|
1784
|
+
public_visible = excluded.public_visible,
|
|
1770
1785
|
public_label = excluded.public_label,
|
|
1771
1786
|
public_description = excluded.public_description,
|
|
1772
1787
|
public_description_visible = excluded.public_description_visible,
|
|
1773
1788
|
public_display_order = excluded.public_display_order,
|
|
1774
1789
|
updated_at = excluded.updated_at
|
|
1775
|
-
`).run(this.tenantId, featureKey, customFeature ? label : null, customFeature ? description : null, customFeature ? 1 : 0, publicLabel, publicDescription, publicDescriptionVisible === null ? null : (publicDescriptionVisible ? 1 : 0), publicDisplayOrder, now, now);
|
|
1790
|
+
`).run(this.tenantId, featureKey, customFeature ? label : null, customFeature ? description : null, customFeature ? 1 : 0, publicVisible === null ? null : (publicVisible ? 1 : 0), publicLabel, publicDescription, publicDescriptionVisible === null ? null : (publicDescriptionVisible ? 1 : 0), publicDisplayOrder, now, now);
|
|
1776
1791
|
});
|
|
1777
1792
|
tx();
|
|
1778
1793
|
return this.listPlanFeatureCatalog().find((feature) => feature.featureKey === featureKey) || (customFeature ? {
|
|
@@ -1780,6 +1795,7 @@ export class PlanEntitlementService {
|
|
|
1780
1795
|
label,
|
|
1781
1796
|
description,
|
|
1782
1797
|
isCustom: true,
|
|
1798
|
+
publicVisible,
|
|
1783
1799
|
publicLabel,
|
|
1784
1800
|
publicDescription,
|
|
1785
1801
|
publicDescriptionVisible,
|
|
@@ -1791,6 +1807,7 @@ export class PlanEntitlementService {
|
|
|
1791
1807
|
label: catalogFeature.label,
|
|
1792
1808
|
description: catalogFeature.description,
|
|
1793
1809
|
isCustom: false,
|
|
1810
|
+
publicVisible,
|
|
1794
1811
|
publicLabel,
|
|
1795
1812
|
publicDescription,
|
|
1796
1813
|
publicDescriptionVisible,
|
package/dist/core/Taskforce.d.ts
CHANGED
|
@@ -618,6 +618,7 @@ export interface PlanFeatureCatalogItem {
|
|
|
618
618
|
label: string;
|
|
619
619
|
description: string;
|
|
620
620
|
isCustom?: boolean;
|
|
621
|
+
publicVisible?: boolean | null;
|
|
621
622
|
publicDescriptionVisible?: boolean | null;
|
|
622
623
|
allowedAccessModes: PlanFeatureAccess[];
|
|
623
624
|
configTemplate: Record<string, unknown>;
|
|
@@ -910,6 +911,8 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
910
911
|
private tenantId;
|
|
911
912
|
private db;
|
|
912
913
|
private globalSettingsDb;
|
|
914
|
+
private readonly globalSettingsSchemaEnsuredStores;
|
|
915
|
+
private readonly structuredSettingsSchemaEnsuredStores;
|
|
913
916
|
private authTokenService;
|
|
914
917
|
private identityService;
|
|
915
918
|
private mcpTokenService;
|
|
@@ -1623,6 +1626,7 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
1623
1626
|
createCustomPlanFeatureCatalogEntry(input: {
|
|
1624
1627
|
label: string;
|
|
1625
1628
|
description?: string | null;
|
|
1629
|
+
publicVisible?: boolean | null;
|
|
1626
1630
|
publicLabel?: string | null;
|
|
1627
1631
|
publicDescription?: string | null;
|
|
1628
1632
|
publicDescriptionVisible?: boolean | null;
|
|
@@ -1633,6 +1637,7 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
1633
1637
|
featureKey: string;
|
|
1634
1638
|
label?: string | null;
|
|
1635
1639
|
description?: string | null;
|
|
1640
|
+
publicVisible?: boolean | null;
|
|
1636
1641
|
publicLabel?: string | null;
|
|
1637
1642
|
publicDescription?: string | null;
|
|
1638
1643
|
publicDescriptionVisible?: boolean | null;
|
package/dist/core/Taskforce.js
CHANGED
|
@@ -40,6 +40,7 @@ import { formatInitiativeReference, formatWorkstreamReference, } from '../utils/
|
|
|
40
40
|
import { getDocumentReferenceLabel } from '../utils/documentReferences.js';
|
|
41
41
|
import { getImageReferenceLabel } from '../utils/imageReferences.js';
|
|
42
42
|
import { getCanonicalDocumentName } from '../utils/documentNames.js';
|
|
43
|
+
import { isPathInsideRoot, resolveStorageKeyPathInsideRoot } from '../utils/pathSafety.js';
|
|
43
44
|
import { DEFAULT_TASKFORCE_THEME } from '../utils/theme.js';
|
|
44
45
|
import { ensureAssigneeTaskSchema, ensureAnnotatedAttachmentSessionsSchema, ensureAuthIdentitySchema, ensureBillingSchema, ensureDocumentReviewSessionsSchema, ensureDocumentReferenceSchema, ensureEntitlementsSchema, ensureScheduleTaskSchema, ensureTaskAssetsSchema, ensureTaskReferenceSchema, ensureTenantSchema, ensureWorkspaceAssetsSchema, ensureWorkspaceSchema } from '../migrations/taskSchemaMigrations.js';
|
|
45
46
|
export class TaskforceRuleError extends Error {
|
|
@@ -142,6 +143,8 @@ export class TaskforceCore {
|
|
|
142
143
|
tenantId;
|
|
143
144
|
db;
|
|
144
145
|
globalSettingsDb = null;
|
|
146
|
+
globalSettingsSchemaEnsuredStores = new WeakSet();
|
|
147
|
+
structuredSettingsSchemaEnsuredStores = new WeakSet();
|
|
145
148
|
authTokenService;
|
|
146
149
|
identityService;
|
|
147
150
|
mcpTokenService;
|
|
@@ -2961,6 +2964,8 @@ export class TaskforceCore {
|
|
|
2961
2964
|
return this.globalSettingsDb || this.db;
|
|
2962
2965
|
}
|
|
2963
2966
|
ensureGlobalSettingsTable(store) {
|
|
2967
|
+
if (this.globalSettingsSchemaEnsuredStores.has(store))
|
|
2968
|
+
return;
|
|
2964
2969
|
store.exec(`
|
|
2965
2970
|
CREATE TABLE IF NOT EXISTS global_settings (
|
|
2966
2971
|
tenant_id TEXT PRIMARY KEY,
|
|
@@ -2968,8 +2973,11 @@ export class TaskforceCore {
|
|
|
2968
2973
|
updated_at TEXT NOT NULL
|
|
2969
2974
|
);
|
|
2970
2975
|
`);
|
|
2976
|
+
this.globalSettingsSchemaEnsuredStores.add(store);
|
|
2971
2977
|
}
|
|
2972
2978
|
ensureStructuredSettingsTables(store) {
|
|
2979
|
+
if (this.structuredSettingsSchemaEnsuredStores.has(store))
|
|
2980
|
+
return;
|
|
2973
2981
|
store.exec(`
|
|
2974
2982
|
CREATE TABLE IF NOT EXISTS system_settings (
|
|
2975
2983
|
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
@@ -3002,6 +3010,7 @@ export class TaskforceCore {
|
|
|
3002
3010
|
CREATE INDEX IF NOT EXISTS idx_user_preferences_user_key
|
|
3003
3011
|
ON user_preferences(user_id, prefs_key);
|
|
3004
3012
|
`);
|
|
3013
|
+
this.structuredSettingsSchemaEnsuredStores.add(store);
|
|
3005
3014
|
}
|
|
3006
3015
|
readSettingsPayloadRow(store, tableName, whereValues) {
|
|
3007
3016
|
try {
|
|
@@ -3260,8 +3269,13 @@ export class TaskforceCore {
|
|
|
3260
3269
|
};
|
|
3261
3270
|
}
|
|
3262
3271
|
if (storageProvider !== 'r2') {
|
|
3263
|
-
const candidatePath =
|
|
3264
|
-
if (!fs.existsSync(candidatePath) || !fs.statSync(candidatePath).isFile()) {
|
|
3272
|
+
const candidatePath = resolveStorageKeyPathInsideRoot(this.basePath, storageKey);
|
|
3273
|
+
if (!candidatePath || !fs.existsSync(candidatePath) || !fs.statSync(candidatePath).isFile()) {
|
|
3274
|
+
return null;
|
|
3275
|
+
}
|
|
3276
|
+
const storageRoot = fs.existsSync(this.basePath) ? fs.realpathSync(this.basePath) : path.resolve(this.basePath);
|
|
3277
|
+
const realCandidatePath = fs.realpathSync(candidatePath);
|
|
3278
|
+
if (!isPathInsideRoot(realCandidatePath, storageRoot)) {
|
|
3265
3279
|
return null;
|
|
3266
3280
|
}
|
|
3267
3281
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -511,6 +511,7 @@ export interface PlanFeatureCatalogItem {
|
|
|
511
511
|
label: string;
|
|
512
512
|
description: string;
|
|
513
513
|
isCustom?: boolean;
|
|
514
|
+
publicVisible?: boolean | null;
|
|
514
515
|
publicLabel?: string | null;
|
|
515
516
|
publicDescription?: string | null;
|
|
516
517
|
publicDescriptionVisible?: boolean | null;
|
|
@@ -59,6 +59,7 @@ interface CreateSyncRecoveryCoordinatorArgs {
|
|
|
59
59
|
persistWorkspaceSyncPatch: (patch: {
|
|
60
60
|
phase: WorkspaceSyncPhase;
|
|
61
61
|
pullCursor: null;
|
|
62
|
+
startupFullReconcileCompletedAt: null;
|
|
62
63
|
lastErrorMessage: null;
|
|
63
64
|
}) => Promise<unknown>;
|
|
64
65
|
persistWorkspaceSyncWatermarksSnapshotBestEffort: () => boolean;
|
|
@@ -214,6 +214,7 @@ export function createSyncRecoveryCoordinator({ currentWorkspaceId, workspaceClo
|
|
|
214
214
|
await persistWorkspaceSyncPatch({
|
|
215
215
|
phase: repairPhase,
|
|
216
216
|
pullCursor: null,
|
|
217
|
+
startupFullReconcileCompletedAt: null,
|
|
217
218
|
lastErrorMessage: null
|
|
218
219
|
});
|
|
219
220
|
if (repairPhase === 'provision-local') {
|
|
@@ -32,6 +32,7 @@ interface CreateWorkspaceTransferCoordinatorArgs {
|
|
|
32
32
|
lastPushAt?: string;
|
|
33
33
|
lastPullAt?: string;
|
|
34
34
|
lastSyncedAt: string;
|
|
35
|
+
startupFullReconcileCompletedAt?: string | null;
|
|
35
36
|
pullCursor?: string | null;
|
|
36
37
|
lastErrorMessage: null;
|
|
37
38
|
} | {
|
|
@@ -42,6 +43,7 @@ interface CreateWorkspaceTransferCoordinatorArgs {
|
|
|
42
43
|
pullCursor: string | null;
|
|
43
44
|
lastPullAt: string;
|
|
44
45
|
lastSyncedAt: string;
|
|
46
|
+
startupFullReconcileCompletedAt?: string | null;
|
|
45
47
|
lastErrorMessage: null;
|
|
46
48
|
}) => Promise<unknown>;
|
|
47
49
|
persistWorkspaceSyncWatermarksSnapshotBestEffort: () => boolean;
|
|
@@ -486,6 +486,9 @@ export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtim
|
|
|
486
486
|
pullCursor: workspacePullCursorRef.current,
|
|
487
487
|
lastPullAt: syncedAt,
|
|
488
488
|
lastSyncedAt: syncedAt,
|
|
489
|
+
...(options?.forceCursorNull === true
|
|
490
|
+
? { startupFullReconcileCompletedAt: syncedAt }
|
|
491
|
+
: {}),
|
|
489
492
|
lastErrorMessage: null
|
|
490
493
|
});
|
|
491
494
|
reportSyncEvent(currentWorkspaceId, {
|
|
@@ -527,7 +527,7 @@ export function useSyncOrchestrator(input) {
|
|
|
527
527
|
workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
|
|
528
528
|
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
|
|
529
529
|
workspaceLastPushedTaskEventWatermarksRef.current = new Map();
|
|
530
|
-
workspaceStartupFullReconcileRanRef.current =
|
|
530
|
+
workspaceStartupFullReconcileRanRef.current = Boolean(readWorkspaceSyncStateSnapshot().startupFullReconcileCompletedAt);
|
|
531
531
|
workspaceFullReconcileInFlightRef.current = false;
|
|
532
532
|
const persisted = readPersistedSyncWatermarks(currentWorkspaceId, taxonomyStateFingerprint);
|
|
533
533
|
if (!hydrateWorkspacePushBaselines(persisted)) {
|
|
@@ -556,6 +556,14 @@ export function useSyncOrchestrator(input) {
|
|
|
556
556
|
workspaceCloudSyncEnabled,
|
|
557
557
|
workspaceSyncPhase
|
|
558
558
|
]);
|
|
559
|
+
useEffect(() => {
|
|
560
|
+
workspaceStartupFullReconcileRanRef.current = Boolean(readWorkspaceSyncStateSnapshot().startupFullReconcileCompletedAt);
|
|
561
|
+
}, [
|
|
562
|
+
currentWorkspaceId,
|
|
563
|
+
readWorkspaceSyncStateSnapshot,
|
|
564
|
+
workspaceLastPullAt,
|
|
565
|
+
workspaceSyncPhase
|
|
566
|
+
]);
|
|
559
567
|
const scheduleWorkspaceSyncRetry = useCallback((minDelayMs) => {
|
|
560
568
|
scheduleWorkspaceRetry(() => retryWorkspaceSyncRef.current(), { minDelayMs });
|
|
561
569
|
}, [scheduleWorkspaceRetry]);
|
|
@@ -2708,14 +2708,21 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
|
|
|
2708
2708
|
onTaskCountChange(tasks.length);
|
|
2709
2709
|
}
|
|
2710
2710
|
}, [tasks.length, onTaskCountChange]);
|
|
2711
|
+
const handledInitialTaskIdRef = useRef(null);
|
|
2711
2712
|
// Deep linking handler
|
|
2712
2713
|
useEffect(() => {
|
|
2713
|
-
if (initialTaskId
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2714
|
+
if (!initialTaskId) {
|
|
2715
|
+
handledInitialTaskIdRef.current = null;
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
if (handledInitialTaskIdRef.current === initialTaskId || tasks.length === 0) {
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
const task = tasks.find(t => t.id.endsWith(initialTaskId) || t.id === initialTaskId);
|
|
2722
|
+
if (task) {
|
|
2723
|
+
handledInitialTaskIdRef.current = initialTaskId;
|
|
2724
|
+
handleEdit(task);
|
|
2725
|
+
setActiveTab('add');
|
|
2719
2726
|
}
|
|
2720
2727
|
}, [initialTaskId, tasks]);
|
|
2721
2728
|
// Update active category logic
|
|
@@ -60,6 +60,9 @@ function buildWorkspaceSyncStatePatch(current, patch) {
|
|
|
60
60
|
version: 2,
|
|
61
61
|
phase: patch.phase || current.phase,
|
|
62
62
|
setupIntent: patch.setupIntent === undefined ? (current.setupIntent ?? null) : (patch.setupIntent ?? null),
|
|
63
|
+
startupFullReconcileCompletedAt: patch.startupFullReconcileCompletedAt === undefined
|
|
64
|
+
? (current.startupFullReconcileCompletedAt ?? null)
|
|
65
|
+
: normalizeCursor(patch.startupFullReconcileCompletedAt),
|
|
63
66
|
pullCursor: patch.pullCursor === undefined ? current.pullCursor : normalizeCursor(patch.pullCursor),
|
|
64
67
|
lastPullAt: patch.lastPullAt === undefined ? current.lastPullAt : normalizeCursor(patch.lastPullAt),
|
|
65
68
|
lastPushAt: patch.lastPushAt === undefined ? current.lastPushAt : normalizeCursor(patch.lastPushAt),
|