@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.
@@ -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: asTask?.status ?? 'todo',
310
+ status: 'scope',
268
311
  description: asTask?.description ?? buildDesc(submission),
269
312
  });
270
313
  if (task === undefined) {
@@ -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 type NextActionableReason = 'ok' | 'no_tasks' | 'no_todo_tasks' | 'all_blocked';
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
  };
@@ -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.tags (OR semantics): only tasks carrying ANY of the given tag names
132
- // are candidates for next_task / blocked; prerequisite completion is still
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.status === 'todo' && (tagMatches === undefined || tagMatches.has(task.id)));
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.8.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.6.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))}