Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
486 commits
Select commit Hold shift + click to select a range
4a5fe39
Match the inline script tag case-insensitively in the early-mode test
knihit Oct 27, 2026
2c4f019
Address review findings on the theme: one palette, forced colors, lea…
knihit Oct 28, 2026
ec9fcdb
Keep text readable on the selected wash in both modes
knihit Oct 28, 2026
6095fdb
Match a script tag with attributes in the early-mode test
knihit Oct 28, 2026
ceac0e6
Make the colour guard see every colour literal in the canvas CSS
knihit Oct 28, 2026
e1491d7
Drop the claim that a reload drops the session from the top bar comment
knihit Oct 28, 2026
f976a63
Read runs through the typed client and add the run core
knihit Oct 26, 2026
1ca073d
Keep the run_id of a 409 and reject a run reply that breaks the contract
knihit Oct 27, 2026
b249cc3
Follow only the run that was started, and tidy the run core
knihit Oct 27, 2026
2d2e3d6
Follow a run through a blip, join an active run, and bound every call
knihit Oct 28, 2026
a64b855
Give the fake API of the source tests the run calls
knihit Oct 28, 2026
2c59c0f
Say when a start may have started, retry 408 and 429, and tighten the…
knihit Oct 28, 2026
0fe9409
Give the session memory tests the run calls and the memory argument
knihit Oct 28, 2026
9eb7ff2
Draw the run on the Execution tab with a Run button and live node states
knihit Oct 26, 2026
53f505d
Test the Execution tab end to end, capture its screenshots and docume…
knihit Oct 26, 2026
9065c8a
Keep the goal across tabs, announce the run error and say the run may…
knihit Oct 27, 2026
7fc4799
Refresh the Execution screenshots: read-only goal box and square port…
knihit Oct 27, 2026
ad67b0e
Take the running captures without the tooltip of the Run button
knihit Oct 27, 2026
1ea862b
Announce the run outside the tabs, and put its alert under the Run bu…
knihit Oct 28, 2026
a3b193e
Warn when the run has other stages than the graph
knihit Oct 28, 2026
98371fe
Fix the run announcements, Safari Enter, and the drift notice
knihit Oct 28, 2026
98719e9
Let go of a run when another workflow opens, and stop telling the per…
knihit Oct 28, 2026
668b427
Tell the person to reload the page to draw a changed workflow file
knihit Oct 28, 2026
e4debfb
Follow a run that is already active, retry failed reads and time out …
knihit Oct 26, 2026
0db5931
Test that a second start in the same tick is ignored
knihit Oct 26, 2026
080bad0
Document read retries, the read-only goal box and the stale-graph limit
knihit Oct 26, 2026
02d9bcd
Name the read-retry rule and make the README match it
knihit Oct 26, 2026
22d7ef0
Join a run that is still going when the page is opened again after a …
knihit Oct 27, 2026
64e8a79
Add the Execution tab changelog line
knihit Oct 27, 2026
10e08e8
Let Run take over a pending look for an earlier run, and retry that look
knihit Oct 28, 2026
4dcaed9
Tell a lookup that failed from a run that is gone, and a start with n…
knihit Oct 28, 2026
d9c8530
Label a joined run, keep that it was joined, and say what a reload does
knihit Oct 28, 2026
07f67a8
Forget a run that has ended, keep a joined run on a change of runs, p…
knihit Oct 28, 2026
89ea2ff
Forget a joined run when another workflow opens
knihit Oct 28, 2026
71135c4
Resume a run after a reload on a server whose launch code works once
knihit Oct 28, 2026
43eec96
Add the editing core of the Designer: draft, stage text, findings and…
knihit Oct 27, 2026
c0688c9
Mark the earlier stage too when two stages share a name
knihit Oct 27, 2026
2486db6
Keep the comments and layout of unchanged stages, and make the editor…
knihit Oct 28, 2026
5982c27
Write new stages in the layout of the file's list, and test every lay…
knihit Oct 28, 2026
009bca6
Give the diagnostic in the backtick test its path and severity
knihit Oct 28, 2026
e6f5a8b
Edit the stages of a workflow in the Designer: Properties, pick agent…
knihit Oct 27, 2026
6154d15
Ask before the page is closed or reloaded with changes that are not s…
knihit Oct 27, 2026
21dacf5
Make the Properties pane keyboard-safe, honest about checks, and clea…
knihit Oct 28, 2026
1818dc4
Take the Designer screenshots again
knihit Oct 28, 2026
c932d9e
Keep the read-only note in the layout and say every add and select
knihit Oct 28, 2026
082413e
Select the generated name after Add stage, so typing replaces it
knihit Oct 28, 2026
4fb9dae
Show the names in a field error as code, without backticks
knihit Oct 28, 2026
dd0de2b
Ask before a pasted launch address drops unsaved changes or a followe…
knihit Oct 28, 2026
81bfb7c
Merge wc/07b-properties into wc/07c-save, keep the session across a r…
knihit Oct 28, 2026
aa3d701
Add the docs theme: tokens, bundled fonts and a light/dark mode switch
knihit Oct 25, 2026
3d85076
Give the page a top bar, a header band and themed canvas nodes and ports
knihit Oct 25, 2026
0dadd04
Cover the theme in e2e tests, add a dark screenshot, document the theme
knihit Oct 25, 2026
d6ed5b0
Put the tab strip on the header band and finish the dark canvas ports
knihit Oct 25, 2026
96869a5
Use a plain comment for the font wait helper in the screenshot support
knihit Oct 25, 2026
4ba0e5f
Draw the S ports as jigsaw knobs and sockets, and tidy the theme
knihit Oct 25, 2026
500f8a1
Match the dark canvas muted text to the theme module
knihit Oct 25, 2026
6908577
Give the knob a longer pinched neck and draw the focus ring around th…
knihit Oct 26, 2026
6f06612
Show the system outline for the focused node in forced-colors mode
knihit Oct 26, 2026
a742468
Draw the S ports as square tabs and notches to match the connector de…
knihit Oct 27, 2026
294849a
Give stages and the end node room for the notch and a state badge
knihit Oct 27, 2026
288c26e
Add the theme changelog line
knihit Oct 27, 2026
f309b88
Match the inline script tag case-insensitively in the early-mode test
knihit Oct 27, 2026
309101d
Address review findings on the theme: one palette, forced colors, lea…
knihit Oct 28, 2026
56ff89e
Keep text readable on the selected wash in both modes
knihit Oct 28, 2026
d4b6852
Match a script tag with attributes in the early-mode test
knihit Oct 28, 2026
154e232
Make the colour guard see every colour literal in the canvas CSS
knihit Oct 28, 2026
dc9a59a
Drop the claim that a reload drops the session from the top bar comment
knihit Oct 28, 2026
366a5ab
Allow the early theme script in the page policy by its hash
knihit Oct 28, 2026
1bdb435
Read runs through the typed client and add the run core
knihit Oct 26, 2026
6dac132
Keep the run_id of a 409 and reject a run reply that breaks the contract
knihit Oct 27, 2026
244c386
Follow only the run that was started, and tidy the run core
knihit Oct 27, 2026
9240613
Follow a run through a blip, join an active run, and bound every call
knihit Oct 28, 2026
403d317
Give the fake API of the source tests the run calls
knihit Oct 28, 2026
0d985ca
Say when a start may have started, retry 408 and 429, and tighten the…
knihit Oct 28, 2026
c9d2789
Give the session memory tests the run calls and the memory argument
knihit Oct 28, 2026
639c654
Drop the session on a 401 from a run call, with the message of a lost…
knihit Oct 28, 2026
4a27270
Draw the run on the Execution tab with a Run button and live node states
knihit Oct 26, 2026
100eede
Test the Execution tab end to end, capture its screenshots and docume…
knihit Oct 26, 2026
e12c347
Keep the goal across tabs, announce the run error and say the run may…
knihit Oct 27, 2026
d7b2a22
Refresh the Execution screenshots: read-only goal box and square port…
knihit Oct 27, 2026
5c21be7
Take the running captures without the tooltip of the Run button
knihit Oct 27, 2026
fb2b112
Announce the run outside the tabs, and put its alert under the Run bu…
knihit Oct 28, 2026
ffa8068
Warn when the run has other stages than the graph
knihit Oct 28, 2026
342ec96
Fix the run announcements, Safari Enter, and the drift notice
knihit Oct 28, 2026
9ffea31
Let go of a run when another workflow opens, and stop telling the per…
knihit Oct 28, 2026
9c080c2
Tell the person to reload the page to draw a changed workflow file
knihit Oct 28, 2026
821b64f
Follow a run that is already active, retry failed reads and time out …
knihit Oct 26, 2026
b35e95d
Test that a second start in the same tick is ignored
knihit Oct 26, 2026
7a24c3c
Document read retries, the read-only goal box and the stale-graph limit
knihit Oct 26, 2026
d5cf958
Name the read-retry rule and make the README match it
knihit Oct 26, 2026
45bc2d3
Join a run that is still going when the page is opened again after a …
knihit Oct 27, 2026
cccf7e6
Add the Execution tab changelog line
knihit Oct 27, 2026
dd88eba
Let Run take over a pending look for an earlier run, and retry that look
knihit Oct 28, 2026
367585f
Tell a lookup that failed from a run that is gone, and a start with n…
knihit Oct 28, 2026
c497358
Label a joined run, keep that it was joined, and say what a reload does
knihit Oct 28, 2026
0477959
Forget a run that has ended, keep a joined run on a change of runs, p…
knihit Oct 28, 2026
596694f
Forget a joined run when another workflow opens
knihit Oct 28, 2026
5b2ac46
Resume a run after a reload on a server whose launch code works once
knihit Oct 28, 2026
19c44a5
Test that a 401 on the run start drops the kept session
knihit Oct 28, 2026
450eabb
Add the editing core of the Designer: draft, stage text, findings and…
knihit Oct 27, 2026
d361eb0
Mark the earlier stage too when two stages share a name
knihit Oct 27, 2026
dbf4cc0
Keep the comments and layout of unchanged stages, and make the editor…
knihit Oct 28, 2026
9937b6f
Write new stages in the layout of the file's list, and test every lay…
knihit Oct 28, 2026
1eb7db6
Give the diagnostic in the backtick test its path and severity
knihit Oct 28, 2026
505d017
Edit the stages of a workflow in the Designer: Properties, pick agent…
knihit Oct 27, 2026
c9da0bc
Ask before the page is closed or reloaded with changes that are not s…
knihit Oct 27, 2026
908ab4b
Make the Properties pane keyboard-safe, honest about checks, and clea…
knihit Oct 28, 2026
0c79925
Take the Designer screenshots again
knihit Oct 28, 2026
46a8899
Keep the read-only note in the layout and say every add and select
knihit Oct 28, 2026
3b7c595
Select the generated name after Add stage, so typing replaces it
knihit Oct 28, 2026
6733e92
Show the names in a field error as code, without backticks
knihit Oct 28, 2026
ea5feb0
Ask before a pasted launch address drops unsaved changes or a followe…
knihit Oct 28, 2026
64dd6fd
Merge wc/07b-properties into wc/07c-save, drop the session on a 401 f…
knihit Oct 28, 2026
c33fd58
Test that a 401 on a run start shows the lost-session message and cle…
knihit Oct 28, 2026
6bd1d4d
Add the docs theme: tokens, bundled fonts and a light/dark mode switch
knihit Oct 25, 2026
c5c6322
Give the page a top bar, a header band and themed canvas nodes and ports
knihit Oct 25, 2026
49b918f
Cover the theme in e2e tests, add a dark screenshot, document the theme
knihit Oct 25, 2026
0de5ff6
Put the tab strip on the header band and finish the dark canvas ports
knihit Oct 25, 2026
c727580
Use a plain comment for the font wait helper in the screenshot support
knihit Oct 25, 2026
20028f9
Draw the S ports as jigsaw knobs and sockets, and tidy the theme
knihit Oct 25, 2026
9a5dcd2
Match the dark canvas muted text to the theme module
knihit Oct 25, 2026
37e1b31
Give the knob a longer pinched neck and draw the focus ring around th…
knihit Oct 26, 2026
91ab31e
Show the system outline for the focused node in forced-colors mode
knihit Oct 26, 2026
479ee37
Draw the S ports as square tabs and notches to match the connector de…
knihit Oct 27, 2026
d7989ed
Give stages and the end node room for the notch and a state badge
knihit Oct 27, 2026
7f8ffef
Add the theme changelog line
knihit Oct 27, 2026
70a5403
Match the inline script tag case-insensitively in the early-mode test
knihit Oct 27, 2026
080e507
Address review findings on the theme: one palette, forced colors, lea…
knihit Oct 28, 2026
6d7b621
Keep text readable on the selected wash in both modes
knihit Oct 28, 2026
697f343
Match a script tag with attributes in the early-mode test
knihit Oct 28, 2026
c2d457b
Make the colour guard see every colour literal in the canvas CSS
knihit Oct 28, 2026
ed7e5f8
Drop the claim that a reload drops the session from the top bar comment
knihit Oct 28, 2026
d791911
Allow the early theme script in the page policy by its hash
knihit Oct 28, 2026
8756014
Read runs through the typed client and add the run core
knihit Oct 26, 2026
82a726d
Keep the run_id of a 409 and reject a run reply that breaks the contract
knihit Oct 27, 2026
022a48a
Follow only the run that was started, and tidy the run core
knihit Oct 27, 2026
db8f6b2
Follow a run through a blip, join an active run, and bound every call
knihit Oct 28, 2026
79fae03
Give the fake API of the source tests the run calls
knihit Oct 28, 2026
a1cea4e
Say when a start may have started, retry 408 and 429, and tighten the…
knihit Oct 28, 2026
e58eb6d
Give the session memory tests the run calls and the memory argument
knihit Oct 28, 2026
7db0b21
Drop the session on a 401 from a run call, with the message of a lost…
knihit Oct 28, 2026
b2b5e9a
Build the API of the refused-address notice test with the helper that…
knihit Oct 29, 2026
6d11062
Draw the run on the Execution tab with a Run button and live node states
knihit Oct 26, 2026
978693b
Test the Execution tab end to end, capture its screenshots and docume…
knihit Oct 26, 2026
3aec1d8
Keep the goal across tabs, announce the run error and say the run may…
knihit Oct 27, 2026
9c92b8b
Refresh the Execution screenshots: read-only goal box and square port…
knihit Oct 27, 2026
b1ab10b
Take the running captures without the tooltip of the Run button
knihit Oct 27, 2026
5ae3677
Announce the run outside the tabs, and put its alert under the Run bu…
knihit Oct 28, 2026
5b789a3
Warn when the run has other stages than the graph
knihit Oct 28, 2026
070abe1
Fix the run announcements, Safari Enter, and the drift notice
knihit Oct 28, 2026
4d6228d
Let go of a run when another workflow opens, and stop telling the per…
knihit Oct 28, 2026
ccb6a32
Tell the person to reload the page to draw a changed workflow file
knihit Oct 28, 2026
950e401
Follow a run that is already active, retry failed reads and time out …
knihit Oct 26, 2026
df69a46
Test that a second start in the same tick is ignored
knihit Oct 26, 2026
006bc13
Document read retries, the read-only goal box and the stale-graph limit
knihit Oct 26, 2026
1364256
Name the read-retry rule and make the README match it
knihit Oct 26, 2026
6bd24e0
Join a run that is still going when the page is opened again after a …
knihit Oct 27, 2026
827f43f
Add the Execution tab changelog line
knihit Oct 27, 2026
9c242bb
Let Run take over a pending look for an earlier run, and retry that look
knihit Oct 28, 2026
0a7b9a7
Tell a lookup that failed from a run that is gone, and a start with n…
knihit Oct 28, 2026
d284085
Label a joined run, keep that it was joined, and say what a reload does
knihit Oct 28, 2026
84971fa
Forget a run that has ended, keep a joined run on a change of runs, p…
knihit Oct 28, 2026
fe1535b
Forget a joined run when another workflow opens
knihit Oct 28, 2026
38cb9b0
Resume a run after a reload on a server whose launch code works once
knihit Oct 28, 2026
dacddb5
Test that a 401 on the run start drops the kept session
knihit Oct 28, 2026
90a3810
Add the editing core of the Designer: draft, stage text, findings and…
knihit Oct 27, 2026
49cb97c
Mark the earlier stage too when two stages share a name
knihit Oct 27, 2026
5b3cf2e
Keep the comments and layout of unchanged stages, and make the editor…
knihit Oct 28, 2026
a909274
Write new stages in the layout of the file's list, and test every lay…
knihit Oct 28, 2026
4758207
Give the diagnostic in the backtick test its path and severity
knihit Oct 28, 2026
596e84f
Edit the stages of a workflow in the Designer: Properties, pick agent…
knihit Oct 27, 2026
7d869cd
Ask before the page is closed or reloaded with changes that are not s…
knihit Oct 27, 2026
946b5e1
Make the Properties pane keyboard-safe, honest about checks, and clea…
knihit Oct 28, 2026
2357aff
Take the Designer screenshots again
knihit Oct 28, 2026
5195d82
Keep the read-only note in the layout and say every add and select
knihit Oct 28, 2026
0ee70ad
Select the generated name after Add stage, so typing replaces it
knihit Oct 28, 2026
76c3f91
Show the names in a field error as code, without backticks
knihit Oct 28, 2026
10f1582
Ask before a pasted launch address drops unsaved changes or a followe…
knihit Oct 28, 2026
05582a0
Merge wc/07b-properties into wc/07c-save
knihit Oct 29, 2026
3c7517f
Say which address loses the draft now that the lost-session message s…
knihit Oct 29, 2026
9026556
Add the docs theme: tokens, bundled fonts and a light/dark mode switch
knihit Oct 25, 2026
acb1945
Give the page a top bar, a header band and themed canvas nodes and ports
knihit Oct 25, 2026
ddf8d52
Cover the theme in e2e tests, add a dark screenshot, document the theme
knihit Oct 25, 2026
1a431fa
Put the tab strip on the header band and finish the dark canvas ports
knihit Oct 25, 2026
8b9b0eb
Use a plain comment for the font wait helper in the screenshot support
knihit Oct 25, 2026
a1d0420
Draw the S ports as jigsaw knobs and sockets, and tidy the theme
knihit Oct 25, 2026
62e8484
Match the dark canvas muted text to the theme module
knihit Oct 25, 2026
6eb2a4b
Give the knob a longer pinched neck and draw the focus ring around th…
knihit Oct 26, 2026
04ef4e9
Show the system outline for the focused node in forced-colors mode
knihit Oct 26, 2026
80bfdf8
Draw the S ports as square tabs and notches to match the connector de…
knihit Oct 27, 2026
24c1917
Give stages and the end node room for the notch and a state badge
knihit Oct 27, 2026
371119f
Add the theme changelog line
knihit Oct 27, 2026
bb7635c
Match the inline script tag case-insensitively in the early-mode test
knihit Oct 27, 2026
0ff37b1
Address review findings on the theme: one palette, forced colors, lea…
knihit Oct 28, 2026
86ec6a9
Keep text readable on the selected wash in both modes
knihit Oct 28, 2026
a600aef
Match a script tag with attributes in the early-mode test
knihit Oct 28, 2026
c679283
Make the colour guard see every colour literal in the canvas CSS
knihit Oct 28, 2026
784cbc6
Drop the claim that a reload drops the session from the top bar comment
knihit Oct 28, 2026
6d1950b
Allow the early theme script in the page policy by its hash
knihit Oct 28, 2026
4390c73
Pin every inline script of the page in the policy test
knihit Oct 29, 2026
c84e2d5
Read runs through the typed client and add the run core
knihit Oct 26, 2026
b7695ba
Keep the run_id of a 409 and reject a run reply that breaks the contract
knihit Oct 27, 2026
8c29fe6
Follow only the run that was started, and tidy the run core
knihit Oct 27, 2026
ae0d3f0
Follow a run through a blip, join an active run, and bound every call
knihit Oct 28, 2026
ec8ec55
Give the fake API of the source tests the run calls
knihit Oct 28, 2026
c19a3b0
Say when a start may have started, retry 408 and 429, and tighten the…
knihit Oct 28, 2026
baaa01f
Give the session memory tests the run calls and the memory argument
knihit Oct 28, 2026
4e12374
Drop the session on a 401 from a run call, with the message of a lost…
knihit Oct 28, 2026
56e0aa6
Build the API of the refused-address notice test with the helper that…
knihit Oct 29, 2026
5fd9259
Build the api of the new session tests with the shared helper
knihit Oct 29, 2026
74d7415
Draw the run on the Execution tab with a Run button and live node states
knihit Oct 26, 2026
175e339
Test the Execution tab end to end, capture its screenshots and docume…
knihit Oct 26, 2026
dcdf44e
Keep the goal across tabs, announce the run error and say the run may…
knihit Oct 27, 2026
cbaf691
Refresh the Execution screenshots: read-only goal box and square port…
knihit Oct 27, 2026
61af00f
Take the running captures without the tooltip of the Run button
knihit Oct 27, 2026
ad2f6f8
Announce the run outside the tabs, and put its alert under the Run bu…
knihit Oct 28, 2026
134660c
Warn when the run has other stages than the graph
knihit Oct 28, 2026
32f77a1
Fix the run announcements, Safari Enter, and the drift notice
knihit Oct 28, 2026
1a2607b
Let go of a run when another workflow opens, and stop telling the per…
knihit Oct 28, 2026
cf5d8f5
Tell the person to reload the page to draw a changed workflow file
knihit Oct 28, 2026
f22b331
Follow a run that is already active, retry failed reads and time out …
knihit Oct 26, 2026
86b42bb
Test that a second start in the same tick is ignored
knihit Oct 26, 2026
7be7758
Document read retries, the read-only goal box and the stale-graph limit
knihit Oct 26, 2026
7a196dc
Name the read-retry rule and make the README match it
knihit Oct 26, 2026
12a2a91
Join a run that is still going when the page is opened again after a …
knihit Oct 27, 2026
0a1ab80
Add the Execution tab changelog line
knihit Oct 27, 2026
8723e02
Let Run take over a pending look for an earlier run, and retry that look
knihit Oct 28, 2026
1b2b04e
Tell a lookup that failed from a run that is gone, and a start with n…
knihit Oct 28, 2026
3aa03aa
Label a joined run, keep that it was joined, and say what a reload does
knihit Oct 28, 2026
aed2f02
Forget a run that has ended, keep a joined run on a change of runs, p…
knihit Oct 28, 2026
e67ba31
Forget a joined run when another workflow opens
knihit Oct 28, 2026
bffd855
Resume a run after a reload on a server whose launch code works once
knihit Oct 28, 2026
ebb01f1
Test that a 401 on the run start drops the kept session
knihit Oct 28, 2026
510a1e5
Add the editing core of the Designer: draft, stage text, findings and…
knihit Oct 27, 2026
931d4f2
Mark the earlier stage too when two stages share a name
knihit Oct 27, 2026
c677226
Keep the comments and layout of unchanged stages, and make the editor…
knihit Oct 28, 2026
39427ea
Write new stages in the layout of the file's list, and test every lay…
knihit Oct 28, 2026
73f1634
Give the diagnostic in the backtick test its path and severity
knihit Oct 28, 2026
fa3619d
Edit the stages of a workflow in the Designer: Properties, pick agent…
knihit Oct 27, 2026
ad7eb7b
Ask before the page is closed or reloaded with changes that are not s…
knihit Oct 27, 2026
83cb284
Make the Properties pane keyboard-safe, honest about checks, and clea…
knihit Oct 28, 2026
4333c21
Take the Designer screenshots again
knihit Oct 28, 2026
3308ef3
Keep the read-only note in the layout and say every add and select
knihit Oct 28, 2026
6590c92
Select the generated name after Add stage, so typing replaces it
knihit Oct 28, 2026
2665798
Show the names in a field error as code, without backticks
knihit Oct 28, 2026
ae371e9
Ask before a pasted launch address drops unsaved changes or a followe…
knihit Oct 28, 2026
df320f8
Merge wc/07b-properties into wc/07c-save
knihit Oct 29, 2026
7793d16
Match the start of the error in the pending session tests
knihit Oct 29, 2026
c49b22c
Use the bundled code font for code in messages
knihit Oct 29, 2026
5c702a1
Merge wc/07b-properties into wc/07c-save
knihit Oct 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 98 additions & 29 deletions workflow-canvas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ of the agent package or the CLI.
The page has Designer and Execution tabs. The Designer tab loads the workflow
from the API and draws it: the intent, each stage in list order (name, agent
and harness) and the end, joined by run-order edges. You can pick the agent of
a stage, rename it, add a stage and delete one. It shows a
a stage, rename it, add a stage, delete one and save the file. It shows a
loading message, an error message or an empty message when there is nothing to
draw. The Execution tab draws the saved workflow and runs it: a Run button
starts a run, and each node shows its live state until the run ends. The
Expand Down Expand Up @@ -236,10 +236,17 @@ modules, the graph core and the wire types.
The rest wraps the core, so the rules can be tested without a browser:

