Skip to content

Commit c96a35e

Browse files
committed
feat: make the right sidebar edit the tasks
Signed-off-by: Simon Emms <simon@simonemms.com>
1 parent ca9b670 commit c96a35e

23 files changed

Lines changed: 3095 additions & 106 deletions

CLAUDE.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -417,18 +417,26 @@ Key expectations:
417417

418418
Claude should not introduce formatting changes that contradict Prettier output.
419419

420-
**IMPORTANT: Before completing any work, always run `npm run format` and
421-
`npm run lint` to ensure all files are properly formatted. This is mandatory.**
420+
**IMPORTANT: Before completing any work, always run the following commands in
421+
order and ensure they all pass. This is mandatory.**
422+
423+
```sh
424+
npm run format # auto-formats all files
425+
npm run lint # Prettier check + ESLint + markdownlint
426+
npm run check # svelte-check TypeScript type checking
427+
npm run dev # verify the app starts without errors
428+
pre-commit run --all-files # full pre-commit suite (license, YAML, markdown, ESLint, format)
429+
```
422430

423431
The `npm run lint` command includes:
424432

425433
- Prettier code formatting check
426434
- ESLint for JavaScript/TypeScript code
427435
- Markdown linting (markdownlint-cli2) for all .md files
428436

429-
**IMPORTANT: After fixing format and lint issues, run `npm run dev` to verify
430-
the application starts without errors. Fix any runtime errors before considering
431-
the work complete. This is mandatory.**
437+
The `npm run check` command runs `svelte-kit sync && svelte-check` and must
438+
report 0 errors before work is considered complete. Fix type errors in source
439+
and test files; do not suppress them.
432440

433441
---
434442

@@ -439,8 +447,9 @@ the work complete. This is mandatory.**
439447
- `no-undef` is intentionally disabled
440448
- Svelte files are type-checked via `typescript-eslint`
441449

442-
**IMPORTANT: Before completing any work, always run `npm run lint` to ensure
443-
all code passes linting. This is mandatory.**
450+
**IMPORTANT: Before completing any work, always run `npm run lint` and
451+
`npm run check` to ensure all code passes linting and type checking. This is
452+
mandatory.**
444453

445454
Do not:
446455

src/lib/i18n/messages/en.json

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
},
2828
"inspector": {
2929
"empty": "Select a node to inspect it.",
30+
"name": "Name",
31+
"nodeId": "Node ID",
3032
"id": "ID",
3133
"kind": "Kind",
3234
"detail": "Detail",
@@ -45,7 +47,108 @@
4547
"moveUpLabel": "Move task up",
4648
"moveDown": "↓ Move down",
4749
"moveDownLabel": "Move task down",
48-
"delete": "Delete task"
50+
"delete": "Delete task",
51+
"set": {
52+
"title": "Assignments",
53+
"addRow": "+ Add assignment",
54+
"keyLabel": "Key",
55+
"valueLabel": "Value",
56+
"removeRow": "Remove",
57+
"overrideLabel": "Type for {{key}}",
58+
"overrideAuto": "Auto",
59+
"overrideString": "String",
60+
"overrideNumber": "Number",
61+
"overrideBoolean": "Boolean",
62+
"overrideNull": "Null",
63+
"errorObjectArray": "Objects and arrays are not supported",
64+
"errorNotNumber": "Must be a valid number",
65+
"errorNotBoolean": "Must be true or false"
66+
},
67+
"wait": {
68+
"title": "Duration",
69+
"seconds": "Seconds",
70+
"minutes": "Minutes",
71+
"hours": "Hours",
72+
"days": "Days"
73+
},
74+
"fork": {
75+
"compete": "Compete mode",
76+
"competeHint": "First branch to finish wins"
77+
},
78+
"loop": {
79+
"title": "Loop configuration",
80+
"in": "Collection",
81+
"each": "Item variable",
82+
"at": "Index variable",
83+
"while": "Break condition"
84+
},
85+
"callGrpc": {
86+
"title": "Call gRPC",
87+
"proto": {
88+
"title": "Proto",
89+
"endpoint": "Endpoint"
90+
},
91+
"service": {
92+
"title": "Service",
93+
"name": "Service name",
94+
"host": "Host",
95+
"hostPlaceholder": "localhost",
96+
"port": "Port",
97+
"portPlaceholder": "50051",
98+
"portInvalid": "Port must be a positive integer"
99+
},
100+
"method": "Method",
101+
"arguments": {
102+
"title": "Arguments",
103+
"addRow": "+ Add argument",
104+
"keyLabel": "Name",
105+
"valueLabel": "Value",
106+
"removeRow": "Remove argument"
107+
}
108+
},
109+
"callHttp": {
110+
"title": "Call HTTP",
111+
"method": "Method",
112+
"endpoint": "URL",
113+
"endpointRequired": "URL is required",
114+
"headers": {
115+
"title": "Headers",
116+
"addRow": "+ Add header",
117+
"keyLabel": "Header name",
118+
"valueLabel": "Value",
119+
"removeRow": "Remove header"
120+
},
121+
"body": {
122+
"title": "Body",
123+
"addBody": "+ Add body",
124+
"removeBody": "Remove body"
125+
}
126+
},
127+
"common": {
128+
"title": "Common",
129+
"if": {
130+
"label": "Condition (if)"
131+
},
132+
"metadata": {
133+
"title": "Metadata",
134+
"addRow": "+ Add entry",
135+
"keyLabel": "Key",
136+
"valueLabel": "Value",
137+
"removeRow": "Remove",
138+
"overrideLabel": "Type for {{key}}",
139+
"type": {
140+
"auto": "Auto",
141+
"string": "String",
142+
"number": "Number",
143+
"boolean": "Boolean",
144+
"null": "Null"
145+
}
146+
}
147+
}
148+
},
149+
"errors": {
150+
"duplicateKey": "Duplicate key",
151+
"invalidNumber": "Must be a valid number"
49152
},
50153
"sidebar": {
51154
"document": "Document",

src/lib/tasks/actions.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import type {
4545
WaitConfig,
4646
WorkflowFile,
4747
} from './model';
48+
import { ZIGFLOW_ID_KEY } from './model';
4849
import { TASK_REGISTRY } from './registry';
4950

5051
// ---------------------------------------------------------------------------
@@ -182,13 +183,13 @@ export function createTaskNode(name: string, config: TaskConfig): TaskNode {
182183
type: 'task',
183184
name,
184185
config,
185-
metadata: { __zigflow_id: zid },
186+
metadata: { [ZIGFLOW_ID_KEY]: zid },
186187
};
187188
}
188189

