@plandesk/api 0.8.0 → 0.9.0
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/events.d.ts +6 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/routes/agent-runs.d.ts +1 -1
- package/dist/routes/agent-runs.js +20 -0
- package/dist/routes/goals.d.ts +4 -0
- package/dist/routes/goals.js +138 -0
- package/dist/routes/projects.d.ts +1 -1
- package/dist/routes/projects.js +12 -1
- package/dist/routes/submissions.d.ts +5 -0
- package/dist/routes/submissions.js +58 -0
- package/dist/serialize.d.ts +22 -1
- package/dist/serialize.js +35 -0
- package/dist/server.js +5 -1
- package/dist/services/canvas.d.ts +2 -0
- package/dist/services/canvas.js +2 -1
- package/dist/services/goals.d.ts +239 -0
- package/dist/services/goals.js +323 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +3 -0
- package/dist/services/projects.js +4 -1
- package/dist/services/sync.d.ts +5 -3
- package/dist/services/sync.js +45 -2
- package/dist/services/tasks.d.ts +10 -1
- package/dist/services/tasks.js +33 -5
- package/package.json +2 -2
- package/web/assets/index-DcD7-VGi.css +1 -0
- package/web/assets/index-Dcaq3wR-.js +318 -0
- package/web/index.html +2 -2
- package/web/assets/index-CUxSE_WZ.css +0 -1
- package/web/assets/index-DSmNB4RA.js +0 -235
package/dist/services/sync.js
CHANGED
|
@@ -18,6 +18,12 @@ export class InvalidTriageError extends Error {
|
|
|
18
18
|
this.name = 'InvalidTriageError';
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
export class InvalidTriageInputError extends Error {
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'InvalidTriageInputError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
21
27
|
export function serializeSubmission(row) {
|
|
22
28
|
return {
|
|
23
29
|
id: row.id,
|
|
@@ -252,19 +258,56 @@ export function createSyncService(deps) {
|
|
|
252
258
|
syncToken: row.syncToken,
|
|
253
259
|
};
|
|
254
260
|
},
|
|
255
|
-
async triage(submissionId, action, remote, asTask) {
|
|
261
|
+
async triage(submissionId, action, remote, asTask, linkTaskId) {
|
|
262
|
+
if (asTask !== undefined && linkTaskId !== undefined) {
|
|
263
|
+
throw new InvalidTriageInputError('as_task and link_task_id are mutually exclusive');
|
|
264
|
+
}
|
|
256
265
|
const submission = dbGetSubmission(db, submissionId);
|
|
257
266
|
if (submission === undefined) {
|
|
258
267
|
throw new InvalidTriageError();
|
|
259
268
|
}
|
|
260
269
|
if (submission.status !== 'pending') {
|
|
270
|
+
// Idempotent retry recovery: a prior triage may have committed the terminal
|
|
271
|
+
// status locally but failed to ack the remote (sync server briefly down),
|
|
272
|
+
// leaving local/remote divergence with no recovery path. Re-ack when the
|
|
273
|
+
// retry's action matches the recorded outcome; ack is idempotent remotely.
|
|
274
|
+
if ((action === 'accept' && submission.status === 'accepted') ||
|
|
275
|
+
(action === 'reject' && submission.status === 'rejected')) {
|
|
276
|
+
await ackSubmission(remote, submissionId, submission.status);
|
|
277
|
+
}
|
|
261
278
|
return serializeSubmission(submission);
|
|
262
279
|
}
|
|
263
280
|
const projectId = submission.projectId;
|
|
281
|
+
if (action === 'accept' && linkTaskId !== undefined) {
|
|
282
|
+
const existingTask = taskService.get(linkTaskId);
|
|
283
|
+
if (existingTask === undefined || existingTask.project_id !== projectId) {
|
|
284
|
+
throw new InvalidTriageInputError('link_task_id does not reference an existing task in this project');
|
|
285
|
+
}
|
|
286
|
+
const updated = setSubmissionStatus(db, submissionId, {
|
|
287
|
+
status: 'accepted',
|
|
288
|
+
linkedTaskId: linkTaskId,
|
|
289
|
+
});
|
|
290
|
+
if (updated === undefined) {
|
|
291
|
+
throw new InvalidTriageError();
|
|
292
|
+
}
|
|
293
|
+
eventBus.emit({ type: 'submissions_pulled', projectId });
|
|
294
|
+
await ackSubmission(remote, submissionId, 'accepted');
|
|
295
|
+
return serializeSubmission(updated);
|
|
296
|
+
}
|
|
264
297
|
if (action === 'accept') {
|
|
298
|
+
// Triage never releases work to `todo`: the scope->todo gate is the human's
|
|
299
|
+
// own board action. Every accepted submission lands in `scope`, regardless of
|
|
300
|
+
// caller — enforced here at the single service chokepoint so both the HTTP
|
|
301
|
+
// route and the MCP tool are covered.
|
|
302
|
+
//
|
|
303
|
+
// create + setSubmissionStatus run as two synchronous steps, but the submission
|
|
304
|
+
// was validated to exist immediately above with no intervening await, so under
|
|
305
|
+
// better-sqlite3's single-connection sync model the row cannot vanish and
|
|
306
|
+
// setSubmissionStatus cannot spuriously return undefined. taskService.create
|
|
307
|
+
// self-transacts, so an outer transaction here is neither possible nor needed.
|
|
265
308
|
const task = taskService.create(projectId, {
|
|
266
309
|
label: asTask?.label ?? submission.title,
|
|
267
|
-
status:
|
|
310
|
+
status: 'scope',
|
|
268
311
|
description: asTask?.description ?? buildDesc(submission),
|
|
269
312
|
});
|
|
270
313
|
if (task === undefined) {
|
package/dist/services/tasks.d.ts
CHANGED
|
@@ -2,7 +2,10 @@ import { type Db, type TaskStatus } from '@plandesk/db';
|
|
|
2
2
|
import type { EventBus } from '../events.js';
|
|
3
3
|
import { serializeTask, type PaginationParams } from '../serialize.js';
|
|
4
4
|
type SerializedTask = ReturnType<typeof serializeTask>;
|
|
5
|
-
export
|
|
5
|
+
export declare class InvalidGoalReferenceError extends Error {
|
|
6
|
+
constructor(goalId: string);
|
|
7
|
+
}
|
|
8
|
+
export type NextActionableReason = 'ok' | 'no_tasks' | 'no_todo_tasks' | 'all_blocked' | 'no_active_goal' | 'multiple_active_goals';
|
|
6
9
|
export type NextActionableResult = {
|
|
7
10
|
next_task: SerializedTask | null;
|
|
8
11
|
reason: NextActionableReason;
|
|
@@ -23,6 +26,7 @@ export type CreateTaskInput = {
|
|
|
23
26
|
y?: number;
|
|
24
27
|
assignee?: string | null;
|
|
25
28
|
dueDate?: Date | null;
|
|
29
|
+
goalId?: string;
|
|
26
30
|
tags?: string[];
|
|
27
31
|
};
|
|
28
32
|
export type UpdateTaskInput = {
|
|
@@ -42,6 +46,7 @@ export declare function createTaskService(deps: TaskServiceDeps): {
|
|
|
42
46
|
tags?: import("../serialize.js").SerializedTag[] | undefined;
|
|
43
47
|
id: string;
|
|
44
48
|
project_id: string;
|
|
49
|
+
goal_id: string;
|
|
45
50
|
label: string;
|
|
46
51
|
status: "scope" | "todo" | "in_progress" | "done" | "backlog";
|
|
47
52
|
description: string | null;
|
|
@@ -56,6 +61,7 @@ export declare function createTaskService(deps: TaskServiceDeps): {
|
|
|
56
61
|
tags?: import("../serialize.js").SerializedTag[] | undefined;
|
|
57
62
|
id: string;
|
|
58
63
|
project_id: string;
|
|
64
|
+
goal_id: string;
|
|
59
65
|
label: string;
|
|
60
66
|
status: "scope" | "todo" | "in_progress" | "done" | "backlog";
|
|
61
67
|
description: string | null;
|
|
@@ -70,6 +76,7 @@ export declare function createTaskService(deps: TaskServiceDeps): {
|
|
|
70
76
|
tags?: import("../serialize.js").SerializedTag[] | undefined;
|
|
71
77
|
id: string;
|
|
72
78
|
project_id: string;
|
|
79
|
+
goal_id: string;
|
|
73
80
|
label: string;
|
|
74
81
|
status: "scope" | "todo" | "in_progress" | "done" | "backlog";
|
|
75
82
|
description: string | null;
|
|
@@ -84,6 +91,7 @@ export declare function createTaskService(deps: TaskServiceDeps): {
|
|
|
84
91
|
tags?: import("../serialize.js").SerializedTag[] | undefined;
|
|
85
92
|
id: string;
|
|
86
93
|
project_id: string;
|
|
94
|
+
goal_id: string;
|
|
87
95
|
label: string;
|
|
88
96
|
status: "scope" | "todo" | "in_progress" | "done" | "backlog";
|
|
89
97
|
description: string | null;
|
|
@@ -96,6 +104,7 @@ export declare function createTaskService(deps: TaskServiceDeps): {
|
|
|
96
104
|
} | undefined;
|
|
97
105
|
delete(id: string): boolean;
|
|
98
106
|
nextActionable(projectId: string, filter?: {
|
|
107
|
+
goalId?: string;
|
|
99
108
|
tags?: string[];
|
|
100
109
|
}): NextActionableResult | undefined;
|
|
101
110
|
};
|
package/dist/services/tasks.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { createTag, createTask, deleteEdgesByTaskId, deleteTask as dbDeleteTask, deleteTaskTagsByTaskId, getProject, getTagByName, getTask, InvalidTaskStatusError, isTaskStatus, listEdges, listTagsByTaskForProject, listTagsForTask, listTasks, nullDocumentsLinkedTask, setTaskTags, taskIdsWithAnyTagName, updateTask, } from '@plandesk/db';
|
|
1
|
+
import { createTag, createTask, deleteEdgesByTaskId, deleteTask as dbDeleteTask, deleteTaskTagsByTaskId, getOrCreateDefaultGoal, getProject, listGoals, getTagByName, getTask, InvalidTaskStatusError, isTaskStatus, listEdges, listTagsByTaskForProject, listTagsForTask, listTasks, nullDocumentsLinkedTask, setTaskTags, taskIdsWithAnyTagName, updateTask, } from '@plandesk/db';
|
|
2
2
|
import { serializeTask } from '../serialize.js';
|
|
3
3
|
import { normalizeTagName } from './tags.js';
|
|
4
|
+
export class InvalidGoalReferenceError extends Error {
|
|
5
|
+
constructor(goalId) {
|
|
6
|
+
super(`Goal ${goalId} does not exist in this project`);
|
|
7
|
+
this.name = 'InvalidGoalReferenceError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
4
10
|
// depends_on: prerequisite = to, dependent = from. All other labels: prerequisite = from, dependent = to.
|
|
5
11
|
function prerequisiteAndDependent(edge) {
|
|
6
12
|
if (edge.fromTaskId === edge.toTaskId) {
|
|
@@ -61,9 +67,15 @@ export function createTaskService(deps) {
|
|
|
61
67
|
if (!project) {
|
|
62
68
|
return undefined;
|
|
63
69
|
}
|
|
70
|
+
if (input.goalId !== undefined &&
|
|
71
|
+
!listGoals(db, projectId).some((g) => g.id === input.goalId)) {
|
|
72
|
+
throw new InvalidGoalReferenceError(input.goalId);
|
|
73
|
+
}
|
|
64
74
|
const { task, tags } = db.transaction((tx) => {
|
|
75
|
+
const goalId = input.goalId ?? getOrCreateDefaultGoal(tx, projectId).id;
|
|
65
76
|
const row = createTask(tx, {
|
|
66
77
|
projectId,
|
|
78
|
+
goalId,
|
|
67
79
|
label: input.label,
|
|
68
80
|
status: input.status,
|
|
69
81
|
description: input.description,
|
|
@@ -128,14 +140,28 @@ export function createTaskService(deps) {
|
|
|
128
140
|
eventBus.emit({ type: 'canvas_updated', projectId });
|
|
129
141
|
return true;
|
|
130
142
|
},
|
|
131
|
-
// filter.
|
|
132
|
-
//
|
|
133
|
-
// evaluated against all tasks in the project.
|
|
143
|
+
// filter.goalId scopes candidates to one goal; when omitted, the project's sole
|
|
144
|
+
// active goal is resolved. filter.tags (OR semantics) composes with goal scope;
|
|
145
|
+
// prerequisite completion is still evaluated against all tasks in the project.
|
|
134
146
|
nextActionable(projectId, filter = {}) {
|
|
135
147
|
const project = getProject(db, projectId);
|
|
136
148
|
if (!project) {
|
|
137
149
|
return undefined;
|
|
138
150
|
}
|
|
151
|
+
let goalId = filter.goalId;
|
|
152
|
+
if (goalId === undefined) {
|
|
153
|
+
const active = listGoals(db, projectId).filter((goal) => goal.status === 'active');
|
|
154
|
+
if (active.length === 0) {
|
|
155
|
+
return { next_task: null, reason: 'no_active_goal', blocked: [] };
|
|
156
|
+
}
|
|
157
|
+
if (active.length > 1) {
|
|
158
|
+
return { next_task: null, reason: 'multiple_active_goals', blocked: [] };
|
|
159
|
+
}
|
|
160
|
+
goalId = active[0]?.id;
|
|
161
|
+
}
|
|
162
|
+
else if (!listGoals(db, projectId).some((goal) => goal.id === goalId)) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
139
165
|
const tasks = listTasks(db, projectId).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
140
166
|
const edges = listEdges(db, projectId);
|
|
141
167
|
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
@@ -160,7 +186,9 @@ export function createTaskService(deps) {
|
|
|
160
186
|
if (tasks.length === 0) {
|
|
161
187
|
return { next_task: null, reason: 'no_tasks', blocked: [] };
|
|
162
188
|
}
|
|
163
|
-
const todoTasks = tasks.filter((task) => task.
|
|
189
|
+
const todoTasks = tasks.filter((task) => task.goalId === goalId &&
|
|
190
|
+
task.status === 'todo' &&
|
|
191
|
+
(tagMatches === undefined || tagMatches.has(task.id)));
|
|
164
192
|
if (todoTasks.length === 0) {
|
|
165
193
|
return { next_task: null, reason: 'no_todo_tasks', blocked: [] };
|
|
166
194
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plandesk/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"typescript": "^6.0.3",
|
|
20
20
|
"vitest": "^3.2.6",
|
|
21
|
-
"@plandesk/db": "0.
|
|
21
|
+
"@plandesk/db": "0.7.0",
|
|
22
22
|
"@plandesk/sync-server": "0.5.0"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.document-editor-content img,.document-reader-content img,.portal-document-content img{max-width:100%;height:auto}.document-editor-content table,.document-reader-content table,.portal-document-content table{border-collapse:collapse;table-layout:fixed;width:100%;margin:.75rem 0}.document-editor-content th,.document-editor-content td,.document-reader-content th,.document-reader-content td,.portal-document-content th,.portal-document-content td{text-align:left;vertical-align:top;border:1px solid #d1d5db;padding:.375rem .625rem}.document-editor-content th,.document-reader-content th,.portal-document-content th{background:#f3f4f6;font-weight:600}.document-editor-content ul[data-type=taskList],.document-reader-content ul[data-type=taskList],.portal-document-content ul[data-type=taskList]{padding-left:0;list-style:none}.document-editor-content ul[data-type=taskList] li,.document-reader-content ul[data-type=taskList] li,.portal-document-content ul[data-type=taskList] li{align-items:flex-start;gap:.5rem;display:flex}.document-editor-content ul[data-type=taskList] li>label,.document-reader-content ul[data-type=taskList] li>label,.portal-document-content ul[data-type=taskList] li>label{margin-top:.15rem}.document-reader-content li[data-type=taskItem],.portal-document-content li[data-type=taskItem]{list-style:none}.document-reader-content li[data-type=taskItem]:before,.portal-document-content li[data-type=taskItem]:before{content:"☐";margin-right:.4rem}.document-reader-content li[data-type=taskItem][data-checked=true]:before,.portal-document-content li[data-type=taskItem][data-checked=true]:before{content:"☑"}.react-flow{--xy-edge-stroke-default:#b1b1b7;--xy-edge-stroke-width-default:1;--xy-edge-stroke-selected-default:#555;--xy-connectionline-stroke-default:#b1b1b7;--xy-connectionline-stroke-width-default:1;--xy-attribution-background-color-default:#ffffff80;--xy-minimap-background-color-default:#fff;--xy-minimap-mask-background-color-default:#f0f0f099;--xy-minimap-mask-stroke-color-default:transparent;--xy-minimap-mask-stroke-width-default:1;--xy-minimap-node-background-color-default:#e2e2e2;--xy-minimap-node-stroke-color-default:transparent;--xy-minimap-node-stroke-width-default:2;--xy-background-color-default:transparent;--xy-background-pattern-dots-color-default:#91919a;--xy-background-pattern-lines-color-default:#eee;--xy-background-pattern-cross-color-default:#e2e2e2;background-color:var(--xy-background-color,var(--xy-background-color-default));--xy-node-color-default:inherit;--xy-node-border-default:1px solid #1a192b;--xy-node-background-color-default:#fff;--xy-node-group-background-color-default:#f0f0f040;--xy-node-boxshadow-hover-default:0 1px 4px 1px #00000014;--xy-node-boxshadow-selected-default:0 0 0 .5px #1a192b;--xy-node-border-radius-default:3px;--xy-handle-background-color-default:#1a192b;--xy-handle-border-color-default:#fff;--xy-selection-background-color-default:#0059dc14;--xy-selection-border-default:1px dotted #0059dccc;--xy-controls-button-background-color-default:#fefefe;--xy-controls-button-background-color-hover-default:#f4f4f4;--xy-controls-button-color-default:inherit;--xy-controls-button-color-hover-default:inherit;--xy-controls-button-border-color-default:#eee;--xy-controls-box-shadow-default:0 0 2px 1px #00000014;--xy-edge-label-background-color-default:#fff;--xy-edge-label-color-default:inherit;--xy-resize-background-color-default:#3367d9;direction:ltr}.react-flow.dark{--xy-edge-stroke-default:#3e3e3e;--xy-edge-stroke-width-default:1;--xy-edge-stroke-selected-default:#727272;--xy-connectionline-stroke-default:#b1b1b7;--xy-connectionline-stroke-width-default:1;--xy-attribution-background-color-default:#96969640;--xy-minimap-background-color-default:#141414;--xy-minimap-mask-background-color-default:#3c3c3c99;--xy-minimap-mask-stroke-color-default:transparent;--xy-minimap-mask-stroke-width-default:1;--xy-minimap-node-background-color-default:#2b2b2b;--xy-minimap-node-stroke-color-default:transparent;--xy-minimap-node-stroke-width-default:2;--xy-background-color-default:#141414;--xy-background-pattern-dots-color-default:#777;--xy-background-pattern-lines-color-default:#777;--xy-background-pattern-cross-color-default:#777;--xy-node-color-default:#f8f8f8;--xy-node-border-default:1px solid #3c3c3c;--xy-node-background-color-default:#1e1e1e;--xy-node-group-background-color-default:#f0f0f040;--xy-node-boxshadow-hover-default:0 1px 4px 1px #ffffff14;--xy-node-boxshadow-selected-default:0 0 0 .5px #999;--xy-handle-background-color-default:#bebebe;--xy-handle-border-color-default:#1e1e1e;--xy-selection-background-color-default:#c8c8dc14;--xy-selection-border-default:1px dotted #c8c8dccc;--xy-controls-button-background-color-default:#2b2b2b;--xy-controls-button-background-color-hover-default:#3e3e3e;--xy-controls-button-color-default:#f8f8f8;--xy-controls-button-color-hover-default:#fff;--xy-controls-button-border-color-default:#5b5b5b;--xy-controls-box-shadow-default:0 0 2px 1px #00000014;--xy-edge-label-background-color-default:#141414;--xy-edge-label-color-default:#f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props,var(--xy-background-color,var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke,var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width,var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke,var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width,var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{pointer-events:none;position:absolute;overflow:visible}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:.5s linear infinite dashdraw}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected,var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke,var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke,var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:.5s linear infinite dashdraw}svg.react-flow__connectionline{z-index:1001;position:absolute;overflow:visible}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{-webkit-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default;position:absolute}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:0 0;pointer-events:none}.react-flow__nodesselection-rect{pointer-events:all;cursor:grab;position:absolute}.react-flow__handle{pointer-events:none;background-color:var(--xy-handle-background-color,var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color,var(--xy-handle-border-color-default));border-radius:100%;width:6px;min-width:5px;height:6px;min-height:5px;position:absolute}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;bottom:0;left:50%;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{z-index:5;margin:15px;position:absolute}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px)translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px)translateY(-50%)}.react-flow__attribution{background:var(--xy-attribution-background-color,var(--xy-attribution-background-color-default));margin:0;padding:2px 3px;font-size:10px}.react-flow__attribution a{color:#999;text-decoration:none}@keyframes dashdraw{0%{stroke-dashoffset:10px}}.react-flow__edgelabel-renderer{pointer-events:none;-webkit-user-select:none;user-select:none;width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__viewport-portal{-webkit-user-select:none;user-select:none;width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__minimap{background:var(--xy-minimap-background-color-props,var(--xy-minimap-background-color,var(--xy-minimap-background-color-default)))}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var(--xy-minimap-mask-background-color-props,var(--xy-minimap-mask-background-color,var(--xy-minimap-mask-background-color-default)));stroke:var(--xy-minimap-mask-stroke-color-props,var(--xy-minimap-mask-stroke-color,var(--xy-minimap-mask-stroke-color-default)));stroke-width:var(--xy-minimap-mask-stroke-width-props,var(--xy-minimap-mask-stroke-width,var(--xy-minimap-mask-stroke-width-default)))}.react-flow__minimap-node{fill:var(--xy-minimap-node-background-color-props,var(--xy-minimap-node-background-color,var(--xy-minimap-node-background-color-default)));stroke:var(--xy-minimap-node-stroke-color-props,var(--xy-minimap-node-stroke-color,var(--xy-minimap-node-stroke-color-default)));stroke-width:var(--xy-minimap-node-stroke-width-props,var(--xy-minimap-node-stroke-width,var(--xy-minimap-node-stroke-width-default)))}.react-flow__background-pattern.dots{fill:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-dots-color-default)))}.react-flow__background-pattern.lines{stroke:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-lines-color-default)))}.react-flow__background-pattern.cross{stroke:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-cross-color-default)))}.react-flow__controls{box-shadow:var(--xy-controls-box-shadow,var(--xy-controls-box-shadow-default));flex-direction:column;display:flex}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{background:var(--xy-controls-button-background-color,var(--xy-controls-button-background-color-default));border:none;border-bottom:1px solid var(--xy-controls-button-border-color-props,var(--xy-controls-button-border-color,var(--xy-controls-button-border-color-default)));width:26px;height:26px;color:var(--xy-controls-button-color-props,var(--xy-controls-button-color,var(--xy-controls-button-color-default)));cursor:pointer;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;padding:4px;display:flex}.react-flow__controls-button svg{fill:currentColor;width:100%;max-width:12px;max-height:12px}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{border-radius:var(--xy-node-border-radius,var(--xy-node-border-radius-default));width:150px;color:var(--xy-node-color,var(--xy-node-color-default));text-align:center;border:var(--xy-node-border,var(--xy-node-border-default));background-color:var(--xy-node-background-color,var(--xy-node-background-color-default));padding:10px;font-size:12px}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover,var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected,var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color,var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color,var(--xy-selection-background-color-default));border:var(--xy-selection-border,var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var(--xy-controls-button-background-color-hover-props,var(--xy-controls-button-background-color-hover,var(--xy-controls-button-background-color-hover-default)));color:var(--xy-controls-button-color-hover-props,var(--xy-controls-button-color-hover,var(--xy-controls-button-color-hover-default)))}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var(--xy-controls-button-border-color-props,var(--xy-controls-button-border-color,var(--xy-controls-button-border-color-default)))}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{background-color:var(--xy-resize-background-color,var(--xy-resize-background-color-default));border:1px solid #fff;border-radius:1px;width:5px;height:5px;translate:-50% -50%}.react-flow__resize-control.handle.left{top:50%;left:0}.react-flow__resize-control.handle.right{top:50%;left:100%}.react-flow__resize-control.handle.top{top:0;left:50%}.react-flow__resize-control.handle.bottom{top:100%;left:50%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color,var(--xy-resize-background-color-default));border-style:solid;border-width:0}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;height:100%;top:0;transform:translate(-50%)}.react-flow__resize-control.line.left{border-left-width:1px;left:0}.react-flow__resize-control.line.right{border-right-width:1px;left:100%}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{width:100%;height:1px;left:0;transform:translateY(-50%)}.react-flow__resize-control.line.top{border-top-width:1px;top:0}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color,var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color,var(--xy-edge-label-color-default))}
|