- `api/`: `WorkflowApi` is the port with the calls the page makes (open a
session, load the workflow, start a run, read a run); `createHttpApi` is its
session, load, save and check the workflow, start a run, read a run);
`createHttpApi` is its
adapter over `fetch`. It ignores fields it does not know, refuses a reply
that breaks the contract, and turns a failed call into an `ApiError` with a
message fit to show.
`messageOf` (`api/message-of.ts`) is how every alert words a failure, and it
adds what to do next: for no answer, or a 5xx that is not the API's (the
answer of a proxy whose server is down), "If it stopped, start it again and
get a fresh launch address from the server."; for the API's 401 (the session
is gone, as after the server started again), "Get a fresh launch address from
the server."
- `launch/launch-code.ts` and `workflow/` take the launch code from the
address and load the workflow. `loadWorkflow` never rejects: it returns
`{ ok: true, workflow }` or `{ ok: false, message }`. `createWorkflowSource`
Expand Down Expand Up @@ -292,11 +299,12 @@ The rest wraps the core, so the rules can be tested without a browser:

## The Designer tab

The tab has the graph on the left, with **Add stage** above it, and
**Properties** on the right. Properties shows the selected stage (the first
one when the page opens): its `name` and its `agent`, and **Delete stage**. A
stage is only a `name` and an `agent`, and the stages run in the order they are
listed, so there is no other field and no way to link or reorder them yet.
The tab has the graph on the left, with **Add stage** above it and the save
line and **Save** in its header, and **Properties** on the right. Properties
shows the selected stage (the first one when the page opens): its `name` and
its `agent`, and **Delete stage**. A stage is only a `name` and an `agent`, and
the stages run in the order they are listed, so there is no other field and no
way to link or reorder them yet.