189190
export function createSetNode(
190191
name: string,
191-
assignments: Record<string, string>,
192+
assignments: SetConfig['assignments'],
192193
): TaskNode {
193194
const config: SetConfig = { kind: 'set', assignments };
194195
return createTaskNode(name, config);
@@ -275,7 +276,7 @@ export function createSwitchNode(name: string): SwitchNode {
275276
type: 'switch',
276277
name,
277278
branches: [],
278-
metadata: { __zigflow_id: zid },
279+
metadata: { [ZIGFLOW_ID_KEY]: zid },
279280
};
280281
}
281282

@@ -290,7 +291,7 @@ export function addSwitchBranch(
290291
label,
291292
condition,
292293
graph: emptyFlowGraph(),
293-
metadata: { __zigflow_id: zid },
294+
metadata: { [ZIGFLOW_ID_KEY]: zid },
294295
};
295296
return { ...node, branches: [...node.branches, branch] };
296297
}
@@ -343,7 +344,7 @@ export function createForkNode(name: string): ForkNode {
343344
name,
344345
compete: false,
345346
branches: [],
346-
metadata: { __zigflow_id: zid },
347+
metadata: { [ZIGFLOW_ID_KEY]: zid },
347348
};
348349
}
349350

@@ -353,7 +354,7 @@ export function addForkBranch(node: ForkNode, label: string): ForkNode {
353354
id: zid,
354355
label,
355356
graph: emptyFlowGraph(),
356-
metadata: { __zigflow_id: zid },
357+
metadata: { [ZIGFLOW_ID_KEY]: zid },
357358
};
358359
return { ...node, branches: [...node.branches, branch] };
359360
}
@@ -402,7 +403,7 @@ export function createTryNode(name: string): TryNode {
402403
type: 'try',
403404
name,
404405
tryGraph: emptyFlowGraph(),
405-
metadata: { __zigflow_id: zid },
406+
metadata: { [ZIGFLOW_ID_KEY]: zid },
406407
};
407408
}
408409

@@ -426,7 +427,7 @@ export function createLoopNode(name: string, inExpr: string): LoopNode {
426427
name,
427428
in: inExpr,
428429
bodyGraph: emptyFlowGraph(),
429-
metadata: { __zigflow_id: zid },
430+
metadata: { [ZIGFLOW_ID_KEY]: zid },
430431
};
431432
}
432433

src/lib/tasks/model.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@
2020
// representation (IR). It must remain free of UI dependencies and serialisable
2121
// to JSON at all times.
2222

