@parhelia/core 0.3.12816 → 0.3.12818

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 (48) hide show
  1. package/dist/editor/ContentTree.d.ts +1 -1
  2. package/dist/editor/ContentTree.js +25 -3
  3. package/dist/editor/ContentTree.js.map +1 -1
  4. package/dist/editor/ai/terminal/useAgentStopCommand.js +16 -14
  5. package/dist/editor/ai/terminal/useAgentStopCommand.js.map +1 -1
  6. package/dist/editor/client/operations.js +12 -3
  7. package/dist/editor/client/operations.js.map +1 -1
  8. package/dist/editor/media-selector/Thumbnails.js +23 -10
  9. package/dist/editor/media-selector/Thumbnails.js.map +1 -1
  10. package/dist/editor/media-selector/mediaItemDrag.d.ts +22 -5
  11. package/dist/editor/media-selector/mediaItemDrag.js +108 -10
  12. package/dist/editor/media-selector/mediaItemDrag.js.map +1 -1
  13. package/dist/editor/settings/panels/ProjectTemplatesPanel.js +45 -1
  14. package/dist/editor/settings/panels/ProjectTemplatesPanel.js.map +1 -1
  15. package/dist/revision.d.ts +2 -2
  16. package/dist/revision.js +2 -2
  17. package/dist/task-board/TaskBoardWorkspace.js +186 -7
  18. package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
  19. package/dist/task-board/components/CreateTaskDialog.d.ts +2 -0
  20. package/dist/task-board/components/CreateTaskDialog.js +6 -3
  21. package/dist/task-board/components/CreateTaskDialog.js.map +1 -1
  22. package/dist/task-board/components/GoalCoverageBar.d.ts +4 -0
  23. package/dist/task-board/components/GoalCoverageBar.js +22 -0
  24. package/dist/task-board/components/GoalCoverageBar.js.map +1 -0
  25. package/dist/task-board/components/GoalDataView.d.ts +39 -0
  26. package/dist/task-board/components/GoalDataView.js +385 -0
  27. package/dist/task-board/components/GoalDataView.js.map +1 -0
  28. package/dist/task-board/components/GoalsMatrix.d.ts +16 -0
  29. package/dist/task-board/components/GoalsMatrix.js +129 -0
  30. package/dist/task-board/components/GoalsMatrix.js.map +1 -0
  31. package/dist/task-board/components/ProjectDashboard.d.ts +1 -1
  32. package/dist/task-board/components/ProjectGoalsPanel.d.ts +16 -0
  33. package/dist/task-board/components/ProjectGoalsPanel.js +74 -113
  34. package/dist/task-board/components/ProjectGoalsPanel.js.map +1 -1
  35. package/dist/task-board/components/SubgoalStatusIcon.d.ts +4 -0
  36. package/dist/task-board/components/SubgoalStatusIcon.js +19 -0
  37. package/dist/task-board/components/SubgoalStatusIcon.js.map +1 -0
  38. package/dist/task-board/components/goalDisplaySpec.d.ts +19 -0
  39. package/dist/task-board/components/goalDisplaySpec.js +72 -0
  40. package/dist/task-board/components/goalDisplaySpec.js.map +1 -0
  41. package/dist/task-board/goalRefreshState.d.ts +6 -0
  42. package/dist/task-board/goalRefreshState.js +11 -0
  43. package/dist/task-board/goalRefreshState.js.map +1 -0
  44. package/dist/task-board/goalTaskNavigationState.d.ts +43 -0
  45. package/dist/task-board/goalTaskNavigationState.js +65 -0
  46. package/dist/task-board/goalTaskNavigationState.js.map +1 -0
  47. package/dist/task-board/types.d.ts +6 -0
  48. package/package.json +1 -1
@@ -37,6 +37,8 @@ import { WizardCommunicationCenter } from "./components/WizardCommunicationCente
37
37
  import { Loader2 } from "lucide-react";
38
38
  import { MainContentTree } from "../editor/sidebar/MainContentTree";
39
39
  import { useTaskBoardAgentPanelState } from "./useTaskBoardAgentPanelState";