- **Add stage** adds a stage at the end, named `agent-1` (the first free
`agent-N`) with the agent `k-developer`, selects it and moves focus to its
Expand All @@ -319,37 +327,94 @@ listed, so there is no other field and no way to link or reorder them yet.
moves focus to its name; a click on it changes nothing. Escape on a node
leaves focus on it. A polite live region says what was added, deleted or
selected.

Nothing is written until **Save**. The line beside it says **No changes**,
**Unsaved changes**, **Saving** or **Saved** in a polite live region. Save
sends the whole text with `PUT /api/workflow` and the `hash` of the file as it
was loaded (`base_hash`), and takes the hash of the answer for the next save.
Only the block of stages changes: the `name`, `intent`, `version` and the
comments outside the block stay as they were. A stage that is unchanged keeps
its own lines and the comments in front of it; the comments written inside a
stage that is changed or deleted go with it. If the stages are not a block at the top level of the file (for
example `stages: [...]`), the whole file is written from the view, in the plain
layout, which drops its comments and `version`; an info note above the graph
says so before you save.

- **Checking.** A moment (400 ms) after the last change, the text is checked
with `POST /api/validate`. What it finds shows under the field it is about,
and all of it in an alert above the graph, with the name of the stage in
front. What the last check found stays on show while you type, until the
check of the new text answers, so the page does not jump at every key. The
server has the last word: Save is off while the check of the current draft
has errors, with the reason in a tooltip, and a save that goes out in the
pause is checked again by the server. A check that cannot reach the server
says nothing; the save checks again and reports it. Only an error stops a
save: a warning or a note is shown as a note (a warning or info alert, and
the text under the field in the warning style) and Save stays on.
- **A refused save** (422) shows the findings the same way. A refusal with no
error among its findings shows the sentence of the server as a failure, so
it is never silent.
- **A conflict** (409) means the file changed after the page loaded. Nothing
is written, and an alert says so. If the file already holds the text that was
sent (the 409 names its hash, and the page reads the file), the earlier write
landed and the save counts as done. Otherwise the alert offers **Copy my
version**, which puts the draft text on the clipboard (or shows it in a box,
if the page may not), and **Load the file again**, which loads the file from
the server and replaces the draft. The stages cannot be changed while the file
loads. After the load, a note keeps **Copy my version** for the text that was
replaced. It stays while the person goes on to edit, until the file is loaded
again or a save works, so the draft is never lost without a way back to it.
- **A read-only workflow** (the server says `read_only`) can be looked at, and
a stage can be selected, but the name is read-only and Add stage, Delete
stage and the agent list are off. A note above the graph says why, and the
buttons carry the reason in a tooltip.
stage, the agent list and Save are off. A note above the graph says why, and
the buttons carry the reason in a tooltip.
- **A save whose answer is lost** (no answer, a server error, or a 200 that the
page cannot read) may still have written the file. The page reads the file
again: if it holds the text that was sent, the save counts as done. If it
does not, the failure is shown in an alert, and Save can be tried again. If
the file cannot be read either, the alert says "The save did not finish" and
that it is not known whether the file was written.
- **Any other failure** of a save shows its sentence in an alert, and Save can
be tried again. When the session is gone (the API's 401, or a 401 from a
proxy), the sentence adds that the changes on the page are lost when the
launch address is opened again. A check that meets a lost session says so at
once, under "The session is lost", as no save can work then.
- **Focus and keys.** Everything above works with the keyboard: Tab to **Add
stage** and press Enter; the new stage's name has focus. A button that is off
(Add stage, Delete stage) stays focusable and gives the reason in a tooltip,
so focus stays on the button that was pressed.
(Save, Add stage, Delete stage) stays focusable and gives the reason in a
tooltip, so focus stays on the button that was pressed, for instance Save
while it saves.
- **The two tabs.** The draft lives in `App`, so it is kept when you switch
tabs. The Execution tab draws the saved workflow, because a run uses the
saved file, and says so when the Designer holds changes that are not saved.