23+
// ---------------------------------------------------------------------------
24+
// Internal metadata key — used to persist stable node/branch IDs across
25+
// round-trips through the Zigflow DSL YAML format.
26+
// ---------------------------------------------------------------------------
27+
28+
export const ZIGFLOW_ID_KEY = '__zigflow_id';
29+
2330
// ---------------------------------------------------------------------------
2431
// Document & file
2532
// ---------------------------------------------------------------------------
@@ -160,9 +167,14 @@ export type LoopNode = {
160167
// Each config carries a `kind` discriminant for exhaustive switching.
161168
// ---------------------------------------------------------------------------
162169

170+
// Assignment values support JSON primitives. Objects and arrays are
171+
// intentionally excluded from the Studio editor (they remain valid in
172+
// the IR for round-trip purposes but cannot be created via the UI).
173+
export type AssignmentValue = string | number | boolean | null;
174+
163175
export type SetConfig = {
164176
kind: 'set';
165-
assignments: Record<string, string>;
177+
assignments: Record<string, AssignmentValue>;
166178
};
167179

168180
export type CallHTTPConfig = {

src/lib/tasks/parse.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import type {
6464
WaitConfig,
6565
WorkflowFile,
6666
} from './model';
67+
import { ZIGFLOW_ID_KEY } from './model';
6768

6869
// ---------------------------------------------------------------------------
6970
// Public types
@@ -339,13 +340,13 @@ function resolveId(
339340
ctx: ParseCtx,
340341
): { id: string; metadata: Record<string, unknown> } {
341342
const metadata = (def['metadata'] ?? {}) as Record<string, unknown>;
342-
const existing = metadata['__zigflow_id'];
343+
const existing = metadata[ZIGFLOW_ID_KEY];
343344
if (typeof existing === 'string' && existing.length > 0) {
344345
return { id: existing, metadata };
345346
}
346347
const id = crypto.randomUUID();
347348
ctx.modified = true;
348-
return { id, metadata: { ...metadata, __zigflow_id: id } };
349+
return { id, metadata: { ...metadata, [ZIGFLOW_ID_KEY]: id } };
349350
}
350351

351352
// ---------------------------------------------------------------------------
@@ -412,7 +413,11 @@ function parseTaskConfig(
412413
function buildSetConfig(def: RawEntry): SetConfig {
413414
return {
414415
kind: 'set',
415-
assignments: (def['set'] ?? {}) as Record<string, string>,
416+
// js-yaml parses YAML scalars to their native JS types (boolean, number,
417+
// null, string). The cast here is intentionally permissive; invalid
418+
// complex types (objects/arrays) are blocked by the Studio editor, not
419+
// the parser, to allow round-trip fidelity for manually authored files.
420+
assignments: (def['set'] ?? {}) as SetConfig['assignments'],
416421
};
417422
}
418423

src/lib/tasks/registry.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type {
2828
TaskNode,
2929
TryNode,
3030
} from './model';
31+
import { ZIGFLOW_ID_KEY } from './model';
3132

3233
// ---------------------------------------------------------------------------
3334
// Types
@@ -62,7 +63,7 @@ function taskNode(name: string, type: NodeType): TaskNode {
6263
type: 'task',
6364
name,
6465
config: defaultConfig(type),
65-
metadata: { __zigflow_id: nid },
66+
metadata: { [ZIGFLOW_ID_KEY]: nid },
6667
} as TaskNode;
6768
}
6869

@@ -78,7 +79,7 @@ function defaultConfig(type: NodeType): TaskNode['config'] {
7879
protoEndpoint: '',
7980
serviceName: '',
8081
serviceHost: '',
81-
servicePort: 443,
82+
servicePort: 50051,
8283
method: '',
8384
};
8485
case 'call-activity':
@@ -205,7 +206,7 @@ export const TASK_REGISTRY: readonly TaskDefinition[] = [
205206
type: 'switch',
206207
name: 'switch',
207208
branches: [],
208-
metadata: { __zigflow_id: nid },
209+
metadata: { [ZIGFLOW_ID_KEY]: nid },
209210
};
210211
},
211212
},
@@ -222,7 +223,7 @@ export const TASK_REGISTRY: readonly TaskDefinition[] = [
222223
name: 'fork',
223224
compete: false,
224225
branches: [],
225-
metadata: { __zigflow_id: nid },
226+
metadata: { [ZIGFLOW_ID_KEY]: nid },
226227
};
227228
},
228229
},
@@ -238,7 +239,7 @@ export const TASK_REGISTRY: readonly TaskDefinition[] = [
238239
type: 'try',
239240
name: 'try-catch',
240241
tryGraph: emptyGraph(),
241-
metadata: { __zigflow_id: nid },
242+
metadata: { [ZIGFLOW_ID_KEY]: nid },
242243
};
243244
},
244245
},
@@ -255,7 +256,7 @@ export const TASK_REGISTRY: readonly TaskDefinition[] = [
255256
name: 'loop',
256257
in: '$.',
257258
bodyGraph: emptyGraph(),
258-
metadata: { __zigflow_id: nid },
259+
metadata: { [ZIGFLOW_ID_KEY]: nid },
259260
};
260261
},
261262
},

0 commit comments

Comments
 (0)