From 1b03ae3f9522309401199aa18de6002238bd1072 Mon Sep 17 00:00:00 2001 From: falbany Date: Tue, 3 Nov 2026 00:38:09 +0100 Subject: [PATCH] fix(web): WorkflowRunnerModal recovery, copy buttons, sidebar a11y (#78, #79, #80, #82) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #79 session-expired recovery: api/client.ts workflow routes now throw ApiError with the HTTP status; a 404 respond/start flips the modal to a recovery panel (pinned above the footer per #75) with a Restart workflow button (re-issues start with identical props via a nonce), a Close button, and a collapsible raw-error block for bug reports. Sidebar shows an explicit "Session unavailable" state instead of an eternal "Loading steps..."; dead gate buttons and the bypass button are hidden. #78 copy buttons: ghost CopyButton (clipboard API, silent catch for webviews without one) on the interactive command box, the generic command block, and both output
 blocks; label flips to "Copied"
for 1.5s.

#80 sidebar a11y: steps render as a semantic 
             ) : (
-              
Loading steps...
+
+ {error || sessionGone + ? "Session unavailable — see the error panel." + : "Loading steps..."} +
)} @@ -406,12 +545,6 @@ export function WorkflowRunnerModal({
- {error && ( -
- - {error} -
- )} {/* Active / Inspected Step Details */}
@@ -515,14 +648,18 @@ export function WorkflowRunnerModal({

Command Output:

-
-                        {session.last_output}
-                      
+
+ +
+                          {session.last_output}
+                        
+
)} {/* Priority 3: General content display for inspected or non-prompting current step */} - {!(isFinished && selectedStepIndex === null) && + {!sessionGone && + !(isFinished && selectedStepIndex === null) && !((selectedStepIndex === null || selectedStepIndex === session?.current_index) && (session?.status === "waiting_notify" || session?.status === "waiting_ask" || session?.status === "waiting_notice")) && (
{activeStep?.message && ( @@ -536,17 +673,27 @@ export function WorkflowRunnerModal({

Command:

-
- {activeStep?.command ? activeStep.command : "Waiting..."} +
+ {activeStep?.command ? ( + <> + + {activeStep.command} + + ) : ( + "Waiting..." + )}
{activeStep?.output && (

Output:

-
-                            {activeStep.output}
-                          
+
+ +
+                              {activeStep.output}
+                            
+
)} @@ -568,10 +715,61 @@ export function WorkflowRunnerModal({
+ {/* Error / recovery strip: pinned above the footer so errors are + visible without scrolling (#75 layout, #79 acceptance). */} + {(error || sessionGone) && ( +
+
+ +
+

+ {sessionGone + ? "Workflow session on the server has expired or ended." + : error} +

+ {sessionGone ? ( +
+ {error && ( +
+ + Raw error (for bug reports) + +
+                              {error}
+                            
+
+ )} +
+ + +
+
+ ) : null} +
+
+
+ )} + {/* Bottom Action Area (Ask Callbacks / Dismiss) */}
-
- {session?.status === "waiting_ask" && (selectedStepIndex === null || selectedStepIndex === session.current_index) + {/* Live region (#80): status / prompt transitions are announced + without moving focus. */} +
+ {sessionGone + ? "Session unavailable — restart or close above." + : session?.status === "waiting_ask" && (selectedStepIndex === null || selectedStepIndex === session.current_index) ? "Action required to proceed:" : session?.status === "waiting_notify" && (selectedStepIndex === null || selectedStepIndex === session.current_index) ? "Acknowledge message to proceed:" @@ -583,7 +781,7 @@ export function WorkflowRunnerModal({
- {session?.status === "waiting_notice" && (selectedStepIndex === null || selectedStepIndex === session.current_index) && ( + {!sessionGone && session?.status === "waiting_notice" && (selectedStepIndex === null || selectedStepIndex === session.current_index) && (