The page does not write the file in this version: the draft is only kept in the
page. So while the draft differs from the file, closing or reloading the page
While the draft differs from the saved file, closing or reloading the page
makes the browser ask whether to leave (`useLeaveGuard`, a `beforeunload`
handler that is on only then). A change that is put back, so the draft equals
the file again, does not ask.
handler that is on only then), because the draft lives in the page. A change
that is put back, so the draft equals the file again, does not ask, and nor
does a draft that has been saved.

The code follows the split of the Execution tab:

- `src/designer/core/` is the core and does no IO: the draft of the stages
(`draft.ts`), the text a save would write (`stage-text.ts`), the findings put
on the stage and field they are about (`problems.ts`), and the state of the
editor with its reducer. `test-support/designer-purity.test.ts` fails if the
core imports anything but its own modules and the wire types, or reads a
- `src/designer/core/` is the core and does no IO: the draft of the stages, the
text that a save writes (`stage-text.ts`), the findings put on the stage and
field they are about (`problems.ts`), the state of the editor and its
reducer, and the two loops that check (after a pause) and save. Its ports
are `EditPort` (save, check and reload) and the `Scheduler` of the run core,
so a test moves time by hand and nothing sleeps.
`test-support/designer-purity.test.ts` fails if the core imports anything
but its own modules, the wire types, `messageOf` and the scheduler port, or reads a
clock, randomness, the environment or the network.
- `useEditor` keeps the state in `App`; `DesignerEditor` and `PropertiesPane`
draw it. `useLeaveGuard` asks the browser to confirm leaving while the draft
has changes. A stage keeps an id of its own while it is renamed, and the node is
named from that id, so two stages with one name for a moment still have two
nodes.
- `createHttpApi` has `saveWorkflow` and `checkWorkflow`; `loadWorkflow` ties
them to the session token as the `EditPort`, and turns a 409 `conflict` and
a 422 `invalid_workflow` (with its `diagnostics`) into the core's own errors.
- `useEditor` keeps the state in `App`; `DesignerEditor`, `PropertiesPane`,
`SaveControls` and `EditAlerts` draw it. `useLeaveGuard` asks the browser to
confirm leaving while the draft has changes. A stage keeps an id of its own
while it is renamed, and the node is named from that id, so two stages with
one name for a moment still have two nodes.
- The API contract is unchanged. `test-support/editing-contract.test.ts`
checks the limits and calls that the Designer assumes against it.