40
+ import { goalScopesMatch } from "./goalRefreshState";
41
+ import { initialGoalTaskNavigationState, reduceGoalTaskNavigation, } from "./goalTaskNavigationState";
40
42
  const DependencyGraphView = lazy(() => import("./views/DependencyGraphView").then((module) => ({
41
43
  default: module.DependencyGraphView,
42
44
  })));
@@ -177,10 +179,15 @@ export function TaskBoardWorkspace() {
177
179
  const [tasks, setTasks] = useState([]);
178
180
  const [goals, setGoals] = useState([]);
179
181
  const [goalsLoading, setGoalsLoading] = useState(false);
182
+ const [goalsError, setGoalsError] = useState(null);
180
183
  const [goalCatalog, setGoalCatalog] = useState([]);
181
184
  const [goalPermissions, setGoalPermissions] = useState({});
185
+ const [goalDisplaySpecs, setGoalDisplaySpecs] = useState({});
186
+ const [goalStateScope, setGoalStateScope] = useState(null);
182
187
  const [includeGoalSubprojects, setIncludeGoalSubprojects] = useState(false);
183
188
  const latestGoalsProjectIdRef = useRef(null);
189
+ const goalsRequestGenerationRef = useRef(0);
190
+ const wsGoalRefreshTimeoutRef = useRef(null);
184
191
  const refreshGoalsRef = useRef(null);
185
192
  const includeGoalSubprojectsRef = useRef(false);
186
193
  const [isTasksLoading, setIsTasksLoading] = useState(false);
@@ -250,6 +257,14 @@ export function TaskBoardWorkspace() {
250
257
  const projectsRequestCountRef = useRef(0);
251
258
  const tasksRequestCountRef = useRef(0);
252
259
  const latestTasksProjectIdRef = useRef(null);
260
+ const taskRequestGenerationByProjectRef = useRef(new Map());
261
+ const taskRequestInFlightByProjectRef = useRef(new Map());
262
+ const goalTaskNavigationStateRef = useRef(initialGoalTaskNavigationState);
263
+ const transitionGoalTaskNavigation = useCallback((action) => {
264
+ const next = reduceGoalTaskNavigation(goalTaskNavigationStateRef.current, action);
265
+ goalTaskNavigationStateRef.current = next;
266
+ return next.outcome;
267
+ }, []);
253
268
  const latestDependenciesProjectIdRef = useRef(null);
254
269
  const latestGraphLayoutProjectIdRef = useRef(null);
255
270
  const latestSubprojectCountsProjectIdRef = useRef(null);
@@ -798,6 +813,9 @@ export function TaskBoardWorkspace() {
798
813
  return () => window.removeEventListener("popstate", handlePopState);
799
814
  }, [syncTaskBoardStateFromUrl]);
800
815
  const refreshTasks = useCallback(async (projectId) => {
816
+ const generation = (taskRequestGenerationByProjectRef.current.get(projectId) ?? 0) + 1;
817
+ taskRequestGenerationByProjectRef.current.set(projectId, generation);
818
+ taskRequestInFlightByProjectRef.current.set(projectId, generation);
801
819
  tasksRequestCountRef.current += 1;
802
820
  latestTasksProjectIdRef.current = projectId;
803
821
  const requestProjectId = projectId;
@@ -813,14 +831,29 @@ export function TaskBoardWorkspace() {
813
831
  }
814
832
  const nextTasks = result.data || [];
815
833
  setTasks(nextTasks);
834
+ const navigationOutcome = transitionGoalTaskNavigation({
835
+ type: "tasksLoaded",
836
+ projectId,
837
+ generation,
838
+ taskIds: nextTasks.map((task) => task.taskId),
839
+ });
840
+ if (navigationOutcome.kind === "select") {
841
+ setTaskSelection(navigationOutcome.taskId, { source: "user" });
842
+ }
843
+ else if (navigationOutcome.kind === "missing") {
844
+ toast.error("The referenced task is not available in this project");
845
+ }
816
846
  }
817
847
  finally {
848
+ if (taskRequestInFlightByProjectRef.current.get(projectId) === generation) {
849
+ taskRequestInFlightByProjectRef.current.delete(projectId);
850
+ }
818
851
  tasksRequestCountRef.current = Math.max(0, tasksRequestCountRef.current - 1);
819
852
  if (tasksRequestCountRef.current === 0) {
820
853
  setIsTasksLoading(false);
821
854
  }
822
855
  }
823
- }, []);
856
+ }, [setTaskSelection, transitionGoalTaskNavigation]);
824
857
  const refreshExecutionState = useCallback(async (projectId) => {
825
858
  const result = await getExecutionState({ projectId });
826
859
  if (result.type !== "success") {
@@ -832,22 +865,36 @@ export function TaskBoardWorkspace() {
832
865
  setExecutionRecordsByTaskId(indexed);
833
866
  }, []);
834
867
  const refreshGoals = useCallback(async (projectId, includeDescendants) => {
868
+ const generation = ++goalsRequestGenerationRef.current;
835
869
  latestGoalsProjectIdRef.current = projectId;
836
870
  setGoalsLoading(true);
871
+ setGoalsError(null);
837
872
  try {
838
873
  const result = await getProjectGoals(projectId, includeDescendants);
839
- if (latestGoalsProjectIdRef.current !== projectId)
874
+ if (latestGoalsProjectIdRef.current !== projectId ||
875
+ generation !== goalsRequestGenerationRef.current)
840
876
  return;
841
877
  if (result.type !== "success") {
842
- toast.error(result.summary || "Failed to load goals");
878
+ const message = result.summary || "Failed to load goals";
879
+ setGoals([]);
880
+ setGoalCatalog([]);
881
+ setGoalPermissions({});
882
+ setGoalDisplaySpecs({});
883
+ setGoalStateScope({ projectId, includeDescendants });
884
+ setGoalsError(message);
885
+ toast.error(message);
843
886
  return;
844
887
  }
845
888
  setGoals(result.data?.goals ?? []);
846
889
  setGoalCatalog(result.data?.catalog ?? []);
847
890
  setGoalPermissions(result.data?.permissions ?? {});
891
+ setGoalDisplaySpecs(result.data?.displaySpecs ?? {});
892
+ setGoalStateScope({ projectId, includeDescendants });
893
+ setGoalsError(null);
848
894
  }
849
895
  finally {
850
- if (latestGoalsProjectIdRef.current === projectId) {
896
+ if (latestGoalsProjectIdRef.current === projectId &&
897
+ generation === goalsRequestGenerationRef.current) {
851
898
  setGoalsLoading(false);
852
899
  }
853
900
  }
@@ -868,6 +915,14 @@ export function TaskBoardWorkspace() {
868
915
  return;
869
916
  void refreshGoals(selectedProjectId, includeGoalSubprojects);
870
917
  }, [isGoalsView, selectedProjectId, includeGoalSubprojects, refreshGoals]);
918
+ useEffect(() => {
919
+ return () => {
920
+ if (wsGoalRefreshTimeoutRef.current !== null) {
921
+ window.clearTimeout(wsGoalRefreshTimeoutRef.current);
922
+ wsGoalRefreshTimeoutRef.current = null;
923
+ }
924
+ };
925
+ }, [selectedProjectId, includeGoalSubprojects]);
871
926
  const refreshGraphLayout = useCallback(async (projectId) => {
872
927
  latestGraphLayoutProjectIdRef.current = projectId;
873
928
  const requestProjectId = projectId;
@@ -1701,7 +1756,11 @@ export function TaskBoardWorkspace() {
1701
1756
  useEffect(() => {
1702
1757
  previousTaskStatusesRef.current = {};
1703
1758
  previousWizardAttentionSignatureRef.current = "";
1704
- }, [selectedProjectId]);
1759
+ transitionGoalTaskNavigation({
1760
+ type: "projectSelected",
1761
+ projectId: selectedProjectId,
1762
+ });
1763
+ }, [selectedProjectId, transitionGoalTaskNavigation]);
1705
1764
  useEffect(() => {
1706
1765
  if (!selectedProjectId)
1707
1766
  return;
@@ -1846,7 +1905,13 @@ export function TaskBoardWorkspace() {
1846
1905
  !isProjectWithinSelectedTree(projectId, currentProjectId)) {
1847
1906
  return;
1848
1907
  }
1849
- refreshGoalsRef.current?.();
1908
+ if (wsGoalRefreshTimeoutRef.current !== null) {
1909
+ window.clearTimeout(wsGoalRefreshTimeoutRef.current);
1910
+ }
1911
+ wsGoalRefreshTimeoutRef.current = window.setTimeout(() => {
1912
+ wsGoalRefreshTimeoutRef.current = null;
1913
+ refreshGoalsRef.current?.();
1914
+ }, WS_REFRESH_DELAY_MS);
1850
1915
  return;
1851
1916
  }
1852
1917
  if (messageType !== "task:updated" &&
@@ -2056,6 +2121,105 @@ export function TaskBoardWorkspace() {
2056
2121
  wizardNextReviewTaskId,
2057
2122
  ]);
2058
2123
  const isProjectViewLoading = !!selectedProjectId && projectViewLoadingId === selectedProjectId;
2124
+ const goalScopeMatches = goalScopesMatch(goalStateScope, {
2125
+ projectId: selectedProjectId ?? "",
2126
+ includeDescendants: includeGoalSubprojects,
2127
+ });
2128
+ const goalProjectLabels = useMemo(() => {
2129
+ const byId = new Map(projects.map((entry) => [entry.project.projectId, entry.project]));
2130
+ const labels = {};
2131
+ for (const entry of projects) {
2132
+ const project = entry.project;
2133
+ const titles = [];
2134
+ const seen = new Set();
2135
+ let cursorId = project.projectId;
2136
+ while (cursorId && !seen.has(cursorId)) {
2137
+ const cursor = byId.get(cursorId);
2138
+ if (!cursor)
2139
+ break;
2140
+ seen.add(cursorId);
2141
+ titles.unshift(cursor.title);
2142
+ cursorId = cursor.parentProjectId ?? null;
2143
+ }
2144
+ labels[project.projectId] = {
2145
+ title: project.title,
2146
+ path: titles.join(" / "),
2147
+ };
2148
+ }
2149
+ return labels;
2150
+ }, [projects]);
2151
+ const handleGoalOpenItem = useCallback(async (ref) => {
2152
+ if (!editContext)
2153
+ return;
2154
+ try {
2155
+ const loaded = await editContext.loadItem({
2156
+ id: ref.itemId,
2157
+ language: ref.language ?? editContext.currentItemDescriptor?.language ?? "en",
2158
+ version: 0,
2159
+ });
2160
+ if (!loaded) {
2161
+ toast.error("The referenced item could not be opened");
2162
+ return;
2163
+ }
2164
+ editContext.setShowAgentsWorkspaceEditor(true);
2165
+ }
2166
+ catch {
2167
+ toast.error("The referenced item could not be opened");
2168
+ }
2169
+ }, [editContext]);
2170
+ const handleGoalSelectTask = useCallback((ref) => {
2171
+ const targetProjectId = ref.projectId ?? selectedProjectIdRef.current;
2172
+ if (!targetProjectId)
2173
+ return;
2174
+ const project = projectsRef.current.find((entry) => entry.project.projectId.toLowerCase() ===
2175
+ targetProjectId.toLowerCase());
2176
+ if (!project) {
2177
+ toast.error("The referenced task project is not available");
2178
+ return;
2179
+ }
2180
+ const resolvedProjectId = project.project.projectId;
2181
+ const currentProjectId = selectedProjectIdRef.current;
2182
+ const inFlightGeneration = taskRequestInFlightByProjectRef.current.get(resolvedProjectId);
2183
+ const latestIssued = taskRequestGenerationByProjectRef.current.get(resolvedProjectId) ?? 0;
2184
+ const navigationOutcome = transitionGoalTaskNavigation({
2185
+ type: "request",
2186
+ projectId: resolvedProjectId,
2187
+ taskId: ref.taskId,
2188
+ currentProjectId,
2189
+ currentTaskIds: tasksRef.current.map((task) => task.taskId),
2190
+ inFlightGeneration,
2191
+ latestIssuedGeneration: latestIssued,
2192
+ });
2193
+ if (navigationOutcome.kind === "select") {
2194
+ setTaskSelection(navigationOutcome.taskId, { source: "user" });
2195
+ return;
2196
+ }
2197
+ if (navigationOutcome.kind === "missing") {
2198
+ toast.error("The referenced task is not available in this project");
2199
+ return;
2200
+ }
2201
+ if (navigationOutcome.kind === "load") {
2202
+ if (navigationOutcome.switchProject) {
2203
+ handleSelectProject(navigationOutcome.projectId);
2204
+ }
2205
+ else if (navigationOutcome.refresh) {
2206
+ void refreshTasks(navigationOutcome.projectId);
2207
+ }
2208
+ }
2209
+ }, [
2210
+ handleSelectProject,
2211
+ refreshTasks,
2212
+ setTaskSelection,
2213
+ transitionGoalTaskNavigation,
2214
+ ]);
2215
+ const handleGoalTaskCreated = useCallback((projectId) => {
2216
+ if (projectId === selectedProjectIdRef.current) {
2217
+ void refreshVisibleTaskData();
2218
+ }
2219
+ else if (directSubprojectIdsRef.current.has(projectId)) {
2220
+ void refreshSubprojectTaskCountsRef.current?.();
2221
+ }
2222
+ }, [refreshVisibleTaskData]);
2059
2223
  const taskBoardContent = useMemo(() => {
2060
2224
  if (isProjectViewLoading) {
2061
2225
  return (_jsx("div", { className: "border-border-default bg-neutral-grey-5/70 flex h-full min-h-[240px] items-center justify-center rounded-lg border border-dashed", "data-testid": "taskboard-project-loading-state", children: _jsxs("div", { className: "text-muted-foreground flex flex-col items-center gap-3 text-sm", children: [_jsx(Loader2, { strokeWidth: 1, className: "h-6 w-6 animate-spin" }), _jsx("div", { children: "Loading project..." })] }) }));
@@ -2076,7 +2240,14 @@ export function TaskBoardWorkspace() {
2076
2240
  } }) })] }));
2077
2241
  }