## The Execution tab

Expand Down Expand Up @@ -413,8 +478,9 @@ intent, because the API needs one (1 to 2048 characters) to start a run.
alert. The goal is one line: line breaks in the outcome of the intent become
spaces, and Enter that ends an input method composition starts no run.
- A workflow can be saved while a run goes on, so the run may have other stages
than the graph. The graph is read once, when the page loads, while the run
works from the saved file. Node states attach by stage name, so a warning
than the graph. The graph is the saved workflow, and it is drawn again when
the Designer saves, while a run works from the file as it was saved when the
run started. Node states attach by stage name, so a warning
above the graph names the stages that are not drawn and those that are not in
the run.
- A run goes on while the Designer tab is open, and the tab shows it again when
Expand Down Expand Up @@ -547,8 +613,11 @@ the theme button is pressed). The Execution tab is saved before a run
(`execution-tab-running.png`) and after the run passed
(`execution-tab-finished.png`), all in the light theme, and while the first
stage runs in the dark theme (`execution-tab-dark.png`). The Designer tab is
also saved with edits in the draft (`designer-editing.png`, and
`designer-editing-dark.png` in the dark theme). Pull request
also saved with unsaved edits (`designer-editing.png`, and
`designer-editing-dark.png` in the dark theme), after a save
(`designer-saved.png`), with a problem the server found
(`designer-problems.png`) and when the file changed on disk
(`designer-conflict.png`). Pull request
descriptions embed these files, so regenerate and commit them when the UI
changes.