2078
2242
  if (isGoalsView) {
2079
- return (_jsxs("div", { className: "flex h-full min-h-0 min-w-0 flex-col gap-3", children: [_jsx(ProjectDashboard, { projectId: selectedProjectId, selectedProjectTitle: selectedProject?.project.title ?? "No project selected", selectedProjectDescription: selectedProject?.project.description, selectedProjectCostUsed: selectedProjectCumulativeCostUsed, selectedProjectCostLimit: selectedProject?.project.costLimit ?? null, selectedProjectTaskCounts: selectedProjectTaskCounts, subprojects: directSubprojects, taskCountsByProjectId: subprojectTaskCounts, parentProjectId: parentProject?.project.projectId ?? null, parentProjectTitle: parentProject?.project.title ?? null, onSelectProject: handleSelectProject, canCreateTask: canEditTasks, onCreateTask: () => setCreateDialogOpen(true), showCreateTaskButton: false }), sharedViewSwitcher, _jsx("div", { className: "mt-3 max-w-[1000px]", children: _jsx(ProjectGoalsPanel, { goals: goals, catalog: goalCatalog, loading: goalsLoading, hasSubprojects: hasDirectSubprojects, includeDescendants: includeGoalSubprojects, onToggleIncludeDescendants: setIncludeGoalSubprojects, selectedProjectId: selectedProjectId, permissions: goalPermissions, onChanged: () => refreshGoalsRef.current?.() }) })] }));
2243
+ return (_jsxs("div", { className: "flex h-full min-h-0 min-w-0 flex-col gap-3", children: [_jsx(ProjectDashboard, { projectId: selectedProjectId, selectedProjectTitle: selectedProject?.project.title ?? "No project selected", selectedProjectDescription: selectedProject?.project.description, selectedProjectCostUsed: selectedProjectCumulativeCostUsed, selectedProjectCostLimit: selectedProject?.project.costLimit ?? null, selectedProjectTaskCounts: selectedProjectTaskCounts, subprojects: directSubprojects, taskCountsByProjectId: subprojectTaskCounts, parentProjectId: parentProject?.project.projectId ?? null, parentProjectTitle: parentProject?.project.title ?? null, onSelectProject: handleSelectProject, canCreateTask: canEditTasks, onCreateTask: () => setCreateDialogOpen(true), showCreateTaskButton: false }), sharedViewSwitcher, _jsx("div", { className: "mt-3 min-w-0", children: _jsx(ProjectGoalsPanel, { goals: goalScopeMatches ? goals : [], catalog: goalScopeMatches ? goalCatalog : [], loading: goalsLoading || !goalScopeMatches, error: goalScopeMatches ? goalsError : null, onRetry: () => {
2244
+ if (selectedProjectId) {
2245
+ void refreshGoals(selectedProjectId, includeGoalSubprojects);
2246
+ }
2247
+ }, hasSubprojects: hasDirectSubprojects, includeDescendants: includeGoalSubprojects, onToggleIncludeDescendants: setIncludeGoalSubprojects, selectedProjectId: selectedProjectId, permissions: goalScopeMatches ? goalPermissions : {}, goalDisplaySpecs: goalScopeMatches ? goalDisplaySpecs : {}, projectLabels: goalProjectLabels, onOpenItem: (ref) => void handleGoalOpenItem(ref), onSelectTask: handleGoalSelectTask, onTaskCreated: handleGoalTaskCreated, onChanged: () => {
2248
+ if (goalScopeMatches)
2249
+ refreshGoalsRef.current?.();
2250
+ } }) })] }));
2080
2251
  }
2081
2252
  if (isKpisView) {
2082
2253
  return (_jsxs("div", { className: "flex h-full min-h-0 min-w-0 flex-col gap-3", children: [_jsx(ProjectDashboard, { projectId: selectedProjectId, selectedProjectTitle: selectedProject?.project.title ?? "No project selected", selectedProjectDescription: selectedProject?.project.description, selectedProjectCostUsed: selectedProjectCumulativeCostUsed, selectedProjectCostLimit: selectedProject?.project.costLimit ?? null, selectedProjectTaskCounts: selectedProjectTaskCounts, subprojects: directSubprojects, taskCountsByProjectId: subprojectTaskCounts, parentProjectId: parentProject?.project.projectId ?? null, parentProjectTitle: parentProject?.project.title ?? null, onSelectProject: handleSelectProject, canCreateTask: canEditTasks, onCreateTask: () => setCreateDialogOpen(true), showCreateTaskButton: false }), sharedViewSwitcher, _jsx("div", { className: "mt-3 max-w-[1000px]", children: _jsx(ProjectKpisPanel, { selectedProjectCostUsed: selectedProjectCumulativeCostUsed, selectedProjectCostLimit: selectedProject?.project.costLimit ?? null, selectedProjectTaskCounts: selectedProjectTaskCounts, selectedProjectAttention: selectedProject?.overview?.attention, subprojects: directSubprojects, taskCountsByProjectId: subprojectTaskCounts, attentionTaskGroups: kpiAttentionTaskGroups, attentionDetailsLoading: subprojectCountsLoading, onSelectAttentionTask: (taskId) => setTaskSelection(taskId, { source: "user" }) }) })] }));
@@ -2114,8 +2285,12 @@ export function TaskBoardWorkspace() {
2114
2285
  isGoalsView,
2115
2286
  goals,
2116
2287
  goalsLoading,
2288
+ goalsError,
2117
2289
  goalCatalog,
2118
2290
  goalPermissions,
2291
+ goalDisplaySpecs,
2292
+ goalScopeMatches,
2293
+ goalProjectLabels,
2119
2294
  includeGoalSubprojects,
2120
2295
  hasDirectSubprojects,
2121
2296
  graphLayout,
@@ -2145,7 +2320,11 @@ export function TaskBoardWorkspace() {
2145
2320
  wizardCommunicationPanel,
2146
2321
  setTaskSelection,
2147
2322
  refreshProjects,
2323
+ refreshGoals,
2148
2324
  refreshVisibleTaskData,
2325
+ handleGoalOpenItem,
2326
+ handleGoalSelectTask,
2327
+ handleGoalTaskCreated,
2149
2328
  ]);
2150
2329
  const taskDetailAgentLaunch = useMemo(() => {
2151
2330
  if (!selectedTask)