Expand Down
40 changes: 37 additions & 3 deletions workflow-canvas/e2e/capture/screenshots.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
import { expect, test } from '../support/editing-fixtures';
import { answerRunsWith, runReply } from '../support/run-routes';
import { overwriteWorkflow, SAMPLE_TEXT } from '../support/stub-workflow';
import { saveFullPageScreenshot } from '../support/screenshots';
import type { WorkflowCanvasPage } from '../support/workflow-canvas-page';

Expand Down Expand Up @@ -88,19 +89,52 @@ test('capture the Execution tab in the dark mode during a run', async ({
await saveFullPageScreenshot(canvas.page, 'execution-tab-dark');
});

// Editing. The stub's workflow is not touched: these only change the draft.
// Editing. Each test puts the stub's workflow back afterwards (the fixture
// does), so the captures above and below it see the sample.
// These two change what a stage says but add none: a longer graph is fitted at
// a fractional zoom, and the edges of such a view differ by a level of grey
// between machines, which the byte-exact check cannot take.
test('capture the Designer tab with unsaved edits', async ({ designer }) => {
await designer.chooseAgent('k-architect');
await designer.stage('review').click();
await designer.rename('ship');
await designer.stage('ship').waitFor();
await designer.expectStatus('Unsaved changes');

await saveFullPageScreenshot(designer.page, 'designer-editing');
});

test('capture the Designer tab after a save', async ({ designer }) => {
await designer.rename('write');
await designer.save.click();
await designer.expectStatus('Saved');
await saveFullPageScreenshot(designer.page, 'designer-saved');
});

test('capture the Designer tab with a problem the server found', async ({
designer,
}) => {
await designer.stage('review').click();
await designer.rename('draft');
await expect(designer.problems).toBeVisible();

await saveFullPageScreenshot(designer.page, 'designer-problems');
});

test('capture the Designer tab when the file changed on disk', async ({
designer,
request,
}) => {
await designer.rename('write');
await overwriteWorkflow(
request,
SAMPLE_TEXT.replace('k-quality-assurance', 'k-tpm'),
);
await designer.save.click();
await expect(designer.conflict).toBeVisible();

await saveFullPageScreenshot(designer.page, 'designer-conflict');
});

test('capture the Designer tab with unsaved edits in the dark mode', async ({
canvas,
designer,
Expand All @@ -109,7 +143,7 @@ test('capture the Designer tab with unsaved edits in the dark mode', async ({
await designer.chooseAgent('k-architect');
await designer.stage('review').click();
await designer.rename('ship');
await designer.stage('ship').waitFor();
await designer.expectStatus('Unsaved changes');

await saveFullPageScreenshot(designer.page, 'designer-editing-dark');
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified workflow-canvas/e2e/screenshots/designer-editing-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified workflow-canvas/e2e/screenshots/designer-editing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified workflow-canvas/e2e/screenshots/designer-tab-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified workflow-canvas/e2e/screenshots/designer-tab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified workflow-canvas/e2e/screenshots/execution-tab-finished.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 27 additions & 1 deletion workflow-canvas/e2e/support/designer-page.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
// SPDX-License-Identifier: Apache-2.0
import type { Locator, Page } from '@playwright/test';
import { expect, type Locator, type Page } from '@playwright/test';

/** What the save line says. */
export type SaveStatus =
| 'No changes'
| 'Unsaved changes'
| 'Saving'
| 'Saved'
| 'Loaded again from the server';

/** Page object for the editing parts of the Designer tab. */
export class DesignerPage {
readonly name: Locator;
readonly agent: Locator;
readonly add: Locator;
readonly remove: Locator;
readonly save: Locator;
readonly saveStatus: Locator;
readonly conflict: Locator;
readonly problems: Locator;
readonly selected: Locator;

constructor(readonly page: Page) {
this.name = page.getByRole('textbox', { name: 'name', exact: true });
this.agent = page.getByRole('button', { name: /^agent / });
this.add = page.getByRole('button', { name: 'Add stage' });
this.remove = page.getByRole('button', { name: 'Delete stage' });
this.save = page.getByRole('button', { name: 'Save', exact: true });
this.saveStatus = page.getByTestId('save-status');
this.conflict = page.getByTestId('conflict');
this.problems = page.getByTestId('problems');
this.selected = page.getByText('Selected:');
}

Expand All @@ -34,4 +50,14 @@ export class DesignerPage {
.getByRole('option', { name: new RegExp(`^${id}\\b`) })
.click();
}

async expectStatus(status: SaveStatus): Promise<void> {
await expect(this.saveStatus).toHaveText(status);
}

/** Asserts the save button can be pressed, or cannot. */
async expectSaveEnabled(enabled: boolean): Promise<void> {
if (enabled) await expect(this.save).toBeEnabled();
else await expect(this.save).toBeDisabled();
}
}
17 changes: 15 additions & 2 deletions workflow-canvas/e2e/support/editing-fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
// SPDX-License-Identifier: Apache-2.0
import { DesignerPage } from './designer-page';
import { test as base } from './fixtures';
import { restoreSample } from './stub-workflow';

export { expect } from './fixtures';

/** `test` for specs that edit on the Designer tab. */
export const test = base.extend<{ designer: DesignerPage }>({
/**
* `test` for specs that save to the stub. The stub keeps what is saved until
* it stops, so an automatic fixture puts the sample back before each test
* opens the page, and after it.
*/
export const test = base.extend<{ sample: void; designer: DesignerPage }>({
sample: [
async ({ request }, use) => {
await restoreSample(request);
await use();
await restoreSample(request);
},
{ auto: true },
],
designer: async ({ canvas }, use) => {
await canvas.expectWorkflowDrawn();
await use(new DesignerPage(canvas.page));
Expand Down
6 changes: 5 additions & 1 deletion workflow-canvas/e2e/support/screenshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ async function waitForFonts(page: Page): Promise<void> {

/**
* Saves a full-page PNG as `e2e/screenshots/<name>.png`, rendered with the
* bundled fonts, with animations and the caret frozen so repeated runs match.
* bundled fonts, with animations and the caret frozen and the pointer moved off
* the page, so repeated runs match.
*/
export async function saveFullPageScreenshot(
page: Page,
name: string,
): Promise<void> {
await waitForFonts(page);
// A button that was just clicked and is now off shows its reason in a tooltip
// while the pointer is over it, which comes up a moment later or not.
await page.mouse.move(0, 0);
await page.screenshot({
path: path.join(SCREENSHOT_DIR, `${name}.png`),
fullPage: true,
Expand Down
Loading
Loading