+
+
+
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-account.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-account.png
new file mode 100644
index 0000000000..d3a69b5d6a
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-account.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-advanced.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-advanced.png
new file mode 100644
index 0000000000..3792a1bec3
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-advanced.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-llm.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-llm.png
new file mode 100644
index 0000000000..0ee6a345cb
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-llm.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-mobile.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-mobile.png
new file mode 100644
index 0000000000..9875a0e67d
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-mobile.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-save.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-save.png
new file mode 100644
index 0000000000..ff1319481b
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-save.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-tablet.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-tablet.png
new file mode 100644
index 0000000000..333df8d7db
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-settings-tablet.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-workflows.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-workflows.png
new file mode 100644
index 0000000000..f022c06c41
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype-workflows.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype.html b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype.html
new file mode 100644
index 0000000000..bfa02aeb67
--- /dev/null
+++ b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype.html
@@ -0,0 +1,2844 @@
+
+
+
+
+
+ Aevatar Workflow + Activity + Settings Prototype
+
+
+
+
+
+
+
+
+
+
Workflows
Create, edit, publish, and run automations.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Customer escalation triage
Current draft · revision 12 · Saved just now
+ Draft
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Activity
Every Run, newest first. Search, inspect, and recover from one place.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Settings
Personal defaults and access.
+
+
+
+
+
+
+
+
+
AI defaults
Choose the service and model used by new Chat, Studio, and global tool sessions without an override.
+
+
+
+
+
+
Preferred serviceGateway or an exact connected service.
+
+
+
+
Default modelLeave unset to use the service default.
+
+
+
+
+
+
+
Unsaved changesYour AI defaults have not been saved.
+
+
+
+
+
+
Account
Identity and service access for this browser session.
+
+
+
+
Profile
+
+
+
+
Authentication
+
+
Session expires2026/8/4 18:30
+
ProviderNyxID
+
Scopeopenid profile email
+
Browser token refreshDisabled
+
+
+
+
+
+
+
Advanced
Read-only runtime and routing details for troubleshooting.
+
+
+
Request values
Effective values supplied to new requests.
+
+
x-aevatar-llm-routenyxid_gateway
+
x-aevatar-llm-modelgpt-4o-mini
+
studio.runtime_base_urlhttp://127.0.0.1:5080
+
aevatar.runtime_modelocal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
New workflow
Each starting point creates a workflow draft directly.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Describe your workflow
A draft will open immediately for review.
+
+
+
+
+
+
+
+
Import workflow YAML
Validate the workflow document, then review its nodes on the canvas.
+
+
+
+
+
+
+
+
+
+
+
Choose a workflow template
Templates create an editable draft with connected nodes and configuration.
+
+
+
+
+
+
+
+
+
New schedule
Configure recurring work without leaving Workflows.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Run workflow
Confirm the revision, inputs, and external effects.
+
+
WorkflowCurrent draft · revision 12
+
+
+
+
+
+
+
External effects
This workflow may post a Lark message and create a Linear issue.
+
+
This Run will be saved in Activity.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/render-schedule-png.py b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/render-schedule-png.py
new file mode 100644
index 0000000000..0cdcc93404
--- /dev/null
+++ b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/render-schedule-png.py
@@ -0,0 +1,230 @@
+#!/usr/bin/env python3
+"""Render review PNGs from the deterministic Schedule Excalidraw source."""
+
+from __future__ import annotations
+
+import argparse
+import hashlib
+import json
+import math
+from pathlib import Path
+from typing import Any
+
+from PIL import Image, ImageDraw, ImageFont, PngImagePlugin
+
+
+BASELINE_DIR = Path(__file__).resolve().parent
+SOURCE = BASELINE_DIR / "aevatar-workflow-schedule-design.excalidraw"
+FRAME_OUTPUTS = (
+ ("01 · Workflows — schedule management modal", "schedule-workflows-list-modal.png"),
+ ("02 · Workflow — schedule setup panel", "schedule-workflow-editor-panel.png"),
+ ("03 · Schedule — review before creation", "schedule-review.png"),
+ ("04 · Schedule — creation pending", "schedule-creation-pending.png"),
+ ("05 · Workflow — schedule overview", "schedule-detail.png"),
+ ("06 · Workflow — schedule history", "schedule-history.png"),
+ ("07 · Workflow — change schedule", "schedule-edit.png"),
+)
+
+SANS_FONT = Path("/System/Library/Fonts/Supplemental/Arial Unicode.ttf")
+MONO_FONT = Path("/System/Library/Fonts/Menlo.ttc")
+FALLBACK_FONT = Path("/System/Library/Fonts/Supplemental/Arial.ttf")
+
+
+def color(value: str | None) -> str | None:
+ if not value or value == "transparent":
+ return None
+ return value
+
+
+def load_font(font_family: int, size: float) -> ImageFont.FreeTypeFont:
+ preferred = MONO_FONT if font_family == 3 else SANS_FONT
+ font_path = preferred if preferred.exists() else FALLBACK_FONT
+ return ImageFont.truetype(str(font_path), max(7, round(size)))
+
+
+def dashed_line(
+ draw: ImageDraw.ImageDraw,
+ start: tuple[float, float],
+ end: tuple[float, float],
+ fill: str,
+ width: int,
+ dash: float,
+) -> None:
+ dx = end[0] - start[0]
+ dy = end[1] - start[1]
+ length = math.hypot(dx, dy)
+ if not length:
+ return
+ ux, uy = dx / length, dy / length
+ cursor = 0.0
+ while cursor < length:
+ segment_end = min(cursor + dash, length)
+ draw.line(
+ (
+ start[0] + ux * cursor,
+ start[1] + uy * cursor,
+ start[0] + ux * segment_end,
+ start[1] + uy * segment_end,
+ ),
+ fill=fill,
+ width=width,
+ )
+ cursor += dash * 1.8
+
+
+def draw_arrowhead(
+ draw: ImageDraw.ImageDraw,
+ start: tuple[float, float],
+ end: tuple[float, float],
+ fill: str,
+ width: int,
+) -> None:
+ angle = math.atan2(end[1] - start[1], end[0] - start[0])
+ size = max(7, width * 4)
+ wing = math.pi / 7
+ left = (end[0] - size * math.cos(angle - wing), end[1] - size * math.sin(angle - wing))
+ right = (end[0] - size * math.cos(angle + wing), end[1] - size * math.sin(angle + wing))
+ draw.line((left, end, right), fill=fill, width=width, joint="curve")
+
+
+def render_frame(
+ document: dict[str, Any],
+ frame: dict[str, Any],
+ size: tuple[int, int],
+ *,
+ padding: int = 0,
+) -> Image.Image:
+ image = Image.new("RGB", size, "#f1f3f5")
+ draw = ImageDraw.Draw(image)
+ usable_width = size[0] - padding * 2
+ usable_height = size[1] - padding * 2
+ scale = min(usable_width / frame["width"], usable_height / frame["height"])
+ origin_x = padding + (usable_width - frame["width"] * scale) / 2
+ origin_y = padding + (usable_height - frame["height"] * scale) / 2
+
+ def point(x: float, y: float) -> tuple[float, float]:
+ return (
+ origin_x + (x - frame["x"]) * scale,
+ origin_y + (y - frame["y"]) * scale,
+ )
+
+ elements = [
+ element
+ for element in document["elements"]
+ if not element.get("isDeleted")
+ and element.get("type") != "frame"
+ and element.get("frameId") == frame["id"]
+ ]
+ for element in elements:
+ kind = element["type"]
+ stroke = color(element.get("strokeColor")) or "#1e1e1e"
+ fill = color(element.get("backgroundColor"))
+ stroke_width = max(1, round(element.get("strokeWidth", 1) * scale))
+ x1, y1 = point(element["x"], element["y"])
+ x2 = x1 + element.get("width", 0) * scale
+ y2 = y1 + element.get("height", 0) * scale
+
+ if kind == "rectangle":
+ radius = max(2, round(8 * scale)) if element.get("roundness") else 0
+ draw.rounded_rectangle(
+ (x1, y1, x2, y2),
+ radius=radius,
+ fill=fill,
+ outline=stroke,
+ width=stroke_width,
+ )
+ continue
+
+ if kind == "ellipse":
+ draw.ellipse((x1, y1, x2, y2), fill=fill, outline=stroke, width=stroke_width)
+ continue
+
+ if kind in {"line", "arrow"}:
+ raw_points = element.get("points") or [[0, 0], [element.get("width", 0), element.get("height", 0)]]
+ points = [(x1 + px * scale, y1 + py * scale) for px, py in raw_points]
+ for start, end in zip(points, points[1:]):
+ if element.get("strokeStyle") == "dashed":
+ dashed_line(draw, start, end, stroke, stroke_width, max(5, 7 * scale))
+ else:
+ draw.line((start, end), fill=stroke, width=stroke_width, joint="curve")
+ if kind == "arrow" and element.get("endArrowhead") and len(points) > 1:
+ draw_arrowhead(draw, points[-2], points[-1], stroke, stroke_width)
+ continue
+
+ if kind == "text":
+ font_size = element.get("fontSize", 16) * scale
+ font = load_font(element.get("fontFamily", 1), font_size)
+ text_value = element.get("text", "")
+ align = element.get("textAlign", "left")
+ spacing = max(0, round(font_size * (element.get("lineHeight", 1.25) - 1)))
+ if align == "center":
+ draw.multiline_text(
+ (x1 + element.get("width", 0) * scale / 2, y1),
+ text_value,
+ font=font,
+ fill=stroke,
+ anchor="ma",
+ align="center",
+ spacing=spacing,
+ )
+ elif align == "right":
+ draw.multiline_text(
+ (x2, y1),
+ text_value,
+ font=font,
+ fill=stroke,
+ anchor="ra",
+ align="right",
+ spacing=spacing,
+ )
+ else:
+ draw.multiline_text(
+ (x1, y1),
+ text_value,
+ font=font,
+ fill=stroke,
+ spacing=spacing,
+ )
+
+ return image
+
+
+def output_metadata(source_bytes: bytes) -> PngImagePlugin.PngInfo:
+ metadata = PngImagePlugin.PngInfo()
+ metadata.add_text("Source-SHA256", hashlib.sha256(source_bytes).hexdigest())
+ metadata.add_text(
+ "Renderer-SHA256",
+ hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
+ )
+ return metadata
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--output-dir", type=Path, default=BASELINE_DIR)
+ args = parser.parse_args()
+ args.output_dir.mkdir(parents=True, exist_ok=True)
+
+ source_bytes = SOURCE.read_bytes()
+ document = json.loads(source_bytes)
+ frames = [element for element in document["elements"] if element.get("type") == "frame"]
+ frames_by_name = {frame["name"]: frame for frame in frames}
+ expected_names = tuple(frame_name for frame_name, _ in FRAME_OUTPUTS)
+ if tuple(frames_by_name) != expected_names:
+ raise SystemExit(
+ f"Schedule frame inventory mismatch: expected {expected_names!r}, "
+ f"found {tuple(frames_by_name)!r}"
+ )
+
+ for frame_name, output_name in FRAME_OUTPUTS:
+ output_path = args.output_dir / output_name
+ render_frame(document, frames_by_name[frame_name], (1440, 900)).save(
+ output_path,
+ optimize=True,
+ pnginfo=output_metadata(source_bytes),
+ )
+ print(f"rendered {output_path.name}: 1440x900")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-creation-pending.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-creation-pending.png
new file mode 100644
index 0000000000..4927dde13d
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-creation-pending.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-detail.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-detail.png
new file mode 100644
index 0000000000..e155e0a6f2
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-detail.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-edit.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-edit.png
new file mode 100644
index 0000000000..80a5268d1c
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-edit.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-history.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-history.png
new file mode 100644
index 0000000000..45ddc06c1f
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-history.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-review.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-review.png
new file mode 100644
index 0000000000..f22a0a3d48
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-review.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-workflow-editor-panel.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-workflow-editor-panel.png
new file mode 100644
index 0000000000..53135e524e
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-workflow-editor-panel.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-workflows-list-modal.png b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-workflows-list-modal.png
new file mode 100644
index 0000000000..994a0ae2e1
Binary files /dev/null and b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-workflows-list-modal.png differ
diff --git a/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py
new file mode 100644
index 0000000000..ef95af0cd8
--- /dev/null
+++ b/apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py
@@ -0,0 +1,698 @@
+#!/usr/bin/env python3
+"""Verify the immutable Workflow Activity vNext design baseline."""
+
+from __future__ import annotations
+
+import hashlib
+import importlib.util
+import json
+import shutil
+import subprocess
+import sys
+import tempfile
+import zlib
+from pathlib import Path
+
+
+BASELINE_DIR = Path(__file__).resolve().parent
+BOARD_NAME = "aevatar-workflow-activity-vnext.excalidraw"
+SCHEDULE_BOARD_NAME = "aevatar-workflow-schedule-design.excalidraw"
+GENERATOR_NAME = "aevatar-workflow-activity-vnext.gen.py"
+SCHEDULE_GENERATOR_NAME = "aevatar-workflow-schedule-design.gen.py"
+SCHEDULE_RENDERER_NAME = "render-schedule-png.py"
+PROTOTYPE_NAME = "prototype.html"
+SCHEDULE_PROTOTYPE_NAME = "prototype-schedule.html"
+SCHEDULE_PNG_SHA256 = {
+ "schedule-workflows-list-modal.png": "e151c9feedec7ead21498bd5af62a1f914fdbf949de8a4ca7269a84622a1d72b",
+ "schedule-workflow-editor-panel.png": "9b948a4ca69a1549f93319cf992024fa4bba9869ca1c8d333cda1b0196aa2e56",
+ "schedule-review.png": "1e4ebcf6b675efa625b5b9a2968cca289734cd1d6a48539b9e4a3a76232b1020",
+ "schedule-creation-pending.png": "3964b97d0c5bebb625cfc767ad287c8ab8f303a6b2769dc017df8345214425c7",
+ "schedule-detail.png": "b2253dff9288838557f0378af7f71aeacd3afbb57c9771a5cc7bb0f49b0e7c0b",
+ "schedule-history.png": "536a4775ea946251bb66185ac470a3316a737b9dd44d3998e3f092da385ebb76",
+ "schedule-edit.png": "1fed99e375085e78af23ef6999d6232304a98aac47bd0f338939af32027987dd",
+}
+OBSOLETE_SCHEDULE_PNGS = (
+ "prototype-schedule.png",
+ "aevatar-workflow-schedule-design.png",
+ "schedule-authorization-review.png",
+)
+EXPECTED_SHA256 = "30e74d7b410ae72c4c91432355436679033679c54c10b1702908435b001577de"
+EXPECTED_SCHEDULE_SHA256 = "cd5c84c45ffb0cdb253af31b7e1b7616504b35520298f01773026e3b76882d8a"
+EXPECTED_FRAMES = (
+ "01 Workflows - catalogue",
+ "02 New workflow - direct creation",
+ "03 Describe - generated Workflow draft",
+ "04 Start blank - empty Workflow draft",
+ "05 Import YAML - imported Workflow draft",
+ "06 Template - populated Workflow draft",
+ "07 Run - unified execution dialog",
+ "08 Running draft - Studio canvas and Run console",
+ "09 Activity - filtered by Workflow",
+ "10 Activity - all retained Runs",
+ "11 Run detail - immutable record",
+ "12 Failed Run - recovery creates a new record",
+ "13 Workflows and Activity - states",
+ "14 Settings - AI defaults",
+ "15 Settings - save and recovery states",
+ "16 Settings - Account",
+ "17 Settings - Advanced and responsive",
+)
+EXPECTED_SCHEDULE_FRAMES = (
+ "01 · Workflows — schedule management modal",
+ "02 · Workflow — schedule setup panel",
+ "03 · Schedule — review before creation",
+ "04 · Schedule — creation pending",
+ "05 · Workflow — schedule overview",
+ "06 · Workflow — schedule history",
+ "07 · Workflow — change schedule",
+)
+
+
+def png_chunks(data: bytes) -> list[tuple[bytes, bytes]]:
+ chunks: list[tuple[bytes, bytes]] = []
+ offset = 8
+ while offset + 12 <= len(data):
+ length = int.from_bytes(data[offset:offset + 4], "big")
+ chunk_type = data[offset + 4:offset + 8]
+ chunk_data = data[offset + 8:offset + 8 + length]
+ chunks.append((chunk_type, chunk_data))
+ offset += length + 12
+ if chunk_type == b"IEND":
+ break
+ return chunks
+
+
+def paeth(left: int, above: int, upper_left: int) -> int:
+ prediction = left + above - upper_left
+ left_distance = abs(prediction - left)
+ above_distance = abs(prediction - above)
+ upper_left_distance = abs(prediction - upper_left)
+ if left_distance <= above_distance and left_distance <= upper_left_distance:
+ return left
+ if above_distance <= upper_left_distance:
+ return above
+ return upper_left
+
+
+def sampled_png_colors(chunks: list[tuple[bytes, bytes]]) -> set[tuple[int, int, int]]:
+ ihdr = next(chunk for chunk_type, chunk in chunks if chunk_type == b"IHDR")
+ width = int.from_bytes(ihdr[0:4], "big")
+ height = int.from_bytes(ihdr[4:8], "big")
+ if ihdr[8:13] != bytes((8, 2, 0, 0, 0)):
+ raise SystemExit("Schedule PNG must be non-interlaced 8-bit RGB")
+ encoded = b"".join(chunk for chunk_type, chunk in chunks if chunk_type == b"IDAT")
+ decoded = zlib.decompress(encoded)
+ bytes_per_pixel = 3
+ stride = width * bytes_per_pixel
+ previous = bytearray(stride)
+ offset = 0
+ colors: set[tuple[int, int, int]] = set()
+ sample_x = max(1, width // 240)
+ sample_y = max(1, height // 160)
+
+ for row_index in range(height):
+ filter_type = decoded[offset]
+ offset += 1
+ row = bytearray(decoded[offset:offset + stride])
+ offset += stride
+ for byte_index in range(stride):
+ left = row[byte_index - bytes_per_pixel] if byte_index >= bytes_per_pixel else 0
+ above = previous[byte_index]
+ upper_left = previous[byte_index - bytes_per_pixel] if byte_index >= bytes_per_pixel else 0
+ if filter_type == 1:
+ row[byte_index] = (row[byte_index] + left) & 0xFF
+ elif filter_type == 2:
+ row[byte_index] = (row[byte_index] + above) & 0xFF
+ elif filter_type == 3:
+ row[byte_index] = (row[byte_index] + ((left + above) // 2)) & 0xFF
+ elif filter_type == 4:
+ row[byte_index] = (row[byte_index] + paeth(left, above, upper_left)) & 0xFF
+ elif filter_type != 0:
+ raise SystemExit(f"unsupported PNG filter: {filter_type}")
+ if row_index % sample_y == 0:
+ for column in range(0, width, sample_x):
+ start = column * bytes_per_pixel
+ colors.add(tuple(row[start:start + bytes_per_pixel]))
+ previous = row
+
+ return colors
+
+
+def verify_png(
+ path: Path,
+ expected_size: tuple[int, int],
+ expected_sha256: str,
+ expected_source_sha256: str,
+ expected_renderer_sha256: str,
+) -> None:
+ data = path.read_bytes()
+ if not data.startswith(b"\x89PNG\r\n\x1a\n"):
+ raise SystemExit(f"not a PNG file: {path.name}")
+ actual_size = (
+ int.from_bytes(data[16:20], "big"),
+ int.from_bytes(data[20:24], "big"),
+ )
+ if actual_size != expected_size:
+ raise SystemExit(
+ f"{path.name} size mismatch: expected {expected_size}, got {actual_size}"
+ )
+ actual_sha256 = hashlib.sha256(data).hexdigest()
+ if actual_sha256 != expected_sha256:
+ raise SystemExit(
+ f"{path.name} SHA-256 mismatch: expected {expected_sha256}, got {actual_sha256}"
+ )
+ chunks = png_chunks(data)
+ metadata = {
+ value.split(b"\0", 1)[0].decode("latin-1"): value.split(b"\0", 1)[1].decode("latin-1")
+ for chunk_type, value in chunks
+ if chunk_type == b"tEXt" and b"\0" in value
+ }
+ if metadata.get("Source-SHA256") != expected_source_sha256:
+ raise SystemExit(f"{path.name} is not linked to the current Schedule Excalidraw")
+ if metadata.get("Renderer-SHA256") != expected_renderer_sha256:
+ raise SystemExit(f"{path.name} is not linked to the current PNG renderer")
+ colors = sampled_png_colors(chunks)
+ if len(colors) < 16:
+ raise SystemExit(f"{path.name} does not contain a non-blank rendered design")
+
+
+def main() -> None:
+ board_path = BASELINE_DIR / BOARD_NAME
+ schedule_board_path = BASELINE_DIR / SCHEDULE_BOARD_NAME
+ generator_path = BASELINE_DIR / GENERATOR_NAME
+ schedule_generator_path = BASELINE_DIR / SCHEDULE_GENERATOR_NAME
+ schedule_renderer_path = BASELINE_DIR / SCHEDULE_RENDERER_NAME
+ prototype_path = BASELINE_DIR / PROTOTYPE_NAME
+ schedule_prototype_path = BASELINE_DIR / SCHEDULE_PROTOTYPE_NAME
+ board_bytes = board_path.read_bytes()
+ schedule_board_bytes = schedule_board_path.read_bytes()
+
+ for obsolete_png in OBSOLETE_SCHEDULE_PNGS:
+ if (BASELINE_DIR / obsolete_png).exists():
+ raise SystemExit(f"obsolete combined Schedule PNG must be removed: {obsolete_png}")
+
+ actual_sha256 = hashlib.sha256(board_bytes).hexdigest()
+ if actual_sha256 != EXPECTED_SHA256:
+ raise SystemExit(
+ f"design SHA-256 mismatch: expected {EXPECTED_SHA256}, got {actual_sha256}"
+ )
+
+ actual_schedule_sha256 = hashlib.sha256(schedule_board_bytes).hexdigest()
+ if actual_schedule_sha256 != EXPECTED_SCHEDULE_SHA256:
+ raise SystemExit(
+ "schedule design SHA-256 mismatch: "
+ f"expected {EXPECTED_SCHEDULE_SHA256}, got {actual_schedule_sha256}"
+ )
+ actual_renderer_sha256 = hashlib.sha256(schedule_renderer_path.read_bytes()).hexdigest()
+
+ document = json.loads(board_bytes)
+ schedule_document = json.loads(schedule_board_bytes)
+ schedule_visible_text = "\n".join(
+ element["text"]
+ for element in schedule_document["elements"]
+ if element.get("type") == "text"
+ )
+ schedule_visible_text_casefolded = schedule_visible_text.casefold()
+ if "Schedule" not in schedule_visible_text:
+ raise SystemExit("schedule entry point is missing from the schedule design board")
+ for required in ("Workflow Schedule", "write it as cron instead", "Time zone",
+ "NEXT FIVE FIRE TIMES", "Review schedule", "Create schedule",
+ "202 Accepted", "Run now", "Pause", "Delete"):
+ if required.casefold() not in schedule_visible_text_casefolded:
+ raise SystemExit(f"schedule frame is missing required copy: {required}")
+ if "run input (optional)" not in schedule_visible_text_casefolded:
+ raise SystemExit("schedule frame incorrectly hides optional recurring prompt semantics")
+ for required_state in ("enabled after creation",):
+ if required_state not in schedule_visible_text_casefolded:
+ raise SystemExit(f"schedule frames are missing honest Schedule state: {required_state}")
+ if "trigger" in schedule_visible_text_casefolded:
+ raise SystemExit("schedule board still contains unsupported trigger terminology")
+ for forbidden in (
+ "team member",
+ "team automation",
+ "teamid",
+ "memberid",
+ "publishedserviceid",
+ "dedicated agent key",
+ "node ids",
+ "owner llm",
+ "permission digest",
+ "permissiondigest",
+ "policy version",
+ "policyversion",
+ "review and reauthorize",
+ "/api/schedules",
+ "/automations/preflight",
+ ):
+ if forbidden in schedule_visible_text_casefolded:
+ raise SystemExit(f"Schedule design leaks the wrong resource model: {forbidden}")
+ schedule_frame_names = tuple(
+ element["name"]
+ for element in schedule_document["elements"]
+ if element.get("type") == "frame"
+ )
+ if schedule_frame_names != EXPECTED_SCHEDULE_FRAMES:
+ raise SystemExit(
+ "schedule frame inventory mismatch:\n"
+ f"expected {EXPECTED_SCHEDULE_FRAMES!r}\n"
+ f"got {schedule_frame_names!r}"
+ )
+ if len(schedule_frame_names) != len(EXPECTED_SCHEDULE_FRAMES):
+ raise SystemExit("schedule board must contain exactly seven standalone review scenes")
+
+ schedule_frames_by_name = {
+ element["name"]: element["id"]
+ for element in schedule_document["elements"]
+ if element.get("type") == "frame"
+ }
+
+ def schedule_frame_text(frame_name: str) -> str:
+ frame_id = schedule_frames_by_name.get(frame_name)
+ return "\n".join(
+ element["text"]
+ for element in schedule_document["elements"]
+ if element.get("type") == "text" and element.get("frameId") == frame_id
+ )
+
+ quick_modal_text = schedule_frame_text("01 · Workflows — schedule management modal").casefold()
+ for required in ("schedules for weekly feedback report", "new schedule", "open"):
+ if required not in quick_modal_text:
+ raise SystemExit(f"Workflow catalogue Schedule management modal is missing: {required}")
+ if "recurring runs owned by weekly feedback report" in quick_modal_text:
+ raise SystemExit("Workflow catalogue Schedule modal repeats its ownership as helper copy")
+ quick_modal_lines = {line.strip() for line in quick_modal_text.splitlines()}
+ for forbidden in ("edit", "pause", "run now", "delete"):
+ if forbidden in quick_modal_lines:
+ raise SystemExit(f"Workflow catalogue repeats selected-Schedule actions in every row: {forbidden}")
+ configure_frame_name = "02 · Workflow — schedule setup panel"
+ configure_text = schedule_frame_text(configure_frame_name).casefold()
+ for required in (
+ "schedule name",
+ "weekly feedback report recurring work",
+ "repeat",
+ "every weekday at 09:00",
+ "write it as cron instead",
+ "post /api/scopes/{scopeid}/workflows/{workflowid}/schedules/preview",
+ "no prompt",
+ ):
+ if required not in configure_text:
+ raise SystemExit(f"{configure_frame_name} is missing Schedule configuration content: {required}")
+ if "cron expression" in configure_text:
+ raise SystemExit(f"{configure_frame_name} exposes raw cron as a default primary field")
+
+ schedule_edit_text = schedule_frame_text("07 · Workflow — change schedule").casefold()
+ for required in (
+ "schedule name",
+ "weekly review",
+ "repeat",
+ "every monday at 10:00",
+ "asia/shanghai",
+ "0 10 * * 1",
+ "write it as cron instead",
+ ):
+ if required not in schedule_edit_text:
+ raise SystemExit(f"Workflow Schedule edit is missing repeat-builder semantics: {required}")
+ if "cron expression" in schedule_edit_text:
+ raise SystemExit("Workflow Schedule edit exposes raw cron as a default primary field")
+
+ review_text = schedule_frame_text("03 · Schedule — review before creation").casefold()
+ for required in (
+ "review schedule",
+ "weekly feedback report",
+ "weekly feedback report recurring work",
+ "every weekday at 09:00",
+ "asia/shanghai",
+ "no prompt",
+ "enabled after creation",
+ "next five fire times",
+ "create schedule",
+ "post /api/scopes/{scopeid}/workflows/{workflowid}/schedules",
+ ):
+ if required not in review_text:
+ raise SystemExit(f"Schedule creation review is missing API-backed fact: {required}")
+
+ pending_text = schedule_frame_text("04 · Schedule — creation pending").casefold()
+ for required in (
+ "202 accepted",
+ "schedule request accepted",
+ "appear in this workflow's schedule list shortly",
+ ):
+ if required not in pending_text:
+ raise SystemExit(f"Schedule pending frame is missing honest accepted state: {required}")
+
+ for workflow_frame_name in (
+ "05 · Workflow — schedule overview",
+ "06 · Workflow — schedule history",
+ "07 · Workflow — change schedule",
+ ):
+ workflow_frame_text = schedule_frame_text(workflow_frame_name).casefold()
+ if "workflow canvas remains visible" not in workflow_frame_text:
+ raise SystemExit(f"{workflow_frame_name} is not visibly owned by Workflow")
+ if "weekly feedback report" not in workflow_frame_text:
+ raise SystemExit(f"{workflow_frame_name} does not identify its owning Workflow")
+
+ schedule_overview_text = schedule_frame_text("05 · Workflow — schedule overview").casefold()
+ for required in (
+ "morning digest",
+ "overview",
+ "history",
+ "active",
+ "every weekday at 09:00",
+ "asia/shanghai",
+ "next scheduled",
+ "last attempt",
+ "total attempts",
+ "failed attempts",
+ "summarize new feedback",
+ "advanced details",
+ "runs",
+ "technical format",
+ "0 9 * * 1-5",
+ "run now",
+ "edit schedule",
+ "more",
+ ):
+ if required not in schedule_overview_text:
+ raise SystemExit(f"Workflow Schedule Overview is missing: {required}")
+ for forbidden in ("cron expression", "recent fires", "observed schedule state only"):
+ if forbidden in schedule_overview_text:
+ raise SystemExit(f"Workflow Schedule Overview exposes secondary implementation content: {forbidden}")
+
+ schedule_history_text = schedule_frame_text("06 · Workflow — schedule history").casefold()
+ for required in (
+ "morning digest",
+ "recent attempts",
+ "scheduled",
+ "manual",
+ "schedule outcome",
+ "run created",
+ "failed to start",
+ "accepted",
+ "completed",
+ "technical details",
+ "the scheduled attempt could not start the workflow",
+ "view related runs in activity",
+ ):
+ if required not in schedule_history_text:
+ raise SystemExit(f"Workflow Schedule History is missing: {required}")
+ schedule_history_lines = {line.strip() for line in schedule_history_text.splitlines()}
+ for forbidden in ("result", "run started", "action"):
+ if forbidden in schedule_history_lines:
+ raise SystemExit(f"Workflow Schedule History retains ambiguous or redundant copy: {forbidden}")
+ for forbidden in ("run id", "actor id", "command id", "correlation id", "idempotency key"):
+ if forbidden in schedule_history_text:
+ raise SystemExit(f"Workflow Schedule History leaks a runtime identifier: {forbidden}")
+ if "what will run" in schedule_visible_text_casefolded:
+ raise SystemExit("Schedule board still presents the removed collection model")
+
+ prototype_text = prototype_path.read_text(encoding="utf-8")
+ modal_list_start = prototype_text.index("function renderScheduleModalList")
+ modal_list_end = prototype_text.index("function openScheduleQuickModal", modal_list_start)
+ prototype_modal_list_text = prototype_text[modal_list_start:modal_list_end]
+ for required in (
+ 'textContent = `Schedules for ${workflow.name}`',
+ 'id="quick-schedule-refresh"',
+ 'id="quick-schedule-new"',
+ ):
+ if required not in prototype_modal_list_text:
+ raise SystemExit(f"prototype Schedule collection is missing meaningful hierarchy: {required}")
+ for forbidden in ("Recurring runs owned by", "
Schedules
", "schedule-quick-context"):
+ if forbidden in prototype_modal_list_text:
+ raise SystemExit(f"prototype Schedule collection retains redundant title content: {forbidden}")
+ overview_start = prototype_text.index("function scheduleOverviewMarkup")
+ overview_end = prototype_text.index("function scheduleHistoryMarkup", overview_start)
+ prototype_overview_text = prototype_text[overview_start:overview_end]
+ for required in (
+ 'Runs${escapeHtml(editModel.humanSummary)}',
+ 'Technical format${escapeHtml(schedule.cronExpression)}',
+ ):
+ if required not in prototype_overview_text:
+ raise SystemExit(f"prototype Schedule Overview is missing readable technical hierarchy: {required}")
+ creation_start = prototype_text.index("function resetScheduleCreation")
+ creation_end = prototype_text.index("function openSchedulePrototype", creation_start)
+ creation_text = prototype_text[creation_start:creation_end]
+ configure_start = creation_text.index('if (quickScheduleStep === "configure")')
+ configure_end = creation_text.index('if (quickScheduleStep === "previewing")', configure_start)
+ configure_creation_text = creation_text[configure_start:configure_end]
+ if 'id="editor-schedule"' not in prototype_text:
+ raise SystemExit("prototype schedule action is missing from the editor")
+ if 'data-schedule-workflow="${item.id}"' not in prototype_text:
+ raise SystemExit("prototype does not expose Schedule from published Workflow rows")
+ if 'button.onclick = () => openScheduleQuickModal(button.dataset.scheduleWorkflow)' not in prototype_text:
+ raise SystemExit("Workflow list Schedule action does not open the management modal")
+ for required in (
+ 'id="schedule-quick-modal"',
+ "function openScheduleQuickModal",
+ "function renderScheduleModalList",
+ "function openSchedulePanelCreation",
+ "function renderScheduleCreation",
+ "function requestQuickSchedulePreview",
+ "function acceptQuickScheduleCreation",
+ "Review schedule",
+ "Create schedule",
+ 'textContent = `Schedules for ${workflow.name}`',
+ 'id="quick-schedule-new"',
+ "without leaving Workflows",
+ "Next five fire times",
+ "POST /api/scopes/{scopeId}/workflows/{workflowId}/schedules/preview",
+ "POST /api/scopes/{scopeId}/workflows/{workflowId}/schedules",
+ "Repeat",
+ "Every weekday at 09:00",
+ "write it as cron instead",
+ 'id="quick-schedule-cron-editor" hidden',
+ "updateQuickScheduleRepeatPresentation",
+ '"Every hour": "0 * * * *"',
+ '"Write cron yourself": "15 14 * * 2,4"',
+ "prototypeSchedulePreviewByCron",
+ "const presetCron = quickScheduleCronByRepeat[repeat]",
+ "browserTimeZone()",
+ "Schedule request accepted. It will appear in the Workflow schedule list shortly.",
+ "exitScheduleCreation();",
+ "Save and publish the latest changes before scheduling.",
+ ):
+ if required not in prototype_text:
+ raise SystemExit(f"prototype quick-create Schedule modal is missing: {required}")
+ for required in (
+ 'scheduleCreationSurface === "panel"',
+ 'schedulePanelMode = "new"',
+ "prototypeSchedulePreviewByCron[quickScheduleDraft.cronExpression]",
+ 'modal.id === "schedule-quick-modal"',
+ ):
+ if required not in prototype_text:
+ raise SystemExit(f"prototype shared Schedule creation flow is missing: {required}")
+ for forbidden in (
+ "function createQuickSchedule()",
+ "Schedule created for",
+ "Mon 24 Aug · 09:00",
+ "Summarize new feedback and post one concise update.",
+ 'LarkAlready granted',
+ "prototypeSchedulePreflightByService",
+ "prototypeSchedulePreviewByRequest",
+ "prototypeSchedulePreflightByOwner",
+ "requestQuickSchedulePreflight",
+ "preflightMatchesWorkflow",
+ "confirmedPermissionDigest",
+ "confirmedPolicyVersion",
+ "Dedicated Agent Key",
+ "Review and reauthorize",
+ "/automations/preflight",
+ 'quickScheduleStep === "accepted"',
+ "quick-schedule-close-accepted",
+ ):
+ if forbidden in prototype_text:
+ raise SystemExit(f"prototype quick-create Schedule modal still contains optimistic or shared sample state: {forbidden}")
+ if 'id="quick-schedule-cron-editor" hidden' not in configure_creation_text:
+ raise SystemExit("prototype quick-create Schedule exposes raw cron as a default primary field")
+ for required in (
+ 'id="quick-schedule-name"',
+ 'name: content.querySelector("#quick-schedule-name").value.trim()',
+ ):
+ if required not in configure_creation_text and required not in creation_text:
+ raise SystemExit(f"prototype Schedule creation does not preserve its editable name: {required}")
+ for required in (
+ "function scheduleEditModel(schedule)",
+ 'id="schedule-edit-name"',
+ 'id="schedule-edit-repeat"',
+ 'id="schedule-edit-repeat-time"',
+ 'id="schedule-edit-time-zone"',
+ 'id="schedule-edit-cron"',
+ 'id="schedule-edit-prompt"',
+ 'displayName: root.querySelector("#schedule-edit-name").value.trim()',
+ 'cronExpression: root.querySelector("#schedule-edit-cron").value.trim()',
+ 'timezone: root.querySelector("#schedule-edit-time-zone").value.trim()',
+ "enabled: schedule.enabled",
+ 'prompt: root.querySelector("#schedule-edit-prompt").value.trim()',
+ ):
+ if required not in prototype_text:
+ raise SystemExit(f"prototype Schedule edit does not round-trip observed state: {required}")
+ for forbidden in ("const grants = workflow.steps.map", "schedules.unshift("):
+ if forbidden in creation_text:
+ raise SystemExit(f"prototype Schedule creation derives or invents observed state: {forbidden}")
+ if 'window.location.hash === "#schedule"' not in prototype_text:
+ raise SystemExit("prototype does not expose a directly addressable Schedule state")
+ if 'class="studio-schedule-panel"' not in prototype_text:
+ raise SystemExit("prototype schedule panel is missing from the editor")
+ if "Publish this workflow before scheduling it." not in prototype_text:
+ raise SystemExit("prototype schedule draft-state copy is missing")
+ if "schedulePanelMode" not in prototype_text or "data-schedule-id" not in prototype_text:
+ raise SystemExit("prototype does not model Schedule list and detail states")
+ if "const schedule = schedules[0]" in prototype_text:
+ raise SystemExit("prototype only renders the first Schedule")
+ if "graphLayout(workflowSteps, schedulePanelOpen)" not in prototype_text:
+ raise SystemExit("prototype does not reserve canvas layout for the Schedule panel")
+ if "flex: 0 0 400px;" not in prototype_text:
+ raise SystemExit("prototype Schedule panel does not reserve desktop width")
+ if "What will run" in prototype_text:
+ raise SystemExit("prototype still presents Schedule definitions inside Activity")
+ for forbidden in (
+ "function openScheduledRuns",
+ 'id="schedule-activity"',
+ "View scheduled runs",
+ "watch Activity",
+ "next fire and Activity",
+ "rereads Team Automation and Activity",
+ "It will appear in Activity when available.",
+ ):
+ if forbidden in prototype_text:
+ raise SystemExit(f"prototype Schedule surface uses the obsolete Activity handoff: {forbidden}")
+ for required in (
+ "function renderScheduleModalSelected",
+ "function scheduleAttemptOutcome",
+ "function scheduleOverviewMarkup",
+ "function scheduleHistoryMarkup",
+ "function openRelatedRunsInActivity",
+ "View related runs in Activity",
+ "Recent attempts",
+ "Schedule outcome",
+ "Failed to start",
+ "Run created",
+ "Accepted",
+ "Technical details",
+ "The ${source.toLowerCase()} attempt could not start the Workflow.",
+ 'class="schedule-attempt-row-link"',
+ 'target="_blank" rel="noopener noreferrer"',
+ "data-production-href",
+ "prototypeRun",
+ "activityScheduleId",
+ "run.scheduleId === activityScheduleId",
+ "?workflowId=${workflow.id}&schedule=${schedule.scheduleId}&origin=schedule",
+ ):
+ if required not in prototype_text:
+ raise SystemExit(f"prototype Schedule history flow is missing: {required}")
+ if "scheduleMutation" not in prototype_text:
+ raise SystemExit("prototype does not model accepted Schedule mutations")
+ if 'data-step-type="schedule"' in prototype_text:
+ raise SystemExit("prototype still exposes Schedule as a workflow graph node")
+ if "schedule.enabled = event.target.checked" in prototype_text:
+ raise SystemExit("prototype treats an accepted Schedule update as authoritative")
+ if "prototypeSchedules[currentWorkflow.id] = []" in prototype_text:
+ raise SystemExit("prototype treats an accepted Schedule delete as authoritative")
+ for required in (
+ "prototypeWorkflowSchedules[currentWorkflow.scopeId]?.[currentWorkflow.id]",
+ "GET /api/scopes/{scopeId}/workflows/{workflowId}/schedules",
+ "GET /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}",
+ "PUT /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}",
+ "POST /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}:enable",
+ "POST /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}:disable",
+ "POST /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}:run-now",
+ "DELETE /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}",
+ "displayName: quickScheduleDraft.name",
+ "cronExpression: quickScheduleDraft.cronExpression",
+ "timezone: quickScheduleDraft.timeZone",
+ "enabled: quickScheduleDraft.enabled",
+ "prompt: quickScheduleDraft.prompt",
+ "schedule.scheduleId",
+ "enabled: schedule.enabled",
+ "refreshWorkflowSchedules",
+ ):
+ if required not in prototype_text:
+ raise SystemExit(f"prototype is missing workflow-scoped Schedule behavior: {required}")
+ schedule_surface_text = prototype_text[
+ prototype_text.index("function scheduleAvailability"):
+ prototype_text.index("function renderActivity")
+ ]
+ for forbidden in (
+ "schedule.id",
+ ".teamId",
+ ".memberId",
+ "prototypeTeamAutomations",
+ "Team member automation",
+ "Team Automation",
+ "/api/schedules",
+ ):
+ if forbidden in schedule_surface_text:
+ raise SystemExit(f"prototype Schedule flow uses the wrong product owner: {forbidden}")
+ if "teamId" in prototype_text or "memberId" in prototype_text:
+ raise SystemExit("pure Workflow prototype fixtures still require Team or Member identity")
+ if "Run input (optional)" not in prototype_text:
+ raise SystemExit("prototype does not expose prompt as optional")
+ if "Schedule update accepted." not in prototype_text or "Schedule deletion accepted." not in prototype_text:
+ raise SystemExit("prototype does not model accepted Workflow Schedule mutations")
+ if "function schedulePreview(schedule)" not in prototype_text:
+ raise SystemExit("prototype does not distinguish paused Schedule previews")
+ if 'schedule.enabled && schedule.nextFireAt ? [schedule.nextFireAt] : ["No upcoming attempts"]' not in prototype_text:
+ raise SystemExit("prototype promises upcoming attempts for a paused Schedule")
+
+ schedule_prototype_text = schedule_prototype_path.read_text(encoding="utf-8")
+ if "prototype.html#schedule" not in schedule_prototype_text:
+ raise SystemExit("dedicated Schedule prototype does not open the visible Schedule state")
+ for png_name, expected_sha256 in SCHEDULE_PNG_SHA256.items():
+ verify_png(
+ BASELINE_DIR / png_name,
+ (1440, 900),
+ expected_sha256,
+ actual_schedule_sha256,
+ actual_renderer_sha256,
+ )
+
+ frame_names = tuple(
+ element["name"]
+ for element in document["elements"]
+ if element.get("type") == "frame"
+ )
+ if frame_names != EXPECTED_FRAMES:
+ raise SystemExit(
+ "frame inventory mismatch:\n"
+ f"expected {EXPECTED_FRAMES!r}\n"
+ f"got {frame_names!r}"
+ )
+
+ with tempfile.TemporaryDirectory(prefix="aevatar-vnext-baseline-") as temp_dir:
+ generated_dir = Path(temp_dir)
+ generated_script = generated_dir / GENERATOR_NAME
+ shutil.copyfile(generator_path, generated_script)
+ subprocess.run(["python3", str(generated_script)], check=True, capture_output=True)
+ generated_bytes = (generated_dir / BOARD_NAME).read_bytes()
+ generated_schedule_script = generated_dir / SCHEDULE_GENERATOR_NAME
+ shutil.copyfile(schedule_generator_path, generated_schedule_script)
+ subprocess.run(["python3", str(generated_schedule_script)], check=True, capture_output=True)
+ generated_schedule_bytes = (generated_dir / SCHEDULE_BOARD_NAME).read_bytes()
+
+ if importlib.util.find_spec("PIL") is not None:
+ subprocess.run(
+ [sys.executable, str(schedule_renderer_path), "--output-dir", str(generated_dir)],
+ check=True,
+ capture_output=True,
+ )
+ for png_name in SCHEDULE_PNG_SHA256:
+ if (generated_dir / png_name).read_bytes() != (BASELINE_DIR / png_name).read_bytes():
+ raise SystemExit(f"schedule renderer output does not match {png_name}")
+
+ if generated_bytes != board_bytes:
+ raise SystemExit("generator output does not match the committed Excalidraw")
+ if generated_schedule_bytes != schedule_board_bytes:
+ raise SystemExit("schedule generator output does not match the committed Excalidraw")
+
+ print(f"design SHA-256: {actual_sha256}")
+ print(f"frames: {len(frame_names)}/{len(EXPECTED_FRAMES)}")
+ print(f"schedule design SHA-256: {actual_schedule_sha256}")
+ print(f"schedule frames: {len(schedule_frame_names)}/{len(EXPECTED_SCHEDULE_FRAMES)}")
+ print(f"schedule PNGs: {len(SCHEDULE_PNG_SHA256)} standalone 1440x900 scenes")
+ print("schedule PNG pixels: non-blank and source-linked")
+ print("schedule HTML: direct entry available")
+ print("generators output: byte-identical")
+ print("workflow activity vNext baseline: PASS")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/apps/aevatar-console-web/docs/features/2026-09-14-channel-pages.md b/apps/aevatar-console-web/docs/features/2026-09-14-channel-pages.md
new file mode 100644
index 0000000000..88413c1d43
--- /dev/null
+++ b/apps/aevatar-console-web/docs/features/2026-09-14-channel-pages.md
@@ -0,0 +1,292 @@
+# Channels in the Console
+
+> Superseded by [Channel Bot Adoption](../superpowers/specs/2026-09-17-channel-bot-adoption.md).
+> Channels now lists bound and unbound NyxID bots with a static illustrated guide
+> and a Bind action. The Telegram credential form and runtime-config API described
+> below are historical.
+
+Channels is a first-level destination in the Workflow Activity vNext sidebar.
+It lets the signed-in owner view their connected bots and inspect a connection.
+
+The three pages follow the latest simplified [Figma design](https://www.figma.com/design/FaVJx5IZeQX9jHcUi55ndB?node-id=7-226)
+(list frame `7:2`, detail frame `6:131`, Telegram form `7:226`). The initial detail
+design used Remove as its sole resource action. The current detail page also
+links to the editor documented in
+[Channel Runtime Editor](../2026-09-15-channel-runtime-editor.md); its edit form
+now exposes only Label and Skill name, preserving existing service authorization.
+
+Channel refresh is explicitly user driven. Do not add background polling or
+refresh on focus/reconnection to these pages.
+
+Manual Refresh keeps the connected table mounted and displays the shared
+loading overlay over that table while the registration list, bot names, and
+active status refreshes settle. Table actions are inert during the refresh;
+the button blocks duplicate submissions. Completion or failure removes the
+overlay and restores interaction, with the existing error toast and manual
+retry behavior retained.
+
+The page header and content are horizontally centered within the area beside
+the navigation rail, using the same maximum width and responsive side padding.
+The connection form and details share this alignment; the form keeps its
+narrower maximum width. On small screens the containers fill the available
+width while retaining the shell's side padding.
+
+Refresh and Manage use outlined button styling with visible hover/focus states
+and larger touch targets on mobile. Manage retains navigation-link semantics.
+Details omit Scope. Bot ID and Agent key ID are blue underlined external links
+with an external-link icon, opening the corresponding NyxID record in a new tab.
+The website origin is the design's `https://nyx.chrono-ai.fun`, separate from
+the configured NyxID API/OIDC authority. Bot links use `/channel-bots/{botId}`;
+Agent key links use the verified NyxID `/keys/api-key/{agentKeyId}` route.
+The Figma `/keys/xxx` placeholder refers to NyxID service details, so it is not
+used for Agent keys. Each complete source ID is encoded as one URL segment;
+missing IDs remain unlinked placeholders.
+
+The compact identifier in each connected-channel row opens the shared Tooltip
+with the exact full bot ID (or registration ID when no bot ID exists). Mouse
+hover, keyboard focus and click/tap reveal the full value, which wraps within
+the Tooltip instead of being shortened again. The control has a visible focus
+ring.
+
+## Routes and existing setup
+
+- `/scopes/:scopeId/workflow-activity-vnext/channels`: platform directory and
+ owner connection list.
+- `/scopes/:scopeId/workflow-activity-vnext/channels/:registrationId`: exact
+ connection details. Registration IDs are opaque and encoded as one segment.
+- `/scopes/:scopeId/workflow-activity-vnext/channels/connect/telegram`: the
+ single-page Telegram connection form. Connect Telegram navigates here.
+ The channel directory shows Telegram and WhatsApp only. WhatsApp replaces
+ the Lark/Feishu placeholders and is marked Soon without a connection action;
+ Discord and Slack placeholders are omitted. Existing connected records still
+ render the platform returned by the API.
+ The older backend `/channels` onboarding page remains independently available.
+
+`AEVATAR_CHANNEL_WEBHOOK_BASE_URL` optionally sets the public backend callback
+base URL at build time. The default is
+`https://aevatar-console-backend-api.aevatar.ai`. It must be HTTPS without
+credentials, query, or fragment. An invalid value disables registration with
+an inline deployment-configuration message. It is never inferred from the
+separate Console frontend origin or shown as an editable form field.
+
+## Telegram connection form
+
+The form requires a masked bot token and shows independent optional Channel name
+(「Channel 名字」 in Chinese) and Skill name inputs. They share a row on desktop
+and stack on narrow screens. The service selection may be empty. The token
+reveal control is keyboard accessible. Unsaved form navigation asks whether to discard; form data
+is never persisted. The token is used only for Telegram's official name lookup
+and the authenticated registration POST. It stays in component memory for a
+manual retry after failure and clears after successful admission. No registration
+mutation cache stores it.
+
+Services use the current NyxID session's complete `GET /api/v1/user-services`
+inventory and its exact bearer service grants. The account inventory includes
+HTTP tools and LLM services; personal ownership alone does not establish that
+the current Aevatar session can grant a service to a bot.
+
+The existing session helper refreshes an expired session before selection.
+The auth adapter reads `allow_all_services` and `allowed_service_ids` from that
+access token. Explicit `true` permits all active, account-accessible UserServices;
+explicit `false` permits only exact IDs in the list, including an empty grant.
+Inactive services, disallowed organization memberships and unknown credential
+sources remain excluded. Names, slugs, catalog IDs and credential IDs never
+substitute for `UserService.id`. No service type is excluded: an authorized LLM
+service such as Chrono Public remains selectable even without MCP operations.
+
+These claims are used only for display filtering, not as client-side signature
+verification or proof that registration will succeed. The inventory request is
+pinned to the exact bearer used for filtering; NyxID authenticates it and the
+registration backend enforces delegation limits. Unsupported/malformed claims,
+a mismatched account subject, an expired JWT or a failed inventory read leave
+the picker in a retryable error state. There is no allow-all fallback when
+claims are absent. Token/parser errors are sanitized, and token/claim payloads
+never enter the query cache. Only the filtered safe display fields are retained.
+
+Do not substitute `/api/v1/mcp/config` for this authorization list: its documented
+contract describes executable tool operations and omits LLM services and some
+services with unavailable runtime routes. `/api/v1/llm/status` is also not an
+exact current-bearer grant inventory, and account consent records may differ
+from the current token's resource restrictions. The NyxID access-token producer
+in `backend/src/crypto/jwt.rs` emits explicit service grants for OAuth clients;
+`backend/src/mw/auth.rs` consumes the same fields for service authorization.
+
+The inventory read omits browser cookies and bypasses HTTP caching. It runs on
+entry and explicit recovery only; no polling, window-focus or reconnect refresh
+is added. A bearer with only some account services authorized sees only that
+subset, across HTTP and LLM service types.
+
+Options use label, then catalog service name, then slug. Search matches label
+or slug and preserves selections outside the current search. A Select all
+checkbox selects or clears every available service in the current results,
+with a partial-selection state. While searching, it is labeled Select all
+results and preserves selections outside the search. Inactive or disallowed
+services are excluded; no selectable results or a locked form disables it.
+Each selection uses the exact `services[].id`; no default selection is made.
+The registration POST always supplies `authorization_mode: explicit_service_allowlist` and
+`service_ids`, including `[]` for no service access. It never substitutes a
+slug, endpoint ID, API key ID, or omitted field. If a selected service becomes
+unavailable after an explicit recovery read, it is removed from the selection.
+Submission waits while access is being rechecked, preventing stale hidden IDs
+from entering a retry. Load failure has inline retry; an empty authorized list
+can connect with an explicit empty service allowlist even if the account owns
+other services.
+
+Registration uses existing `POST /api/channels/registrations` with
+`platform`, `bot_token`, `label`, `default_skill_name`, `webhook_base_url`, and
+the explicit selection above. A `202`/`status: accepted` response must contain
+the registration ID. After admission, the form clears its token, retains only
+the safe returned ID, and explicitly refreshes registrations once. A successful
+fresh read must contain that exact ID, route scope and Telegram platform before
+the success toast and navigation to the newly created channel's existing detail
+page. The form does not return to the collection automatically or infer success
+from cached rows after a failed read. Until confirmation, it preserves the
+submitted choices and shows a pending state with Check again. This action only
+repeats the GET, never the create POST; failed confirmation reads show a safe
+error toast and remain retryable. No polling, window-focus refresh, or reconnect
+refresh runs. Inbound activity continues to use its real status, independently
+of registration creation.
+Registration failures, including
+504 and network errors, show a shared error toast and restore editable fields and Connect
+Telegram. Inputs and still-authorized selections are preserved for an explicit manual
+retry; no automatic registration retry runs. An in-flight request still blocks
+duplicate clicks. An accepted submission cannot be sent again while navigation
+is completing; late responses after unmount cannot show a toast or navigate.
+
+### Channel name, Skill name and Telegram bot-name defaults
+
+The registration adapter trims the two inputs independently: Channel name maps
+to `label`, and Skill name maps to `default_skill_name`. If both are nonblank,
+Telegram name lookup is skipped. Otherwise, the adapter calls Telegram's
+official `getMe` endpoint once and uses the bot's trimmed `first_name`, never
+its `username`, to resolve missing names.
+
+A blank Channel name defaults to the bot name plus a hyphen and six random
+digits (for example, `My Bot-482731`). The suffix is generated in the frontend
+once per submission, in the range 100000–999999. A blank Skill name defaults
+to the bot name without the suffix, restoring the original independent field
+behavior. Custom names receive no suffix and never overwrite the other input.
+The form locks during lookup/submission and preserves its input after failure;
+a manual retry resolves the current token/name again.
+
+This uses the same official API flow as NyxID's existing Telegram onboarding,
+because the reviewed Aevatar/NyxID backend has no separate pre-registration
+name endpoint. The plain fetch targets only `https://api.telegram.org`, with
+credentials omitted, no cache, no referrer, redirects rejected and a 15-second
+timeout. Telegram's protocol requires the token in the API request path; it
+never enters browser navigation, logs, toast text, persistent state, or the
+authenticated fetch helper. Raw fetch errors and response bodies are discarded.
+A rejected token, unavailable lookup or absent bot name prevents registration,
+shows a safe toast and leaves the form available for retry.
+
+## Connected names, channels and skills
+
+The connected table separates Channel name from Channel (platform). The current
+Aevatar registration summary does not return the submitted label: provisioning
+stores it on the NyxID bot. For a nonempty list with bot references, the page
+therefore reads `GET /api/v1/channel-bots` once through existing NyxID auth/config,
+without an organization selector. This endpoint returns the caller's active
+personal bots as `{ bots, total }`. Only `id`, `platform` and `label` enter the
+query cache. Registration `nyx_channel_bot_id` must exactly match `bots[].id`,
+and platforms must agree; registration IDs, platform bot IDs, usernames and
+skill names never substitute for that join or for the Channel name.
+
+Names load independently of registrations and statuses. Missing/deactivated
+bots, empty labels and failed reads show Name unavailable beside the existing
+identifier; loading is shown explicitly. A name-read failure preserves the
+connected rows and actions, shows a safe error toast and can be retried with
+Refresh. Refresh reads the names once with the list/status reads. No per-row
+name request, automatic retry, polling, focus or reconnect refresh is added.
+
+Channel details use the same bot-identity query and exact join for their Channel
+name heading. Missing or failed name reads keep the detail facts and actions
+available with a local Reload channel name action. The title never substitutes
+a compact bot ID, registration ID or Skill name for the label.
+
+Channel details include Skill using the same name, optional version and Ornn
+link as the connected list. Missing skills show Not set. Reading names/skills
+does not request full runtime configuration or alter bot resources.
+
+### Authorized services in channel details
+
+The detail page displays the registration's saved `authorization_mode` and
+`service_ids` from `GET /api/channels/registrations`. For an explicit allowlist,
+only those exact UserService IDs are shown. One account inventory read resolves
+their labels and slugs; the current session's selectable grants and service
+activity do not hide saved channel authorizations or add other services.
+Only safe ID, label and slug fields enter the service-name query cache.
+
+Services appear as compact labels arranged horizontally, wrapping when the
+available width is filled. Each service shows its resolved display name once;
+the slug is not repeated on a second line. Long names wrap within their label.
+
+Missing service names retain their saved IDs. A failed name lookup preserves
+other details and the saved authorization list, shows a safe toast, and offers
+a manual retry of the names alone. Explicit empty authorization shows no
+services authorized; NyxID default authorization and unavailable or legacy
+authorization details have distinct messages and do not query the inventory.
+No automatic refresh or registration change is introduced by this display.
+
+## API and ownership
+
+Contracts were checked against `origin/feature/integrate` and the backend
+API comment in [issue #3617](https://github.com/aevatarAI/aevatar/issues/3617).
+
+- `GET /api/channels/registrations` supplies the current owner's summaries.
+ The frontend never requests the administrative `scope=all` view and excludes
+ records explicitly marked as foreign. The route scope partitions query
+ state and navigation; authentication remains the server's owner authority.
+- Each visible connection reads
+ `GET /api/channels/registrations/{registrationId}/status` independently.
+ Pending, active, error, and unknown states remain distinct. A status failure
+ does not hide the connection or claim it is active. Visible status queries
+ load on entry and refresh only through the explicit Refresh action. Neither
+ the list nor status queries poll, refresh on window focus, or refresh on
+ network reconnection.
+- Skill uses `default_skill.name` and optional `version` from PR #3620.
+ The deployed name-only `default_skill_name` contract is also supported.
+ An absent name displays Not set; an absent version is omitted. No skill
+ examples are embedded in runtime code. Ornn links use its published
+ `/skills/:idOrName` route and the existing `ORNN_BASE_URL` configuration.
+- Detail uses the exact owned summary and its status. It does not request or
+ cache the full runtime configuration because this design has no editor.
+ Unknown or inaccessible registrations share one unavailable state.
+- The API adapter selects safe fields before returning to the query cache.
+ Webhook URLs, credentials, runtime instructions, arbitrary extra fields, and
+ raw error/cleanup bodies are not retained. Agent key ID is an identifier,
+ never the raw key.
+
+## Removal and recovery
+
+Remove first confirms the specific bot. DELETE uses the registration ID,
+never the bot ID, scope ID, or key ID. A failed operation keeps the detail and
+confirmation available for retry. No real connection is removed during QA.
+
+A `status: deleted` response starts list reconciliation. The UI keeps Remove
+disabled and waits for the registration to disappear from a fresh owner list
+before reporting completion and returning to Channels. Removal triggers one
+list read; if the row is still present, Check again performs one GET and never
+repeats DELETE. No background polling runs while waiting.
+Cleanup warnings produce a safe NyxID follow-up message without displaying
+raw diagnostic values.
+
+## Design baseline and validation
+
+The existing shell continues to follow:
+
+- `docs/design-baselines/workflow-activity-vnext/aevatar-workflow-activity-vnext.excalidraw`
+- SHA-256: `30e74d7b410ae72c4c91432355436679033679c54c10b1702908435b001577de`
+- `docs/superpowers/specs/2026-08-04-workflow-activity-vnext-design.md`
+- `docs/superpowers/specs/2026-08-04-workflow-activity-vnext-user-paths.md`
+
+The supplied simplified Figma frames extend that shell with Channels. Existing
+login, callback, session, returnTo, and Umi localization remain shared. All
+production records and statuses come from real APIs; no mock fallback exists.
+
+Focused coverage exercises API field selection and encoding, deployed/new
+skill contracts, unknown status, loading/error/empty recovery, query isolation,
+first-level navigation, detail ownership, confirmation cancellation, delayed
+removal, failure retry, and safe cleanup warnings. Mobile table rows retain
+labels and Manage; long IDs wrap or shorten for display while full identifiers
+remain in detail. Full frontend tests, typecheck, and production build belong
+to GitHub CI under the personal incremental validation policy.
diff --git a/apps/aevatar-console-web/docs/features/2026-09-14-console-home-scope.md b/apps/aevatar-console-web/docs/features/2026-09-14-console-home-scope.md
new file mode 100644
index 0000000000..3744f14bbe
--- /dev/null
+++ b/apps/aevatar-console-web/docs/features/2026-09-14-console-home-scope.md
@@ -0,0 +1,19 @@
+# Console home scope resolution
+
+The default home, login fallback, overview redirect and 404 return action use
+`/scopes`. This is a technical entry that reads the existing authenticated
+`GET /api/auth/me` contract and opens
+`/scopes/{scopeId}/workflow-activity-vnext/workflows` using the returned scope.
+
+Never embed a personal scope in the default route or infer it from user
+`subject`, browser storage, or a previously cached account. A fresh successful
+session response with `authenticated: true` and a nonempty `scopeId` is required
+before navigation. Loading does not open a workspace; failed or incomplete
+authentication offers manual retry without a fallback scope. No background
+polling, focus refresh, or reconnect refresh is introduced.
+
+Existing explicit deep links and server authorization remain authoritative for
+resource access. This change fixes the default destination; a URL or scope ID
+is not an authorization grant. Tests cover different accounts, cached previous
+identity, missing/expired authentication and manual recovery. Full frontend
+validation is delegated to GitHub CI.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-04-workflow-activity-vnext-implementation.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-04-workflow-activity-vnext-implementation.md
new file mode 100644
index 0000000000..627f4ae6ab
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-04-workflow-activity-vnext-implementation.md
@@ -0,0 +1,99 @@
+# Workflow Activity vNext Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Deliver every approved Workflow Activity vNext user path under the isolated scoped route namespace using only authoritative backend data and existing authentication/localization behavior.
+
+**Architecture:** The route package owns page composition and transient interaction state. Existing Studio, scope, runtime, auth, and Settings adapters remain authoritative; one new typed adapter owns observatory and fork transport decoding. TanStack Query keys include the route scope and normalized filters, while accepted-to-readable and accepted-to-observed transitions use explicit receipt-bound state machines.
+
+**Tech Stack:** React 19, TypeScript, Umi Max, Ant Design, TanStack Query, XYFlow/GraphCanvas, Jest, Testing Library, Biome.
+
+---
+
+### Task 1: Isolated routes and typed transport boundary
+
+**Files:**
+- Modify: `config/routes.ts`
+- Modify: `src/routesConfig.test.ts`
+- Create: `src/shared/models/workflowActivity.ts`
+- Create: `src/shared/api/workflowActivityApi.ts`
+- Create: `src/shared/api/workflowActivityApi.test.ts`
+
+- [ ] Write route assertions for all seven hidden vNext routes, literal `workflows/new` ordering, namespace-only redirect, and unchanged legacy redirects; run the single route test and confirm RED.
+- [ ] Add precise observatory summary/detail/graph, filter, and fork receipt models plus decoders that preserve unknown status values and reject malformed required identity/version fields.
+- [ ] Write adapter tests for encoded scope/filter queries, independent detail/graph requests, 401/403/404 propagation, fork request identity, and receipt decoding; run and confirm RED before implementation.
+- [ ] Implement the seven route records and typed adapter, then rerun only `src/routesConfig.test.ts` and `src/shared/api/workflowActivityApi.test.ts` to GREEN.
+
+### Task 2: Scoped shell, catalogue, and direct creation
+
+**Files:**
+- Create: `src/pages/workflow-activity-vnext/index.tsx`
+- Create: `src/pages/workflow-activity-vnext/WorkflowActivityVNextShell.tsx`
+- Create: `src/pages/workflow-activity-vnext/navigation.ts`
+- Create: `src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`
+- Create: `src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.tsx`
+- Create: `src/pages/workflow-activity-vnext/workflows/workflowCreation.ts`
+- Create: `src/pages/workflow-activity-vnext/hooks/useDraftMaterialization.ts`
+- Create: `src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [ ] Write route integration tests for authoritative catalogue loading, successful empty data, partial-source failure, search, and real-ID navigation; run the new page test and confirm RED.
+- [ ] Write creation tests for Describe, blank, YAML import, and bundled versioned template paths. Cover materialized and `202 projection_pending` responses, bounded `404`, preserved receipt/input, retrying the same GET, and duplicate-submit prevention; confirm RED.
+- [ ] Implement the local rail/mobile navigation with existing `ConsoleLanguageSwitch` and `ConsoleAuthActions`, two-source catalogue, four direct creation forms, and receipt-bound materialization hook.
+- [ ] Rerun only the new route test after each user-path slice until UP-00 through UP-05 are GREEN.
+
+### Task 3: Common editor, first save, and draft execution
+
+**Files:**
+- Create: `src/pages/workflow-activity-vnext/workflows/WorkflowEditorPage.tsx`
+- Create: `src/pages/workflow-activity-vnext/hooks/useWorkflowEditor.ts`
+- Create: `src/pages/workflow-activity-vnext/hooks/useDraftRun.ts`
+- Reuse: `src/pages/team-member-workflow-studio/components/WorkflowStudioCanvas.tsx`
+- Reuse: `src/pages/team-member-workflow-studio/components/WorkflowStudioNodeLibrary.tsx`
+- Reuse: `src/pages/team-member-workflow-studio/components/WorkflowStudioNodeDetailPanel.tsx`
+- Reuse: `src/pages/team-member-workflow-studio/components/WorkflowStudioYamlPanel.tsx`
+- Reuse: `src/pages/team-member-workflow-studio/components/WorkflowStudioDraftRunPanel.tsx`
+
+- [ ] Write editor tests for exact draft loading, committed fallback, dirty state, YAML/canvas shared state, validation findings, existing-draft PUT, and committed-only first-save POST with returned-ID route replacement; confirm RED.
+- [ ] Implement the editor hook and page using existing Studio graph/document helpers without member APIs or member identity.
+- [ ] Write draft-run tests for real serialized YAML, stable submission, accepted/running state, disconnected/error state, no Activity completion claim, and general Open Activity when no trustworthy run ID exists; confirm RED.
+- [ ] Implement `runtimeRunsApi.streamDraftRun` integration and authoritative stream presentation, then rerun the route test to GREEN for UP-06 and UP-07.
+
+### Task 4: Activity observation, ledger, detail, and recovery
+
+**Files:**
+- Create: `src/pages/workflow-activity-vnext/activity/ActivityPage.tsx`
+- Create: `src/pages/workflow-activity-vnext/activity/RunDetailPage.tsx`
+- Create: `src/pages/workflow-activity-vnext/activity/runRecovery.ts`
+- Create: `src/pages/workflow-activity-vnext/hooks/useRunObservation.ts`
+
+- [ ] Write tests for URL-backed server filters, workflow definition resolution, filter-unavailable fallback, loading/empty/error/unknown statuses, and recent-window wording; confirm RED.
+- [ ] Implement the Activity query and ledger without name joins, local totals, revisions, duration, usage, outcome, or Needs-you filtering.
+- [ ] Write tests for independent detail/graph loading, safe not-found, partial running detail, graph-only failure, failed-step eligibility, first-executable-step eligibility, immutable source, fork receipt display, and no `newRunActorId` detail navigation; confirm RED.
+- [ ] Implement detail, graph, Retry, Run again, and bounded observation, then rerun the route test to GREEN for UP-08 through UP-12.
+
+### Task 5: Settings, identity, responsive behavior, and locale
+
+**Files:**
+- Create: `src/pages/workflow-activity-vnext/settings/SettingsPage.tsx`
+- Create: `src/pages/workflow-activity-vnext/styles.ts`
+- Modify: `src/locales/en-US.ts`
+- Modify: `src/locales/zh-CN.ts`
+- Modify: `src/locales/projectMessages.en-US.ts`
+- Modify: `src/locales/projectMessages.zh-CN.ts`
+- Modify: `src/locales/catalog.test.ts`
+
+- [ ] Write tests for real LLM loading/dirty/accepted/observed/catalogue-unavailable/save-failure states, auth-me identity/expiry, existing auth actions, runtime loading/unavailable, and no localStorage authority; confirm RED.
+- [ ] Implement AI Defaults by reusing `userLlmSelection.ts` and `observeUserLlmSave`, Account through existing session/actions, and Advanced through `getUserConfigRuntime`.
+- [ ] Add every new message to both locale catalogues and verify catalogue parity with the single locale test.
+- [ ] Add responsive Operational Automation Ledger styles and test keyboard-visible controls, semantic status, dialogs, long identities, and mobile section navigation; rerun only the new page and locale tests to GREEN for UP-13 through UP-16.
+
+### Task 6: Focused verification and delivery
+
+**Files:**
+- Review all files changed relative to `origin/feat/2026-08-04_workflow-activity-vnext`
+
+- [ ] Run `frontend_change_scope.py --base origin/feat/2026-08-04_workflow-activity-vnext`, explicitly execute every changed/new Jest file and dependency-related tests, then run Biome only on reported static-check files.
+- [ ] Because the user explicitly requested full TypeScript and production-build validation, run the package `tsc` and `build` with `CODEX_ALLOW_FULL_FRONTEND_VALIDATION=1`; do not run the complete Jest suite.
+- [ ] Run the design baseline verifier and confirm the declared SHA, byte-identical generator output, and 17/17 frames.
+- [ ] After the environment synchronizer succeeds, verify real authenticated desktop/tablet/mobile routes in-browser and capture screenshots. If the synchronizer/backend/OAuth environment remains unavailable, record the exact gap without mocks.
+- [ ] Review the complete frontend-only diff, stage only this task's files, commit with an imperative message, push, and create a Draft implementation PR without modifying PR #3187 or enabling auto-merge.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-05-workflow-activity-vnext-nyxid-visual-alignment.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-05-workflow-activity-vnext-nyxid-visual-alignment.md
new file mode 100644
index 0000000000..a9e153c316
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-05-workflow-activity-vnext-nyxid-visual-alignment.md
@@ -0,0 +1,131 @@
+# Workflow Activity vNext NyxID Visual Alignment Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Align every Workflow Activity vNext page with NyxID's measured layout, typography, density, table, form, and responsive patterns while preserving Aevatar colors and all existing product behavior.
+
+**Architecture:** Keep the current route and component ownership. Centralize the measured visual contract in `styles.ts`, make `WorkflowActivityVNextShell.tsx` own the 52px top bar and responsive navigation, and limit page edits to semantic class hooks or removal of conflicting inline styles. No API, identity, query, routing, locale, or workflow behavior changes are allowed.
+
+**Tech Stack:** React 19, TypeScript, Ant Design 6, CSS variables in the existing vNext style module, Jest/Testing Library for existing behavior coverage, Biome, and real-browser responsive verification.
+
+---
+
+## Design baseline declaration
+
+```text
+Design baseline:
+ apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/
+Primary design:
+ aevatar-workflow-activity-vnext.excalidraw
+Design SHA-256:
+ 30e74d7b410ae72c4c91432355436679033679c54c10b1702908435b001577de
+Contract specification:
+ apps/aevatar-console-web/docs/superpowers/specs/
+ 2026-08-04-workflow-activity-vnext-design.md
+User paths:
+ apps/aevatar-console-web/docs/superpowers/specs/
+ 2026-08-04-workflow-activity-vnext-user-paths.md
+Authentication and localization:
+ Existing Aevatar login, callback, session, returnTo, and Umi locale logic;
+ presentation may change, behavior may not.
+Production data source:
+ Real APIs and API-acknowledged user actions only; no mock fallback.
+Baseline integrity:
+ python3 apps/aevatar-console-web/docs/design-baselines/
+ workflow-activity-vnext/verify-baseline.py
+```
+
+### Task 1: Lock the measured design contract
+
+**Files:**
+- Modify: `docs/canon/frontend-design.md`
+- Create: `apps/aevatar-console-web/docs/superpowers/plans/2026-08-05-workflow-activity-vnext-nyxid-visual-alignment.md`
+
+- [x] Record the measured shell, typography, spacing, control, table, responsive, and accessibility rules.
+- [x] Record the permitted Aevatar deviations: existing colors, AlibabaSans delivery, and the repository's 8px radius cap.
+- [x] Update issue `#3194` with the measurement table, page inventory, and acceptance evidence.
+- [x] Run `bash tools/docs/lint.sh` and `git diff --check`.
+- [x] Commit the documentation as `Document NyxID visual alignment`.
+
+### Task 2: Align the shared shell and navigation
+
+**Files:**
+- Modify: `src/pages/workflow-activity-vnext/WorkflowActivityVNextShell.tsx`
+- Modify: `src/pages/workflow-activity-vnext/styles.ts`
+- Test: `src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [x] Preserve the existing semantic navigation and account/language components while moving their presentation into a 52px top bar.
+- [x] Place the Aevatar brand and current breadcrumb in the top bar, start the 200px local rail below it, and render the page title inside the main content surface.
+- [x] Replace the permanent mobile navigation strip with an accessible menu button and drawer using the same route builders.
+- [x] Define shared spacing, type, radius, shadow, control, and table tokens in `.wa-vnext`.
+- [x] Run the existing route integration test before and after the shell change to prove navigation, auth action reuse, and page behavior remain intact:
+
+```bash
+pnpm exec jest --runInBand --runTestsByPath \
+ src/pages/workflow-activity-vnext/index.test.tsx
+```
+
+### Task 3: Align Workflows and New workflow
+
+**Files:**
+- Modify: `src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`
+- Modify: `src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.tsx`
+- Modify: `src/pages/workflow-activity-vnext/styles.ts`
+- Test: `src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [x] Keep Workflow search and source filter in one responsive toolbar.
+- [x] Render the Workflow table with a 32px header, 53-60px rows, one clear Open action, and the branch's existing accessible secondary actions.
+- [x] Remove the mobile card transformation and use a named local horizontal scroll region.
+- [x] Replace New workflow inline presentation styles with shared classes: four equal-level creation choices, then one form container for the selected method.
+- [x] Verify Describe, blank, import, and template retain their current authoritative creation behavior through the existing focused test file.
+
+### Task 4: Align the Workflow editor
+
+**Files:**
+- Modify: `src/pages/workflow-activity-vnext/workflows/WorkflowEditorPage.tsx`
+- Modify: `src/pages/workflow-activity-vnext/styles.ts`
+- Test: `src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [x] Keep Canvas and YAML as a compact segmented mode control and preserve the existing shared editor state.
+- [x] Keep the canvas unframed/full-width inside the work surface; remove conflicting fixed layout values where shared responsive tokens can own sizing.
+- [x] Preserve visible Save, Run, Publish availability and the current node library/detail/YAML behavior.
+- [x] At mobile width, keep editor actions reachable, allow horizontal action scrolling only inside the toolbar, and preserve the existing accessible node panel behavior.
+
+### Task 5: Align Activity and Run detail
+
+**Files:**
+- Modify: `src/pages/workflow-activity-vnext/activity/ActivityPage.tsx`
+- Modify: `src/pages/workflow-activity-vnext/activity/RunDetailPage.tsx`
+- Modify: `src/pages/workflow-activity-vnext/styles.ts`
+- Test: `src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+- Test: `src/pages/workflow-activity-vnext/activity/RunDetailPage.test.tsx`
+
+- [x] Apply the NyxID ledger density to Activity while preserving status, source, updated time, server filters, and the single detail target.
+- [x] Keep mobile Activity as a semantic table in a bounded horizontal scroller rather than a card layout.
+- [x] Move Run status and recovery actions close to the page title; use a 32px underline tab strip and single-layer detail sections.
+- [x] Keep Steps and Diagnostics tables semantic and locally scrollable; preserve independent graph failure and immutable source behavior.
+- [x] Run both focused behavior tests after the visual-only edits.
+
+### Task 6: Align Settings
+
+**Files:**
+- Modify: `src/pages/workflow-activity-vnext/settings/SettingsPage.tsx`
+- Modify: `src/pages/workflow-activity-vnext/styles.ts`
+- Test: `src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [x] Replace the desktop secondary side navigation with a 32px underline tab strip that remains horizontally scrollable on mobile.
+- [x] Render each section in one bordered 8px container with a compact section header and 32-36px controls.
+- [x] Preserve authoritative LLM clean/dirty/accepted/observed states, current account actions, and read-only advanced values.
+- [x] Keep dirty actions at the bottom of the owning container and sticky only when needed; ensure they do not cover mobile inputs.
+
+### Task 7: Focused verification and delivery
+
+**Files:**
+- Review every file changed by Tasks 1-6.
+
+- [x] Run the frontend scope analyzer with the task start commit as base and use only its changed source/test/static-check files.
+- [x] Run directly changed tests and dependency-related Jest tests only. Do not run the full frontend suite.
+- [x] Run Biome with explicit changed frontend file paths. Skip package-wide typecheck because no repository-native affected typecheck target exists; GitHub CI owns full type verification.
+- [x] Run `bash tools/ci/test_stability_guards.sh`, `bash tools/docs/lint.sh`, baseline verification, and `git diff --check`.
+- [x] Verify real pages at `1440x900`, `834x1112`, and `390x844`, including local table scroll and page-level overflow checks.
+- [x] Review `git diff`, stage only this task's files, commit `Align Workflow Activity with NyxID`, push the existing feature branch, update issue `#3194`, and update PR `#3189` with exact focused commands and the GitHub CI delegation statement.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-05-workflow-list-filter-semantics.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-05-workflow-list-filter-semantics.md
new file mode 100644
index 0000000000..8d6575dd76
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-05-workflow-list-filter-semantics.md
@@ -0,0 +1,408 @@
+# Workflow List Filter Semantics Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Align Workflow and Activity list filtering without inventing unsupported Workflow states.
+
+**Architecture:** Keep resource-specific filter semantics while sharing one toolbar interaction model. Workflows derives Draft membership only from the real scoped draft API; Activity keeps its real Run API filters, and both pages synchronize list state with the URL.
+
+**Tech Stack:** React 19, TypeScript, TanStack Query, Ant Design, Umi locale, Jest, Testing Library, Biome.
+
+---
+
+## Task 1: Specify Workflow filter behavior in tests
+
+**Files:**
+
+- Modify: `src/pages/workflow-activity-vnext/index.test.tsx`
+- Test: `src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [ ] Replace the pathname-only test location with a pathname-plus-search snapshot so `useConsoleLocation()` sees copied Workflow URLs:
+
+```tsx
+let mockLocation = '/scopes/scope-alpha/workflow-activity-vnext/workflows';
+
+const readMockUrl = () => new URL(mockLocation, 'http://console.local');
+
+useLocation: () => ({
+ hash: '',
+ pathname: readMockUrl().pathname,
+ search: readMockUrl().search,
+}),
+
+getLocationSnapshot: () =>
+ `${readMockUrl().pathname}${readMockUrl().search}`,
+```
+
+- [ ] Add focused tests with distinct identities (`wf-draft-alpha`, `wf-committed-beta`) proving:
+
+```tsx
+mockLocation =
+ '/scopes/scope-alpha/workflow-activity-vnext/workflows?q=support&view=drafts';
+
+expect(await screen.findByText('Support triage')).toBeInTheDocument();
+expect(screen.queryByText('Invoice review')).not.toBeInTheDocument();
+expect(screen.getByRole('searchbox', { name: 'Search workflows' })).toHaveValue(
+ 'support',
+);
+expect(screen.getByRole('combobox', { name: 'Workflow view' })).toHaveTextContent(
+ 'Drafts',
+);
+```
+
+```tsx
+fireEvent.mouseDown(screen.getByRole('combobox', { name: 'Workflow view' }));
+expect(await screen.findByRole('option', { name: 'All workflows' })).toBeVisible();
+expect(screen.getByRole('option', { name: 'Drafts' })).toBeVisible();
+expect(screen.queryByRole('option', { name: 'Committed' })).not.toBeInTheDocument();
+expect(screen.queryByRole('option', { name: 'Published' })).not.toBeInTheDocument();
+expect(screen.queryByRole('option', { name: 'Failing' })).not.toBeInTheDocument();
+```
+
+```tsx
+fireEvent.change(screen.getByRole('searchbox', { name: 'Search workflows' }), {
+ target: { value: 'invoice' },
+});
+await waitFor(() =>
+ expect(history.replace).toHaveBeenLastCalledWith(
+ '/scopes/scope-alpha/workflow-activity-vnext/workflows?q=invoice',
+ ),
+);
+```
+
+```tsx
+mockStudioApi.listWorkflowDrafts.mockRejectedValue(new Error('draft source down'));
+mockLocation =
+ '/scopes/scope-alpha/workflow-activity-vnext/workflows?view=drafts';
+expect(await screen.findByText('Draft workflows unavailable')).toBeInTheDocument();
+expect(screen.getByRole('button', { name: 'Retry workflows' })).toBeEnabled();
+expect(screen.queryByText('No workflows yet')).not.toBeInTheDocument();
+```
+
+- [ ] Run the Workflow test and confirm RED because the view select, URL-backed search/view, clear action, and draft-unavailable state do not exist yet:
+
+```bash
+pnpm exec jest src/pages/workflow-activity-vnext/index.test.tsx --runInBand
+```
+
+Expected: the newly added assertions fail for missing `Workflow view`, missing URL restoration, or missing draft-unavailable state; unrelated existing tests remain runnable.
+
+## Task 2: Implement the Workflow view filter and honest draft failure
+
+**Files:**
+
+- Modify: `src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`
+- Modify: `src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `src/locales/workflowActivityVNextMessages.zh-CN.ts`
+- Test: `src/pages/workflow-activity-vnext/index.test.tsx`
+- Test: `src/locales/catalog.test.ts`
+
+- [ ] Import `Select`, `useConsoleLocation`, and move Refresh into the shell header next to New workflow:
+
+```tsx
+headerActions={
+
+ } onClick={retry}>
+ {t('workflowActivityVNext.common.refresh', 'Refresh')}
+
+ }
+ onClick={() => history.push(buildWorkflowActivityNewHref(scopeId))}
+ type="primary"
+ >
+ {t('workflowActivityVNext.workflows.new', 'New workflow')}
+
+
+}
+```
+
+- [ ] Restore `query` and `view` from `location.search`, synchronize browser navigation into state, and serialize only non-default values:
+
+```tsx
+type WorkflowView = 'all' | 'drafts';
+
+function readWorkflowView(params: URLSearchParams): WorkflowView {
+ return params.get('view') === 'drafts' ? 'drafts' : 'all';
+}
+
+const location = useConsoleLocation();
+const initialParams = React.useMemo(
+ () => new URLSearchParams(location.search),
+ [location.search],
+);
+const [query, setQuery] = React.useState(initialParams.get('q') ?? '');
+const [view, setView] = React.useState(
+ readWorkflowView(initialParams),
+);
+
+React.useEffect(() => {
+ const params = new URLSearchParams(location.search);
+ setQuery(params.get('q') ?? '');
+ setView(readWorkflowView(params));
+}, [location.search]);
+
+React.useEffect(() => {
+ const params = new URLSearchParams();
+ if (query.trim()) params.set('q', query.trim());
+ if (view === 'drafts') params.set('view', 'drafts');
+ const suffix = params.toString();
+ history.replace(`${location.pathname}${suffix ? `?${suffix}` : ''}`);
+}, [location.pathname, query, view]);
+```
+
+- [ ] Build draft membership exclusively from exact API-returned Workflow IDs and apply it before text search:
+
+```tsx
+const draftWorkflowIds = React.useMemo(
+ () => new Set((drafts.data ?? []).map((item) => item.workflowId)),
+ [drafts.data],
+);
+
+return [...merged.values()]
+ .filter((item) => view !== 'drafts' || draftWorkflowIds.has(item.workflowId))
+ .filter((item) => {
+ const normalized = query.trim().toLowerCase();
+ return (
+ !normalized ||
+ [item.name, item.description, item.workflowId].some((value) =>
+ value.toLowerCase().includes(normalized),
+ )
+ );
+ })
+ .sort(/* retain current updated-at ordering */);
+```
+
+- [ ] Add the two-option view Select. Disable only Drafts when the draft source failed; do not add Committed, Published, Failing, Ready, or inferred lifecycle values:
+
+```tsx
+
+```
+
+- [ ] Before ordinary empty rendering, show an honest unavailable state when `view === 'drafts' && drafts.isError`. Its Retry calls `drafts.refetch()` and must not re-request committed data or synthesize rows.
+
+- [ ] For a successful zero-row filtered result, render `No matching workflows`, the revised `Try a different search or filter.`, and a `Clear filters` button that calls `setQuery('')` and `setView('all')`. Preserve the original `No workflows yet` create action only for an unfiltered, successfully empty catalogue.
+
+- [ ] Add both locale catalogues:
+
+```ts
+'workflowActivityVNext.workflows.allView': 'All workflows',
+'workflowActivityVNext.workflows.clearFilters': 'Clear filters',
+'workflowActivityVNext.workflows.draftsUnavailable':
+ 'Draft workflows unavailable',
+'workflowActivityVNext.workflows.draftsUnavailableDescription':
+ 'Try again to load draft workflows.',
+'workflowActivityVNext.workflows.draftsView': 'Drafts',
+'workflowActivityVNext.workflows.viewFilter': 'Workflow view',
+```
+
+```ts
+'workflowActivityVNext.workflows.allView': '全部工作流',
+'workflowActivityVNext.workflows.clearFilters': '清除筛选',
+'workflowActivityVNext.workflows.draftsUnavailable': '草稿工作流不可用',
+'workflowActivityVNext.workflows.draftsUnavailableDescription':
+ '请重试加载草稿工作流。',
+'workflowActivityVNext.workflows.draftsView': '草稿',
+'workflowActivityVNext.workflows.viewFilter': '工作流视图',
+```
+
+- [ ] Run focused GREEN verification:
+
+```bash
+pnpm exec jest \
+ src/pages/workflow-activity-vnext/index.test.tsx \
+ src/locales/catalog.test.ts \
+ --runInBand
+```
+
+Expected: all selected tests pass; console output has no React act errors or unexpected network failures.
+
+## Task 3: Make Activity search URL-backed without changing API semantics
+
+**Files:**
+
+- Modify: `src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+- Modify: `src/pages/workflow-activity-vnext/activity/ActivityPage.tsx`
+
+- [ ] Import `fireEvent` and `history` in the test, then add a failing test that starts from all supported URL fields:
+
+```tsx
+mockSearch =
+ '?q=customer&status=failed&origin=draft&definition=definition-alpha&workflowFilter=unavailable';
+
+renderWithQueryClient();
+
+expect(screen.getByRole('searchbox', { name: 'Search runs' })).toHaveValue(
+ 'customer',
+);
+fireEvent.change(screen.getByRole('searchbox', { name: 'Search runs' }), {
+ target: { value: 'invoice' },
+});
+await waitFor(() =>
+ expect(history.replace).toHaveBeenLastCalledWith(
+ '/scopes/scope-alpha/workflow-activity-vnext/activity?q=invoice&status=failed&origin=draft&definition=definition-alpha&workflowFilter=unavailable',
+ ),
+);
+expect(mockListRuns).toHaveBeenLastCalledWith('scope-alpha', {
+ status: 'failed',
+ origins: ['draft'],
+ definitionActorIds: ['definition-alpha'],
+ take: 100,
+});
+```
+
+- [ ] Run RED and confirm failure is caused by the missing `q` restoration/serialization:
+
+```bash
+pnpm exec jest src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx --runInBand
+```
+
+- [ ] Initialize search from `initialParams`, restore it in the location effect, and serialize trimmed `q` alongside every existing parameter:
+
+```tsx
+const [search, setSearch] = React.useState(initialParams.get('q') ?? '');
+
+// location.search effect
+setSearch(params.get('q') ?? '');
+
+// URL serialization effect
+if (search.trim()) params.set('q', search.trim());
+```
+
+- [ ] Add `search` to the URL effect dependency list only. Keep the existing TanStack Query key and `workflowActivityApi.listRuns` payload unchanged so free text remains local filtering.
+
+- [ ] Run GREEN:
+
+```bash
+pnpm exec jest src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx --runInBand
+```
+
+Expected: all selected Activity tests pass and the API assertion contains only status, origins, definitionActorIds, and take.
+
+## Task 4: Align toolbar responsiveness
+
+**Files:**
+
+- Modify: `src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`
+- Modify: `src/pages/workflow-activity-vnext/activity/ActivityPage.tsx`
+- Modify: `src/pages/workflow-activity-vnext/styles.ts`
+- Test: `src/pages/workflow-activity-vnext/index.test.tsx`
+- Test: `src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+
+- [ ] Wrap each page's resource-specific Select controls in `wa-vnext__toolbar-filters`, use the same `wa-vnext__toolbar-search` class on both search inputs, and remove divergent inline widths.
+
+```tsx
+
+
+ {/* page-specific Select controls */}
+
+```
+
+- [ ] Add stable dimensions and mobile full-width behavior without changing other vNext tables or the shell:
+
+```css
+.wa-vnext__toolbar-search { flex: 0 1 360px; max-width: 100%; width: 360px; }
+.wa-vnext__toolbar-filters { justify-content: flex-end; }
+.wa-vnext__toolbar-filters .ant-select { min-width: 160px; }
+
+@media (max-width: 600px) {
+ .wa-vnext__toolbar-search { flex-basis: auto; width: 100%; }
+ .wa-vnext__toolbar-filters { display: grid; grid-template-columns: 1fr; width: 100%; }
+ .wa-vnext__toolbar-filters .ant-select,
+ .wa-vnext__toolbar-filters .ant-space-item,
+ .wa-vnext__toolbar-filters .ant-btn { width: 100%; }
+}
+```
+
+- [ ] Run the two page tests after the markup/style refactor:
+
+```bash
+pnpm exec jest \
+ src/pages/workflow-activity-vnext/index.test.tsx \
+ src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx \
+ --runInBand
+```
+
+Expected: both page test files pass.
+
+## Task 5: Focused verification, browser evidence, and delivery
+
+**Files:**
+
+- Verify only the files changed by Tasks 1-4 and this plan.
+
+- [ ] Run the exact selected test set:
+
+```bash
+pnpm exec jest \
+ src/pages/workflow-activity-vnext/index.test.tsx \
+ src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx \
+ src/locales/catalog.test.ts \
+ --runInBand
+```
+
+- [ ] Run dependency-selected tests:
+
+```bash
+pnpm exec jest --findRelatedTests \
+ src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx \
+ src/pages/workflow-activity-vnext/activity/ActivityPage.tsx \
+ src/pages/workflow-activity-vnext/styles.ts \
+ src/locales/workflowActivityVNextMessages.en-US.ts \
+ src/locales/workflowActivityVNextMessages.zh-CN.ts \
+ --runInBand
+```
+
+- [ ] Compute the frontend-only change scope:
+
+```bash
+python3 /Users/abigaildeng/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py \
+ --repo . --base HEAD
+```
+
+- [ ] Run Biome only on the analyzer's exact changed frontend files, for example:
+
+```bash
+pnpm exec biome check \
+ src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx \
+ src/pages/workflow-activity-vnext/activity/ActivityPage.tsx \
+ src/pages/workflow-activity-vnext/index.test.tsx \
+ src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx \
+ src/pages/workflow-activity-vnext/styles.ts \
+ src/locales/workflowActivityVNextMessages.en-US.ts \
+ src/locales/workflowActivityVNextMessages.zh-CN.ts
+```
+
+- [ ] Run whitespace validation:
+
+```bash
+git diff --check
+```
+
+- [ ] Do not run the full frontend test suite, package-wide lint, package-wide typecheck, or production build locally. Record that GitHub CI owns full verification under the machine-wide `frontend-incremental-pr` policy.
+
+- [ ] Verify the already-running real frontend at `http://localhost:5173` without mock data at desktop `1440x900`, tablet `834x1112`, and mobile `390x844`. Capture screenshots proving the resource-specific filters, header Refresh placement, URL restoration, responsive full-width controls, and lack of horizontal overflow. Record any real remote API or authentication gap instead of synthesizing success.
+
+- [ ] Review `git diff --stat`, `git diff`, and `git status --short`. Confirm every changed path is inside `apps/aevatar-console-web/`, no legacy route/auth/menu/API adapter changed, and no unsupported Workflow state or identity conversion was introduced.
+
+- [ ] Stage only this task's files, commit with:
+
+```bash
+git commit -m "Align Workflow and Activity filters"
+```
+
+- [ ] Push `feat/2026-08-04_workflow-activity-vnext-implementation` and update Draft PR #3189 with exact focused commands/results plus the CI-delegated full checks. Keep PR #3189 Draft. Do not change PR #3187, enable auto-merge, or merge either PR.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-add-node-sequential-link.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-add-node-sequential-link.md
new file mode 100644
index 0000000000..19e9b8dc97
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-add-node-sequential-link.md
@@ -0,0 +1,400 @@
+# Add Node Sequential Link Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make Workflow Activity vNext Add node extend the current flow with explicit sequential transitions instead of appending a disconnected step that triggers `implicit_next` warnings.
+
+**Architecture:** Add one pure document-normalization helper beside the existing insertion helpers, then compose it into only the Workflow Activity vNext `addNode` callback. The helper materializes runtime-defined adjacent ordering without changing explicit or branched transitions; `insertStepByType` remains responsible for rewiring the chosen predecessor and preserving its former successor.
+
+**Tech Stack:** React, TypeScript, Jest, Testing Library, pnpm.
+
+---
+
+## File Map
+
+- Create `apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-add-node-sequential-link.md`: executable TDD and delivery plan.
+- Modify `apps/aevatar-console-web/src/shared/studio/document.ts`: export a pure helper that turns eligible adjacent implicit transitions into explicit `next` values.
+- Modify `apps/aevatar-console-web/src/shared/studio/document.test.ts`: cover chain materialization, explicit and branched preservation, terminal behavior, and immutability.
+- Modify `apps/aevatar-console-web/src/pages/workflow-activity-vnext/hooks/useWorkflowEditor.ts`: normalize the document, resolve the insertion predecessor, and pass it to `insertStepByType`.
+- Modify `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`: exercise Add node through the page UI for append and selected-middle insertion.
+- Existing `apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-add-node-sequential-link-design.md`: approved behavior contract; no implementation edits expected.
+
+### Task 1: Specify The Pure Transition Materializer
+
+**Files:**
+
+- Modify: `apps/aevatar-console-web/src/shared/studio/document.test.ts`
+- Test: `apps/aevatar-console-web/src/shared/studio/document.test.ts`
+
+- [ ] **Step 1: Import the new helper and write the failing chain test**
+
+Add `materializeImplicitSequentialTransitions` to the import from `./document`, then add a test with three ordered steps: an implicit first step, a second step with an explicit successor, and a terminal third step. Assert that the first gains `next: 'review_step'`, the explicit second successor remains unchanged, and the input object still equals a deep snapshot.
+
+```ts
+it('materializes only eligible implicit sequential transitions without mutating the document', () => {
+ const document: StudioWorkflowDocument = {
+ name: 'workspace-demo',
+ roles: [],
+ steps: [
+ { id: 'draft_step', type: 'llm_call', next: null, branches: {} },
+ {
+ id: 'review_step',
+ type: 'human_approval',
+ next: 'publish_step',
+ branches: {},
+ },
+ { id: 'publish_step', type: 'emit', next: null, branches: {} },
+ ],
+ };
+ const snapshot = structuredClone(document);
+
+ const result = materializeImplicitSequentialTransitions(document);
+
+ expect(result.steps?.map((step) => step.next)).toEqual([
+ 'review_step',
+ 'publish_step',
+ null,
+ ]);
+ expect(document).toEqual(snapshot);
+ expect(result).not.toBe(document);
+});
+```
+
+- [ ] **Step 2: Write the failing branched and terminal test**
+
+Add a case where the first step has `branches: { approved: 'publish_step' }` and no `next`, followed by a second step. Assert the first step still has no linear successor and its branch remains byte-for-byte equivalent. In the same test group, assert empty and single-step documents keep no synthesized transition.
+
+```ts
+it('preserves branched transitions and terminal documents', () => {
+ const branched = materializeImplicitSequentialTransitions({
+ name: 'branched',
+ roles: [],
+ steps: [
+ {
+ id: 'approval_step',
+ type: 'human_approval',
+ next: null,
+ branches: { approved: 'publish_step' },
+ },
+ { id: 'publish_step', type: 'emit', next: null, branches: {} },
+ ],
+ });
+
+ expect(branched.steps?.[0]).toEqual(
+ expect.objectContaining({
+ next: null,
+ branches: { approved: 'publish_step' },
+ }),
+ );
+ expect(materializeImplicitSequentialTransitions({ steps: [] }).steps).toEqual([]);
+ expect(
+ materializeImplicitSequentialTransitions({
+ steps: [{ id: 'only_step', type: 'llm_call', next: null, branches: {} }],
+ }).steps?.[0]?.next,
+ ).toBeNull();
+
+ const firstInsertion = insertStepByType(
+ materializeImplicitSequentialTransitions({ steps: [] }),
+ 'assign',
+ );
+ expect(firstInsertion.document.steps).toEqual([
+ expect.objectContaining({ id: 'assign_step', next: null }),
+ ]);
+});
+```
+
+- [ ] **Step 3: Run the exact helper tests and verify RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/shared/studio/document.test.ts --testNamePattern 'materializes only eligible implicit sequential transitions|preserves branched transitions and terminal documents'
+```
+
+Expected: FAIL because `materializeImplicitSequentialTransitions` is not exported or defined.
+
+- [ ] **Step 4: Commit the failing tests only**
+
+```bash
+git add apps/aevatar-console-web/src/shared/studio/document.test.ts
+git commit -m "Test implicit workflow transition materialization"
+```
+
+### Task 2: Implement The Pure Transition Materializer
+
+**Files:**
+
+- Modify: `apps/aevatar-console-web/src/shared/studio/document.ts`
+- Test: `apps/aevatar-console-web/src/shared/studio/document.test.ts`
+
+- [ ] **Step 1: Implement the minimal pure helper**
+
+Place the exported helper before `insertStepByType`. Clone every step, inspect every non-final step, and only set `next` when the current `next` normalizes empty, the branch map has no entries, and the following sibling has a normalized ID.
+
+```ts
+export function materializeImplicitSequentialTransitions(
+ document: StudioWorkflowDocument,
+): StudioWorkflowDocument {
+ const steps = Array.isArray(document.steps) ? document.steps : [];
+ const nextSteps = steps.map((entry, index) => {
+ const step = { ...entry } as StudioWorkflowStepDocument;
+ const followingStepId = normalizeString(steps[index + 1]?.id);
+ const hasBranches = Object.keys(step.branches ?? {}).length > 0;
+
+ if (
+ index < steps.length - 1 &&
+ !normalizeString(step.next) &&
+ !hasBranches &&
+ followingStepId
+ ) {
+ return { ...step, next: followingStepId } satisfies StudioWorkflowStepDocument;
+ }
+
+ return step;
+ });
+
+ return { ...document, steps: nextSteps };
+}
+```
+
+- [ ] **Step 2: Run the exact helper tests and verify GREEN**
+
+Run the command from Task 1 Step 3.
+
+Expected: PASS for both focused tests.
+
+- [ ] **Step 3: Run all shared document helper tests**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/shared/studio/document.test.ts
+```
+
+Expected: all tests in `document.test.ts` pass.
+
+- [ ] **Step 4: Commit the helper implementation**
+
+```bash
+git add apps/aevatar-console-web/src/shared/studio/document.ts
+git commit -m "Materialize implicit workflow transitions"
+```
+
+### Task 3: Specify Add Node Placement Through The Page
+
+**Files:**
+
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [ ] **Step 1: Write the failing no-selection append test**
+
+Arrange `getWorkflow` with two implicit steps, make `serializeYaml` return its submitted document, click `Add node`, then `Insert Assign node`. Assert the serialized steps are `draft_step -> review_step -> assign_step`, with the inserted step terminal.
+
+```ts
+it('adds a node after the final step and materializes the existing implicit chain', async () => {
+ const document = {
+ name: 'committed_source',
+ roles: [],
+ steps: [
+ { id: 'draft_step', type: 'llm_call', next: null, branches: {} },
+ { id: 'review_step', type: 'human_approval', next: null, branches: {} },
+ ],
+ };
+ mockStudioApi.getWorkflow.mockResolvedValue({
+ workflowId: 'wf-committed-source',
+ name: 'Committed source',
+ fileName: 'committed-source.yaml',
+ filePath: '',
+ directoryId: '',
+ directoryLabel: '',
+ yaml: 'name: committed_source\nroles: []\nsteps: []\n',
+ updatedAtUtc: '2026-08-04T10:00:00Z',
+ document,
+ draftExists: false,
+ findings: [],
+ });
+ mockStudioApi.serializeYaml.mockImplementation(async ({ document: submitted }) => ({
+ yaml: 'serialized',
+ document: submitted,
+ findings: [],
+ }));
+
+ renderWithQueryClient();
+ fireEvent.click(await screen.findByRole('button', { name: 'Add node' }));
+ fireEvent.click(await screen.findByRole('button', { name: 'Insert Assign node' }));
+
+ await waitFor(() => expect(mockStudioApi.serializeYaml).toHaveBeenCalledTimes(1));
+ expect(mockStudioApi.serializeYaml.mock.calls[0][0].document.steps).toEqual([
+ expect.objectContaining({ id: 'draft_step', next: 'review_step' }),
+ expect.objectContaining({ id: 'review_step', next: 'assign_step' }),
+ expect.objectContaining({ id: 'assign_step', next: null }),
+ ]);
+});
+```
+
+- [ ] **Step 2: Write the failing selected-middle insertion test**
+
+Arrange an explicit three-step chain, click `Select step:review_step`, then use Add node to insert Assign. Assert serialization receives `draft_step -> review_step -> assign_step -> publish_step` and all unaffected transitions remain intact.
+
+```ts
+expect(mockStudioApi.serializeYaml.mock.calls[0][0].document.steps).toEqual([
+ expect.objectContaining({ id: 'draft_step', next: 'review_step' }),
+ expect.objectContaining({ id: 'review_step', next: 'assign_step' }),
+ expect.objectContaining({ id: 'assign_step', next: 'publish_step' }),
+ expect.objectContaining({ id: 'publish_step', next: null }),
+]);
+```
+
+- [ ] **Step 3: Run the exact page tests and verify RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx --testNamePattern 'adds a node after the final step and materializes the existing implicit chain|inserts a node after the selected middle step and preserves its successor'
+```
+
+Expected: FAIL because the current callback appends without `afterStepId` and does not materialize existing adjacency.
+
+- [ ] **Step 4: Commit the failing page tests only**
+
+```bash
+git add apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx
+git commit -m "Test Add node sequential placement"
+```
+
+### Task 4: Wire Explicit Placement Into Workflow Activity vNext
+
+**Files:**
+
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/hooks/useWorkflowEditor.ts`
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [ ] **Step 1: Import the materializer**
+
+```ts
+import {
+ applyStepInspectorDraft,
+ createStepInspectorDraft,
+ insertStepByType,
+ materializeImplicitSequentialTransitions,
+} from '@/shared/studio/document';
+```
+
+- [ ] **Step 2: Normalize the document and resolve the predecessor**
+
+Inside `addNode`, after obtaining `current`, materialize its implicit adjacency. Resolve a selected step only when `selectedNodeId` has the `step:` prefix; otherwise use the last step with a non-empty ID.
+
+```ts
+const explicitDocument = materializeImplicitSequentialTransitions(current);
+const selectedStepId = selectedNodeId.startsWith('step:')
+ ? selectedNodeId.slice('step:'.length).trim()
+ : '';
+const finalStepId = [...(explicitDocument.steps ?? [])]
+ .reverse()
+ .map((step) => String(step.id ?? '').trim())
+ .find(Boolean);
+const inserted = insertStepByType(explicitDocument, stepType, {
+ afterStepId: selectedStepId || finalStepId || null,
+});
+```
+
+Do not add warning filtering. Keep the existing serialization request, generation guard, selection update, retry state, and local-edit marking unchanged.
+
+- [ ] **Step 3: Add the selection dependency**
+
+```ts
+[document, markLocalEdit, parseCurrentYaml, selectedNodeId]
+```
+
+- [ ] **Step 4: Run the exact page tests and verify GREEN**
+
+Run the command from Task 3 Step 3.
+
+Expected: both new tests pass.
+
+- [ ] **Step 5: Run the existing insertion regression tests**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx --testNamePattern 'waits for a node insertion to finish before allowing a competing save|locks structural editing while a save is still in flight|keeps a failed node insertion visible and retryable'
+```
+
+Expected: all three existing insertion lifecycle tests pass.
+
+- [ ] **Step 6: Commit the vNext wiring**
+
+```bash
+git add apps/aevatar-console-web/src/pages/workflow-activity-vnext/hooks/useWorkflowEditor.ts
+git commit -m "Fix Add node sequential placement"
+```
+
+### Task 5: Focused Validation And Pull Request
+
+**Files:**
+
+- Verify all task files listed above.
+- Do not run package-wide test, lint, typecheck, or build commands locally.
+
+- [ ] **Step 1: Analyze the frontend change scope**
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base origin/feat/2026-08-04_workflow-activity-vnext
+```
+
+Expected: frontend-only scope limited to the Workflow Activity vNext editor, shared Studio document helper, focused tests, and docs.
+
+- [ ] **Step 2: Read the framework command guidance selected by the analyzer**
+
+Read `~/.codex/skills/frontend-incremental-pr/references/framework-commands.md` and select only changed-file static checks supported by this project. If no reliable affected typecheck exists, skip local typecheck and state that GitHub CI owns full verification.
+
+- [ ] **Step 3: Run focused tests together**
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/shared/studio/document.test.ts src/pages/workflow-activity-vnext/index.test.tsx
+```
+
+Expected: both changed test files pass.
+
+- [ ] **Step 4: Run mandatory test and baseline guards**
+
+```bash
+bash tools/ci/test_stability_guards.sh
+python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py
+git diff --check origin/feat/2026-08-04_workflow-activity-vnext...HEAD
+```
+
+Expected: all commands exit 0 and the baseline digest remains unchanged.
+
+- [ ] **Step 5: Review the complete diff and repository state**
+
+```bash
+git status --short --branch
+git diff --stat origin/feat/2026-08-04_workflow-activity-vnext...HEAD
+git diff origin/feat/2026-08-04_workflow-activity-vnext...HEAD -- apps/aevatar-console-web/src/shared/studio/document.ts apps/aevatar-console-web/src/shared/studio/document.test.ts apps/aevatar-console-web/src/pages/workflow-activity-vnext/hooks/useWorkflowEditor.ts apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-add-node-sequential-link-design.md apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-add-node-sequential-link.md
+```
+
+Expected: no unrelated file changes, no generated artifacts, and the implementation matches the approved design.
+
+- [ ] **Step 6: Commit the implementation plan and any final focused corrections**
+
+```bash
+git add apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-add-node-sequential-link.md
+git commit -m "Document Add node implementation plan"
+```
+
+- [ ] **Step 7: Push and open the pull request against the required base**
+
+```bash
+git push -u origin fix/2026-08-06_add-node-sequential-link
+gh pr create --base feat/2026-08-04_workflow-activity-vnext --head fix/2026-08-06_add-node-sequential-link --title "Fix Add node sequential placement" --body-file
+```
+
+The PR body must include the root cause, explicit transition solution, affected paths, exact focused commands and results, unchanged vNext design baseline declaration, and this statement:
+
+```text
+Full frontend suite/build: deferred to GitHub CI by personal local workflow policy.
+```
+
+Do not wait for CI after the PR is created unless the user asks.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-new-workflow-creation-ux.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-new-workflow-creation-ux.md
new file mode 100644
index 0000000000..c8bb56077a
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-new-workflow-creation-ux.md
@@ -0,0 +1,676 @@
+# New Workflow Creation UX Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make every New workflow method a focused, single-action path that hides non-decisions, preserves backend truth, and opens the common editor after authoritative draft creation.
+
+**Architecture:** Keep route-owned form state in `NewWorkflowPage`, extract deterministic unique-file-name resolution into `workflowCreation.ts`, and preserve the existing typed Studio API plus `useDraftMaterialization` boundaries. The page derives method-specific names, selects the only directory automatically, renders a directory selector only for multiple choices, and runs generation/parse/create as distinct recovery-aware stages behind one user command.
+
+**Tech Stack:** React 19, TypeScript, Ant Design 6, TanStack Query, Umi localization, Jest, Testing Library, Biome.
+
+---
+
+### Task 1: Protect Hidden Save Target And File Name Resolution
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/workflowCreation.ts`
+
+- [ ] **Step 1: Extend the page-test boundary mocks**
+
+Add the two existing read boundaries used by `NewWorkflowPage`:
+
+```tsx
+jest.mock('@/shared/api/scopesApi', () => ({
+ scopesApi: { listWorkflows: jest.fn() },
+}));
+
+studioApi: {
+ authorWorkflow: jest.fn(),
+ createWorkflowDraft: jest.fn(),
+ getWorkspaceSettings: jest.fn(),
+ listWorkflowDrafts: jest.fn(),
+ parseYaml: jest.fn(),
+}
+```
+
+Reset `listWorkflowDrafts` and `listWorkflows` to empty successful arrays in
+`beforeEach` so every test has authoritative collection state.
+
+Define the shared materialized response used by the creation cases:
+
+```tsx
+const materializedWorkflow = {
+ kind: 'materialized',
+ workflow: {
+ directoryId: 'directory-alpha',
+ directoryLabel: 'Workflows',
+ document: { name: 'incident_review', roles: [], steps: [] },
+ draftExists: true,
+ fileName: 'incident-review.yaml',
+ filePath: '/workflows/incident-review.yaml',
+ findings: [],
+ name: 'Incident review',
+ updatedAtUtc: '2026-08-06T10:00:00Z',
+ workflowId: 'wf-created-alpha',
+ yaml: 'name: incident_review\nroles: []\nsteps: []\n',
+ },
+} as const;
+```
+
+- [ ] **Step 2: Write failing save-target and filename tests**
+
+Add route-level tests that prove one directory is hidden, two directories are
+selectable, and an occupied YAML filename receives the first free suffix:
+
+```tsx
+it('hides the only save target while still using its directory id', async () => {
+ mockStudioApi.createWorkflowDraft.mockResolvedValue(materializedWorkflow);
+ renderWithQueryClient();
+
+ fireEvent.click(await screen.findByRole('button', { name: 'Start blank' }));
+ expect(screen.queryByLabelText('Save to')).not.toBeInTheDocument();
+ fireEvent.change(screen.getByLabelText('Workflow name'), {
+ target: { value: 'Incident review' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Create and open' }));
+
+ await waitFor(() =>
+ expect(mockStudioApi.createWorkflowDraft).toHaveBeenCalledWith(
+ expect.objectContaining({ directoryId: 'directory-alpha' }),
+ ),
+ );
+});
+
+it('shows Save to only when the workspace has multiple directories', async () => {
+ mockStudioApi.getWorkspaceSettings.mockResolvedValue({
+ runtimeBaseUrl: '',
+ directories: [
+ readyWorkspace.directories[0],
+ {
+ directoryId: 'directory-beta',
+ isBuiltIn: false,
+ label: 'Operations',
+ path: '/operations',
+ },
+ ],
+ });
+ renderWithQueryClient();
+
+ fireEvent.click(await screen.findByRole('button', { name: 'Start blank' }));
+ fireEvent.mouseDown(screen.getByLabelText('Save to'));
+ fireEvent.click(await screen.findByText('Operations'));
+ fireEvent.change(screen.getByLabelText('Workflow name'), {
+ target: { value: 'Incident review' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Create and open' }));
+
+ await waitFor(() =>
+ expect(mockStudioApi.createWorkflowDraft).toHaveBeenCalledWith(
+ expect.objectContaining({ directoryId: 'directory-beta' }),
+ ),
+ );
+});
+
+it('uses the first available YAML filename without changing the display name', async () => {
+ mockStudioApi.listWorkflowDrafts.mockResolvedValue([
+ { directoryId: 'directory-alpha', fileName: 'incident-review.yaml', name: 'Other' },
+ { directoryId: 'directory-alpha', fileName: 'incident-review-2.yaml', name: 'Other 2' },
+ ]);
+ mockStudioApi.createWorkflowDraft.mockResolvedValue(materializedWorkflow);
+ renderWithQueryClient();
+
+ fireEvent.click(await screen.findByRole('button', { name: 'Start blank' }));
+ fireEvent.change(screen.getByLabelText('Workflow name'), {
+ target: { value: 'Incident review' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Create and open' }));
+
+ await waitFor(() =>
+ expect(mockStudioApi.createWorkflowDraft).toHaveBeenCalledWith(
+ expect.objectContaining({
+ fileName: 'incident-review-3.yaml',
+ workflowName: 'Incident review',
+ }),
+ ),
+ );
+});
+```
+
+- [ ] **Step 3: Run the focused test file and confirm RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx
+```
+
+Expected: FAIL because the single selector is still visible, the multiple-only
+label/action copy does not exist, and filename suffix resolution is absent.
+
+- [ ] **Step 4: Implement deterministic filename resolution**
+
+Add this public helper to `workflowCreation.ts`:
+
+```ts
+export function resolveAvailableWorkflowFileName(
+ name: string,
+ directoryId: string,
+ drafts: readonly { readonly directoryId: string; readonly fileName: string }[],
+): string {
+ const preferred = slugifyWorkflowFileName(name);
+ const extensionIndex = preferred.toLowerCase().lastIndexOf('.yaml');
+ const stem = extensionIndex >= 0 ? preferred.slice(0, extensionIndex) : preferred;
+ const occupied = new Set(
+ drafts
+ .filter((draft) => draft.directoryId === directoryId)
+ .map((draft) => draft.fileName.trim().toLocaleLowerCase()),
+ );
+ if (!occupied.has(preferred.toLocaleLowerCase())) return preferred;
+
+ let suffix = 2;
+ while (occupied.has(`${stem}-${suffix}.yaml`.toLocaleLowerCase())) suffix += 1;
+ return `${stem}-${suffix}.yaml`;
+}
+```
+
+Use this helper when building every draft-create request. The backend remains
+the final conflict authority when another client wins a concurrent create.
+
+- [ ] **Step 5: Render a selector only for multiple directories**
+
+Replace the unconditional `Save location` block with:
+
+```tsx
+{(workspace.data?.directories.length ?? 0) > 1 ? (
+
+) : null}
+```
+
+Keep the existing effect that adopts the first real directory. Do not derive a
+directory locally when the query is pending, failed, or empty.
+
+- [ ] **Step 6: Run the focused test file and confirm GREEN**
+
+Run the Step 3 command again.
+
+Expected: PASS for the new save-target and filename behaviors; existing
+recovery tests remain green.
+
+- [ ] **Step 7: Commit the focused behavior**
+
+```bash
+git add apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/workflowCreation.ts apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.tsx
+git commit -m "Hide automatic workflow save targets"
+```
+
+### Task 2: Collapse Describe Into One Authoritative Action
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.tsx`
+
+- [ ] **Step 1: Write the failing Describe integration test**
+
+```tsx
+it('generates, validates, creates, and opens a described workflow with one action', async () => {
+ mockStudioApi.authorWorkflow.mockResolvedValue(
+ 'name: weekly_feedback\nroles: []\nsteps:\n - id: summarize\n type: llm_call\n',
+ );
+ mockStudioApi.parseYaml.mockResolvedValue({
+ document: { name: 'weekly_feedback', roles: [], steps: [{ id: 'summarize', type: 'llm_call' }] },
+ findings: [],
+ });
+ mockStudioApi.createWorkflowDraft.mockResolvedValue(materializedWorkflow);
+ renderWithQueryClient();
+
+ fireEvent.click(await screen.findByRole('button', { name: 'Describe' }));
+ fireEvent.change(screen.getByLabelText('Workflow name'), {
+ target: { value: 'Weekly feedback' },
+ });
+ expect(screen.queryByLabelText('Generated YAML')).not.toBeInTheDocument();
+ fireEvent.change(screen.getByLabelText('What should this workflow do?'), {
+ target: { value: 'Summarize weekly customer feedback' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Generate and open' }));
+
+ await waitFor(() => expect(mockStudioApi.createWorkflowDraft).toHaveBeenCalledTimes(1));
+ expect(mockStudioApi.authorWorkflow).toHaveBeenCalledWith(
+ { prompt: 'Summarize weekly customer feedback' },
+ expect.objectContaining({ onText: expect.any(Function) }),
+ );
+ expect(mockStudioApi.createWorkflowDraft).toHaveBeenCalledWith(
+ expect.objectContaining({ workflowName: 'Weekly feedback' }),
+ );
+ expect(history.push).toHaveBeenCalledWith(
+ '/scopes/scope-alpha/workflow-activity-vnext/workflows/wf-created-alpha',
+ );
+});
+```
+
+Add a failure case that rejects `createWorkflowDraft`, confirms the prompt
+value remains visible, and confirms another click retries the single action.
+
+- [ ] **Step 2: Run the focused test and confirm RED**
+
+Run the Task 1 Step 3 command.
+
+Expected: FAIL because Describe still renders Workflow name, Automation goal,
+Generate workflow, generated YAML, and a second Create workflow action.
+
+- [ ] **Step 3: Replace the preview flow with one submit pipeline**
+
+Remove `generatedYaml` and `generatedReady`. Implement one handler with clear
+stage boundaries:
+
+```ts
+const createFromDescription = async () => {
+ const normalizedPrompt = prompt.trim();
+ if (!normalizedPrompt || submitting || !directoryId) return;
+ setSubmitting(true);
+ setFailure('');
+ setFindings([]);
+ try {
+ const generated = await studioApi.authorWorkflow(
+ { prompt: normalizedPrompt },
+ { onText: () => undefined },
+ );
+ const parsed = await studioApi.parseYaml({ yaml: generated });
+ setFindings(parsed.findings);
+ if (hasBlockingFindings(parsed.document, parsed.findings)) return;
+ await persist(generated, name.trim(), { manageSubmitting: false });
+ } catch (error) {
+ setFailure(errorMessage(error));
+ } finally {
+ setSubmitting(false);
+ }
+};
+```
+
+Adapt `persist` so composed flows do not toggle `submitting` off between parse
+and create. Keep the materialized/accepted handling in `finishSave` unchanged.
+
+- [ ] **Step 4: Render the focused Describe surface**
+
+Use `Describe your workflow`, the supporting sentence, a Workflow name input,
+a labeled textarea, and one primary button:
+
+```tsx
+
+
+```
+
+- [ ] **Step 5: Run the focused test and confirm GREEN**
+
+Run the Task 1 Step 3 command.
+
+Expected: PASS with one Describe action and preserved existing save-target
+recovery behavior.
+
+- [ ] **Step 6: Commit Describe behavior**
+
+```bash
+git add apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.tsx
+git commit -m "Streamline described workflow creation"
+```
+
+### Task 3: Align Blank, Import, And Template Creation
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.tsx`
+
+- [ ] **Step 1: Write failing method-specific tests**
+
+Protect the final control contract:
+
+```tsx
+it('imports parsed YAML without asking for a second workflow name', async () => {
+ mockStudioApi.parseYaml.mockResolvedValue({
+ document: { name: 'imported_orders', roles: [], steps: [] },
+ findings: [],
+ });
+ mockStudioApi.createWorkflowDraft.mockResolvedValue(materializedWorkflow);
+ renderWithQueryClient();
+
+ fireEvent.click(await screen.findByRole('button', { name: 'Import YAML' }));
+ expect(screen.queryByLabelText('Workflow name')).not.toBeInTheDocument();
+ fireEvent.change(screen.getByLabelText('Workflow YAML'), {
+ target: { value: 'name: imported_orders\nroles: []\nsteps: []\n' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Import and open' }));
+
+ await waitFor(() =>
+ expect(mockStudioApi.createWorkflowDraft).toHaveBeenCalledWith(
+ expect.objectContaining({ workflowName: 'imported_orders' }),
+ ),
+ );
+});
+
+it('creates an independent template copy with one action', async () => {
+ mockStudioApi.parseYaml.mockResolvedValue({
+ document: { name: 'incident_triage', roles: [], steps: [] },
+ findings: [],
+ });
+ mockStudioApi.createWorkflowDraft.mockResolvedValue(materializedWorkflow);
+ renderWithQueryClient();
+
+ fireEvent.click(await screen.findByRole('button', { name: 'Use template' }));
+ expect(screen.queryByLabelText('Workflow name')).not.toBeInTheDocument();
+ fireEvent.click(screen.getByRole('button', { name: 'Use template and open' }));
+
+ await waitFor(() =>
+ expect(mockStudioApi.createWorkflowDraft).toHaveBeenCalledWith(
+ expect.objectContaining({ workflowName: 'Incident triage copy' }),
+ ),
+ );
+});
+```
+
+Update the existing `index.test.tsx` creation assertions to the new accessible
+labels and action names without duplicating the new focused page coverage.
+
+- [ ] **Step 2: Run both owning test files and confirm RED**
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx src/pages/workflow-activity-vnext/index.test.tsx
+```
+
+Expected: FAIL on old name fields and old action labels.
+
+- [ ] **Step 3: Implement method-specific name derivation**
+
+- Blank continues to require `Workflow name` and uses `Create and open`.
+- Describe requires `Workflow name`, uses it independently of the generated
+ YAML document name, and completes through `Generate and open`.
+- Import parses first, rejects a missing parsed name with localized actionable
+ copy, then persists using that parsed name and `Import and open`.
+- Template copies the selected YAML, parses it, and persists with the localized
+ display name `${templateName} copy` through `Use template and open`.
+- Import and Template do not render the shared Workflow name field.
+
+Keep the server parser call for template YAML so backend field naming and
+validation remain authoritative.
+
+- [ ] **Step 4: Run both owning test files and confirm GREEN**
+
+Run the Step 2 command.
+
+Expected: PASS for all creation methods, invalid YAML preservation, method
+switch recovery, and existing materialization behavior.
+
+- [ ] **Step 5: Run the polling stability guard**
+
+```bash
+bash tools/ci/test_stability_guards.sh
+```
+
+Expected: PASS; the changed tests introduce no arbitrary delay or polling
+helper.
+
+- [ ] **Step 6: Commit all creation paths**
+
+```bash
+git add apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.tsx
+git commit -m "Align workflow creation methods"
+```
+
+### Task 4: Restore Visual Hierarchy And Localized Copy
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+- Test: `apps/aevatar-console-web/src/locales/catalog.test.ts`
+
+- [ ] **Step 1: Add the new messages to both locale catalogues**
+
+Use equivalent English and Chinese product copy for:
+
+```ts
+'workflowActivityVNext.new.describeTitle': 'Describe your workflow',
+'workflowActivityVNext.new.describeDescription':
+ 'Start with the outcome. You can review every generated step in the editor.',
+'workflowActivityVNext.new.goal': 'What should this workflow do?',
+'workflowActivityVNext.new.generateAndOpen': 'Generate and open',
+'workflowActivityVNext.new.createAndOpen': 'Create and open',
+'workflowActivityVNext.new.importAndOpen': 'Import and open',
+'workflowActivityVNext.new.templateAndOpen': 'Use template and open',
+'workflowActivityVNext.new.directory': 'Save to',
+'workflowActivityVNext.new.importedNameMissing':
+ 'The imported workflow needs a name before it can be created.',
+```
+
+Remove creation-page-only obsolete messages after `rg` confirms they have no
+remaining consumers: `generatedYaml`, `generate`, `createGenerated`, and
+`defaultWorkspace`.
+
+- [ ] **Step 2: Replace the full-width panel with a bounded creation column**
+
+Add scoped primitives in `styles.ts`:
+
+```css
+.wa-vnext__creation-surface {
+ margin: 0 auto;
+ max-width: 760px;
+ min-width: 0;
+ padding: 8px 0 40px;
+}
+.wa-vnext__creation-heading {
+ border-bottom: 1px solid var(--wa-line);
+ margin-bottom: 22px;
+ padding-bottom: 18px;
+}
+.wa-vnext__creation-heading h2 {
+ font-size: 22px;
+ line-height: 28px;
+ margin: 0;
+}
+.wa-vnext__creation-heading p {
+ color: var(--wa-muted);
+ font-size: 12px;
+ line-height: 18px;
+ margin: 6px 0 0;
+ max-width: 620px;
+}
+.wa-vnext__creation-form {
+ display: grid;
+ gap: 18px;
+}
+.wa-vnext__creation-field {
+ display: grid;
+ font-size: 12px;
+ font-weight: 700;
+ gap: 7px;
+}
+.wa-vnext__creation-actions {
+ align-items: center;
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+}
+```
+
+At mobile width, make actions a single-column grid with full-width buttons.
+Keep method cards, alerts, and materialization notice stable and outside any
+nested card.
+
+- [ ] **Step 3: Run localized catalogue verification**
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/locales/catalog.test.ts
+```
+
+Expected: PASS with matching message IDs and interpolation variables.
+
+- [ ] **Step 4: Run changed-file formatting and lint checks**
+
+Run the frontend scope analyzer first and pass only its `staticCheckFiles` to
+Biome:
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base feat/2026-08-04_workflow-activity-vnext
+pnpm --dir apps/aevatar-console-web exec biome check --write src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.tsx src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx src/pages/workflow-activity-vnext/workflows/workflowCreation.ts src/pages/workflow-activity-vnext/index.test.tsx src/pages/workflow-activity-vnext/styles.ts src/locales/workflowActivityVNextMessages.en-US.ts src/locales/workflowActivityVNextMessages.zh-CN.ts
+```
+
+Expected: Biome exits 0 and only formats the explicit task files. Do not run a
+package lint, full typecheck, or production build.
+
+- [ ] **Step 5: Commit visual and copy changes**
+
+```bash
+git add apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts
+git commit -m "Refine new workflow creation layout"
+```
+
+### Task 5: Focused Verification And Pull Request Delivery
+
+**Files:**
+- Review every file changed from `feat/2026-08-04_workflow-activity-vnext`
+- Update the open PR body after push
+
+- [ ] **Step 1: Run the scope analyzer**
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base feat/2026-08-04_workflow-activity-vnext
+```
+
+Record affected packages, test runner, changed tests, and `staticCheckFiles`.
+
+- [ ] **Step 2: Run only dependency-related and explicitly changed tests**
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --findRelatedTests src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.tsx src/pages/workflow-activity-vnext/workflows/workflowCreation.ts src/pages/workflow-activity-vnext/styles.ts --runInBand
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx src/pages/workflow-activity-vnext/index.test.tsx src/locales/catalog.test.ts
+```
+
+If `--findRelatedTests` selects an unexpectedly broad set, stop it and use the
+three explicit test files instead. Do not run the complete frontend suite.
+
+- [ ] **Step 3: Run required non-frontend guards**
+
+```bash
+bash tools/ci/test_stability_guards.sh
+git diff --check feat/2026-08-04_workflow-activity-vnext...HEAD
+```
+
+Expected: PASS.
+
+- [ ] **Step 4: Perform browser visual verification**
+
+Start the existing development server on a free port with the worktree's
+configured auth/backend origins kept consistent. Verify desktop and mobile
+screens for the chooser, Describe, blank, import, template, loading, failed,
+and multiple-directory states. Confirm no overlap, horizontal escape, hidden
+primary action, or blank canvas. Capture screenshots as review evidence.
+
+If the authenticated backend cannot be reached, use the existing deterministic
+component tests for state evidence and report the browser gap. Do not add mock
+production data or alter auth behavior to obtain a screenshot.
+
+- [ ] **Step 5: Review the complete diff and commit any verification-only fixes**
+
+```bash
+git status --short
+git diff --stat feat/2026-08-04_workflow-activity-vnext...HEAD
+git diff feat/2026-08-04_workflow-activity-vnext...HEAD -- apps/aevatar-console-web/src/pages/workflow-activity-vnext apps/aevatar-console-web/src/locales apps/aevatar-console-web/docs/superpowers
+```
+
+Stage only task files and use an imperative commit message if verification
+requires a source adjustment.
+
+- [ ] **Step 6: Push and create the incremental PR**
+
+Create `/tmp/aevatar-new-workflow-creation-ux-pr.md` with the final observed
+command results substituted into this exact structure:
+
+```markdown
+## Problem and solution
+
+The New workflow route exposed its only workspace directory as a user choice,
+used an unclear Automation goal label, required a second create confirmation
+after generation, and stretched short creation forms across the work area.
+
+This PR hides the automatic directory, shows Save to only for multiple real
+directories, gives every creation method one final action, derives generated
+and imported names from validated documents, prevents YAML path collisions,
+and restores a bounded creation layout.
+
+## Affected paths
+
+- Workflow Activity vNext New workflow route and creation helpers
+- Focused route and component integration tests
+- Workflow Activity vNext English and Chinese messages
+- New workflow creation UX design and implementation documents
+
+## Local verification
+
+- Related tests: focused `NewWorkflowPage.test.tsx`, `index.test.tsx`, and `catalog.test.ts` Jest commands listed above - PASS
+- Changed-file static checks: scope-analyzer `staticCheckFiles` passed to `biome check` - PASS
+- Test stability guard: `bash tools/ci/test_stability_guards.sh` - PASS
+- Full frontend suite/build: deferred to GitHub CI by personal local workflow policy
+
+## Design baseline
+
+Design baseline: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/`
+
+Primary design: `aevatar-workflow-activity-vnext.excalidraw`
+
+Design SHA-256: `30e74d7b410ae72c4c91432355436679033679c54c10b1702908435b001577de`
+
+Contract specification: `apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-design.md`
+
+User paths: `apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-user-paths.md`
+
+Authentication and localization: existing Aevatar login, callback, session,
+returnTo, and Umi locale logic; presentation may change, behavior may not.
+
+Production data source: real APIs and API-acknowledged user actions only; no
+mock fallback.
+
+Baseline integrity: `python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py`
+```
+
+If an executed command differs from the planned command or does not pass,
+replace the corresponding verification line with the exact observed command
+and result; do not add unverified claims.
+
+```bash
+git push -u origin fix/2026-08-06_new-workflow-creation-ux
+gh pr create --base feat/2026-08-04_workflow-activity-vnext --head fix/2026-08-06_new-workflow-creation-ux --title "Fix new workflow creation UX" --body-file /tmp/aevatar-new-workflow-creation-ux-pr.md
+```
+
+The PR body must include problem and solution, affected paths, the required
+Workflow Activity vNext baseline declaration, exact local commands/results,
+and this statement:
+
+```text
+Full frontend suite/build: deferred to GitHub CI by personal local workflow policy.
+```
+
+Do not wait for CI after creating the PR.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-settings-save-action-dock.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-settings-save-action-dock.md
new file mode 100644
index 0000000000..117bdf8768
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-settings-save-action-dock.md
@@ -0,0 +1,340 @@
+# Settings Save Action Dock Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Keep dirty Workflow Activity vNext Settings save actions stationary at the bottom of the main work area without covering form content or changing save semantics.
+
+**Architecture:** Add an optional footer slot to `WorkflowActivityVNextShell`. Only routes providing that slot split the main column into an independently scrolling body and a natural-height footer; `SettingsPage` provides the existing dirty actions through the slot and keeps all form/save state local.
+
+**Tech Stack:** React 19, TypeScript, Umi Max, Ant Design, Jest, Testing Library, CSS template strings, Biome.
+
+---
+
+### Task 1: Protect The Shell-Owned Dirty Action Contract
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [x] **Step 1: Add a focused route integration test**
+
+Add a test in the existing `Workflow Activity vNext settings` describe block. Use distinct authoritative service data, edit Preferred service through the rendered select, and assert the accessible ownership contract:
+
+```tsx
+it('keeps dirty save actions outside the scrolling AI defaults panel', async () => {
+ mockStudioApi.getUserLlmSettings.mockResolvedValue({
+ savedSelection: null,
+ savedRouteLabel: 'System default',
+ selectionStatus: 'system_default',
+ catalogDiagnostic: 'unspecified',
+ remediation: 'none',
+ catalogStatus: 'ready',
+ capabilities: {
+ canEditRoute: true,
+ canEditModel: true,
+ canSave: true,
+ canRetryCatalog: true,
+ },
+ routeOptions: [
+ {
+ routeValue: '/api/v1/proxy/s/service-alpha',
+ label: 'Service alpha',
+ source: 'user_service',
+ status: 'ready',
+ allowed: true,
+ ready: true,
+ userServiceId: 'us-alpha',
+ serviceSlug: 'service-alpha',
+ modelCatalog: {
+ certainty: 'enumerated',
+ modelIds: ['model-alpha'],
+ defaultModelId: 'model-alpha',
+ diagnostic: 'unspecified',
+ },
+ description: null,
+ },
+ ],
+ modelGroupsByRoute: [],
+ });
+
+ renderWithQueryClient();
+
+ expect(
+ screen.queryByRole('region', { name: 'Unsaved settings actions' }),
+ ).not.toBeInTheDocument();
+ fireEvent.mouseDown(
+ await screen.findByRole('combobox', { name: 'Preferred service' }),
+ );
+ fireEvent.click(await screen.findByText('Service alpha'));
+
+ const aiDefaultsPanel = screen.getByRole('region', {
+ name: 'AI defaults',
+ });
+ const saveActions = screen.getByRole('region', {
+ name: 'Unsaved settings actions',
+ });
+ expect(aiDefaultsPanel).not.toContainElement(saveActions);
+ expect(
+ within(saveActions).getByRole('button', { name: 'Save changes' }),
+ ).toBeEnabled();
+
+ fireEvent.click(
+ within(saveActions).getByRole('button', {
+ name: 'Restore saved settings',
+ }),
+ );
+ expect(
+ screen.queryByRole('region', { name: 'Unsaved settings actions' }),
+ ).not.toBeInTheDocument();
+ expect(mockStudioApi.saveUserLlmSettings).not.toHaveBeenCalled();
+});
+```
+
+Reuse the file's existing `within` import from Testing Library.
+
+- [x] **Step 2: Run the exact test and verify RED**
+
+Run from the repository root:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest \
+ --runInBand \
+ --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx \
+ --testNamePattern 'keeps dirty save actions outside the scrolling AI defaults panel'
+```
+
+Expected: FAIL because no region named `Unsaved settings actions` exists and the current action bar remains inside the AI defaults form.
+
+### Task 2: Add The Optional Shell Footer And Move The Actions
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/WorkflowActivityVNextShell.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/settings/SettingsPage.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+
+- [x] **Step 1: Add a route-neutral shell footer slot**
+
+Extend `ShellProps` with:
+
+```tsx
+readonly footer?: React.ReactNode;
+```
+
+Build the existing header and content as `mainBody`. When `footer` is absent,
+render the current main DOM and scrolling unchanged. When present, render:
+
+```tsx
+
+ {footer ? (
+
{mainBody}
+ ) : (
+ mainBody
+ )}
+ {footer ?
{footer}
: null}
+
+```
+
+Do not add the wrapper on routes without a footer.
+
+- [x] **Step 2: Move the action surface out of `aiPanel`**
+
+Delete the dirty action JSX from the `wa-vnext__form`. Create one route-owned
+action region using the same handlers and state:
+
+```tsx
+const settingsFooter = (
+ <>
+ {dirty ? (
+
+
+ {/* Existing status copy and Restore / Save controls. */}
+
+
+ ) : null}
+ >
+);
+```
+
+Pass `footer={settingsFooter}` to `WorkflowActivityVNextShell`. The fragment is
+always present so toggling dirty state does not replace the shell's scroll
+subtree. Keep save, discard, accepted-to-observed, error, delayed, and
+navigation logic unchanged.
+
+- [x] **Step 3: Add synchronized locale messages**
+
+Add to the English catalogue:
+
+```ts
+'workflowActivityVNext.settings.unsavedActionsAria':
+ 'Unsaved settings actions',
+```
+
+Add to the Chinese catalogue:
+
+```ts
+'workflowActivityVNext.settings.unsavedActionsAria': '未保存设置操作',
+```
+
+- [x] **Step 4: Implement stable scroll ownership and responsive sizing**
+
+Add the shell contract:
+
+```css
+.wa-vnext__main--with-footer {
+ display: grid;
+ grid-template-rows: minmax(0, 1fr) auto;
+ overflow: hidden;
+}
+.wa-vnext__main-scroll {
+ min-height: 0;
+ min-width: 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+ overscroll-behavior: contain;
+}
+.wa-vnext__main-footer { min-width: 0; }
+.wa-vnext__settings-footer {
+ background: var(--wa-surface);
+ padding: 0 40px max(12px, env(safe-area-inset-bottom));
+}
+```
+
+Change the save bar to normal layout inside the shell footer: remove
+`position`, `bottom`, and `margin-top`; add `margin: 0 auto` and
+`max-width: 1120px`. Use 32 px footer gutters below 1100 px and 16 px below
+768 px. Preserve the current stacked mobile copy/actions at 600 px, and at
+360 px change the action grid to one column. The dock must never overlay the
+scroll region.
+
+- [x] **Step 5: Run the exact test and verify GREEN**
+
+Run the Task 1 Jest command again.
+
+Expected: PASS with one test and no warning or console error.
+
+- [x] **Step 6: Run dependency-related route coverage**
+
+Run the complete colocated route file because shell composition is shared by
+all vNext child routes:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest \
+ --runInBand \
+ --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx
+```
+
+Expected: PASS for every test in that file.
+
+### Task 3: Align Normative Documentation And Verify The Change
+
+**Files:**
+- Modify: `apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-settings-save-action-dock-design.md`
+- Modify: `apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-design.md`
+- Modify: `apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-user-paths.md`
+- Modify: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/README.md`
+- Modify: `apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-settings-save-action-dock.md`
+
+- [x] **Step 1: Record the approved and implemented contract**
+
+Mark the new design approved for implementation, then implemented after the
+focused checks pass. Replace the two sticky references at lines 520 and 666 of
+the original design, the dirty-step reference at line 674 of the user paths,
+and the two prototype-description references at lines 146 and 254 of the
+baseline README with `shell-fixed Restore and Save changes dock` language.
+Keep the imported Excalidraw, generator, PNGs, and prototype byte-identical;
+document the required production deviation instead of editing those source
+assets.
+
+- [x] **Step 2: Run the frontend scope analyzer**
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py \
+ --repo . \
+ --base origin/feat/2026-08-04_workflow-activity-vnext
+```
+
+Read the JSON. Run every changed test explicitly and dependency-related Jest
+tests for changed source files. Pass only reported `staticCheckFiles` to Biome.
+Do not run a full local frontend test, lint, typecheck, or production build.
+
+- [x] **Step 3: Run documentation and repository hygiene checks**
+
+```bash
+python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py
+bash tools/docs/lint.sh
+bash tools/ci/test_stability_guards.sh
+git diff --check
+```
+
+Expected: baseline SHA matches, 17/17 frames pass, docs lint passes, test
+stability guard passes, and no whitespace error is reported.
+
+- [x] **Step 4: Verify real layout behavior in the existing browser**
+
+Start the worktree frontend on a free non-5000/non-5050 port with matching
+local OAuth origin configuration and no mock data. At 1440 x 900, 834 x 1112,
+and 390 x 844, verify:
+
+- the dirty dock stays stationary while Settings content scrolls;
+- the last field and durable alerts can scroll fully above the dock;
+- the dock never enters the navigation rail;
+- actions remain reachable and do not overlap;
+- mobile has no page-level horizontal overflow and includes safe-area spacing.
+
+Capture screenshots as task evidence. If real authenticated backend state is
+unavailable, report that exact residual gap and do not inject production mock
+data.
+
+Result: the real application ran on port 5187 with `MOCK=none`, but that origin
+had no authenticated session and the registered NyxID callback port 5173 was
+owned by another process. No session or Settings data was mocked. Authenticated
+responsive screenshots remain a preview-environment verification item.
+
+### Task 4: Review, Commit, Deliver, And Reach Mergeable State
+
+**Files:**
+- Review every file changed from `origin/feat/2026-08-04_workflow-activity-vnext`
+
+- [x] **Step 1: Review the complete diff**
+
+Confirm that the diff is frontend-only, contains no imported baseline artifact
+changes, preserves all save contracts, and includes no unrelated worktree
+files. Review long-label behavior and all conditional shell paths.
+
+- [ ] **Step 2: Commit only task files as AbigailDeng**
+
+Stage explicit paths only. Commit with:
+
+```bash
+git -c user.name=AbigailDeng \
+ -c user.email=108705114+AbigailDeng@users.noreply.github.com \
+ commit -m "Fix Settings save action dock"
+```
+
+- [ ] **Step 3: Push and open the pull request**
+
+Push `fix/2026-08-06_settings-save-action-dock` and create a PR with base
+`feat/2026-08-04_workflow-activity-vnext`. Verify the PR author is
+`AbigailDeng`. Include the required design-baseline declaration, affected
+paths, exact focused command results, screenshots or the exact browser-QA gap,
+and:
+
+```text
+Full frontend suite/build: deferred to GitHub CI by personal local workflow policy
+```
+
+- [ ] **Step 4: Make the PR mergeable**
+
+Poll required checks, review comments, merge conflicts, and GitHub mergeability.
+Fix branch-owned failures, push updates, and recheck until the PR reports no
+conflict and all required checks pass. Never create or update the PR as
+`Abigail940404`.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-activity-vnext-scoped-publication.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-activity-vnext-scoped-publication.md
new file mode 100644
index 0000000000..e22c26fdd8
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-activity-vnext-scoped-publication.md
@@ -0,0 +1,228 @@
+# Workflow Activity vNext Scoped Publication Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Let an operator publish a saved, valid vNext Workflow to one explicitly selected real scope service, then observe the accepted update through exact workflow and service-revision reads.
+
+**Architecture:** A vNext-local publication hook owns the accepted receipt and bounded observation state. A focused dialog loads only real scope services, requires a deliberate service selection, and collects any real external-request confirmations before calling the existing typed `studioApi.saveAndBindWorkflow` adapter. The editor remains the owner of document validation and serialization; no Team/member API, inferred service identity, mock data, or browser-storage state is introduced.
+
+**Tech Stack:** React 19, TypeScript, Ant Design, TanStack Query, existing Studio/scope-runtime adapters, Jest, Testing Library, Biome.
+
+**Design baseline:**
+
+```text
+Design baseline:
+ apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/
+Primary design:
+ aevatar-workflow-activity-vnext.excalidraw
+Design SHA-256:
+ 30e74d7b410ae72c4c91432355436679033679c54c10b1702908435b001577de
+Contract specification:
+ apps/aevatar-console-web/docs/superpowers/specs/
+ 2026-08-04-workflow-activity-vnext-design.md
+User paths:
+ apps/aevatar-console-web/docs/superpowers/specs/
+ 2026-08-04-workflow-activity-vnext-user-paths.md
+Production data source:
+ Real APIs and API-acknowledged user actions only; no mock fallback.
+```
+
+---
+
+### Task 1: Receipt-Bound Publication Observation
+
+**Files:**
+- Create: `src/pages/workflow-activity-vnext/hooks/useWorkflowPublication.ts`
+- Create: `src/pages/workflow-activity-vnext/hooks/useWorkflowPublication.test.ts`
+
+- [ ] **Step 1: Write failing observation tests**
+
+Cover these public behaviors with distinct fixtures:
+
+```ts
+it('observes only the accepted workflow, service, and revision once the active serving revision is published', async () => {
+ const result = await observeWorkflowPublication({
+ receipt: { scopeId: 'scope-alpha', workflowId: 'wf-alpha', revisionId: 'rev-alpha', serviceId: 'svc-alpha' },
+ readWorkflow: async () => readableWorkflowDetail('scope-alpha', 'wf-alpha'),
+ readRevisions: async () => revisionCatalog('scope-alpha', 'svc-alpha', publishedActiveRevision('rev-alpha')),
+ delaysMs: [0],
+ });
+
+ expect(result.kind).toBe('observed');
+});
+
+it('treats receipt-bound workflow 404 and 409 plus a missing revision as observation delay without creating another publish request', async () => {
+ const result = await observeWorkflowPublication({
+ receipt: { scopeId: 'scope-alpha', workflowId: 'wf-alpha', revisionId: 'rev-alpha', serviceId: 'svc-alpha' },
+ readWorkflow: async () => { throw httpStatusError(409); },
+ readRevisions: async () => revisionCatalog('scope-alpha', 'svc-alpha', null),
+ delaysMs: [0],
+ });
+
+ expect(result).toEqual({ kind: 'delayed' });
+});
+```
+
+- [ ] **Step 2: Run the new observation test to verify it fails**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/hooks/useWorkflowPublication.test.ts
+```
+
+Expected: FAIL because `observeWorkflowPublication` and its receipt-bound observation behavior do not exist.
+
+- [ ] **Step 3: Implement the minimal observation hook**
+
+Export a `WorkflowPublicationReceipt` with only API-returned `scopeId`, `workflowId`, `revisionId`, and `serviceId`. `observeWorkflowPublication` must query the exact `scopesApi.getWorkflowDetail` and `scopeRuntimeApi.getServiceRevisions` identities in parallel on each bounded attempt. Return `observed` only when:
+
+```ts
+workflow.available === true &&
+workflow.scopeId === receipt.scopeId &&
+workflow.workflow?.workflowId === receipt.workflowId &&
+catalog.scopeId === receipt.scopeId &&
+catalog.serviceId === receipt.serviceId &&
+catalog.activeServingRevisionId === receipt.revisionId &&
+revision.revisionId === receipt.revisionId &&
+revision.implementationKind === 'workflow' &&
+normalize(revision.status) === 'published' &&
+revision.isActiveServing &&
+revision.isServingTarget &&
+revision.allocationWeight > 0 &&
+normalize(revision.servingState) === 'active'
+```
+
+Treat workflow `404`/`409`, service catalog `404`, and a catalog lacking the exact revision as eventual-consistency observation states. Map `401` and `403` distinctly. Treat `PreparationFailed`, a nonempty service failure reason, and `Retired` as terminal failure. The hook retry calls only the exact read functions and never calls `saveAndBindWorkflow`.
+
+- [ ] **Step 4: Run the observation test to verify it passes**
+
+Run the same Jest command from Step 2.
+
+Expected: PASS with no timer-driven artificial success state.
+
+### Task 2: Explicit Target Selection And External-Request Review
+
+**Files:**
+- Create: `src/pages/workflow-activity-vnext/workflows/WorkflowPublishDialog.tsx`
+- Modify: `src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `src/locales/workflowActivityVNextMessages.zh-CN.ts`
+- Test: `src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [ ] **Step 1: Write failing route-integration tests**
+
+Add tests that render the real vNext editor through its route owner and prove:
+
+```ts
+it('requires an explicitly selected real scope service before publishing a saved workflow', async () => {
+ // Open Publish, wait for real service rows, and assert the primary submit action is disabled.
+ // Select 'Service alpha', submit, and assert saveAndBindWorkflow receives serviceId: 'svc-alpha'.
+});
+
+it('keeps publish accepted while the exact workflow and service revision are still being observed', async () => {
+ // Return 202 data, hold or delay the exact GETs, and assert no success toast or ready claim.
+});
+
+it('retries only observation reads after a delayed publication', async () => {
+ // Make the receipt-bound reads return 404/409, click Check again, and assert one POST total.
+});
+
+it('renders unauthorized and forbidden publication observation states distinctly', async () => {
+ // Exercise 401 and 403 from an exact observation read through the rendered alert.
+});
+```
+
+- [ ] **Step 2: Run only the named new route tests to verify they fail**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx --testNamePattern 'explicitly selected real scope service|publish accepted|retries only observation reads|unauthorized and forbidden publication'
+```
+
+Expected: FAIL because the editor's current Publish action is disabled and no publication dialog or observation UI exists.
+
+- [ ] **Step 3: Implement the focused dialog**
+
+The dialog must load `scopeRuntimeApi.listServices(scopeId, { take: 200 })` only while open. It begins with no selection, uses real service display names, and has distinct loading, empty, failure, retry, unauthorized, and forbidden content. It must never choose or submit a default service on the user's behalf.
+
+On submission, use an exact editor-generated publication snapshot and call `studioApi.previewExplicitRequests`. When preview items exist, render an in-dialog review using only relevant user decisions: method/path, risk, and whether approval is required. Keep call-site IDs and request digests out of the default UI; use them only to construct the confirmation payload. A user cancellation returns to the target-selection state without making a publish POST.
+
+- [ ] **Step 4: Run the named route tests to verify they pass**
+
+Run the command from Step 2.
+
+Expected: PASS; each test uses a different `workflowId`, `serviceId`, and `revisionId` fixture identity.
+
+### Task 3: Editor Integration And Honest User Feedback
+
+**Files:**
+- Modify: `src/pages/workflow-activity-vnext/hooks/useWorkflowEditor.ts`
+- Modify: `src/pages/workflow-activity-vnext/workflows/WorkflowEditorPage.tsx`
+- Modify: `src/pages/workflow-activity-vnext/workflows/WorkflowPublishDialog.tsx`
+- Modify: `src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `src/locales/workflowActivityVNextMessages.zh-CN.ts`
+- Test: `src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [ ] **Step 1: Add the smallest editor publication preparation boundary**
+
+Expose a function from `useWorkflowEditor` that parses and serializes the current document only when it is saved, valid, and a real draft. It returns the exact draft `workflowId`, display name, and serialized YAML, or an actionable validation failure. It must not fabricate a revision, modify the current draft, or save silently.
+
+- [ ] **Step 2: Wire the dialog to the existing typed adapters**
+
+Use `createWorkflowRevisionIdentityCandidate()` only as the required request identity candidate. Pass the selected service ID exactly to `studioApi.saveAndBindWorkflow`; capture only API-returned `workflowId`, `revisionId`, and `binding.serviceId` as the observation receipt. Reject a missing binding service ID, scope mismatch, or returned target mismatch as a visible publish failure rather than substituting any identity.
+
+- [ ] **Step 3: Render persistent state and one completed-action toast**
+
+Keep the receipt-bound state in the editor while it is mounted:
+
+```text
+Ready -> Reviewing -> Submitting -> Accepted -> Observing -> Published
+ | |
+ | +-> Delayed -> Check again (GET only)
+ +-> Failed / Unauthorized / Forbidden
+```
+
+Use inline alerts for every nonterminal phase. Emit one localized `ConsoleToast` success only after exact workflow and active service-revision evidence reaches `Published`; do not toast for a click, preview request, or `202 Accepted` response. Never claim full invocation readiness because no public HTTP contract proves it.
+
+- [ ] **Step 4: Run the focused route and hook tests to verify the integrated flow passes**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx src/pages/workflow-activity-vnext/hooks/useWorkflowPublication.test.ts
+```
+
+Expected: PASS.
+
+### Task 4: Focused Verification And Delivery
+
+**Files:**
+- Review only the current task's vNext hook, dialog, editor, locale, test, and plan files.
+
+- [ ] **Step 1: Re-run the frontend scope analyzer**
+
+Run:
+
+```bash
+python3 /Users/abigaildeng/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base origin/feat/2026-08-04_workflow-activity-vnext
+```
+
+- [ ] **Step 2: Run dependency-related tests and changed-file static checks**
+
+Run explicit changed/new Jest tests plus `pnpm exec jest --findRelatedTests` only for changed shared source files when the analyzer identifies a direct consumer graph. Run `pnpm exec biome check` only with the analyzer's `staticCheckFiles` that belong to this task.
+
+- [ ] **Step 3: Verify baseline and diff hygiene**
+
+Run:
+
+```bash
+python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py
+git diff --check -- apps/aevatar-console-web
+```
+
+Expected: declared SHA, byte-identical generator output, and 17/17 frame inventory pass; no whitespace errors.
+
+- [ ] **Step 4: Review and deliver only task-owned changes**
+
+Review the complete diff. Stage only the publication task's frontend files, commit with an imperative message, push the implementation branch, and create or update the implementation Draft PR. Do not modify, ready, merge, or auto-merge PR #3187. Full frontend suite, package-wide lint, package-wide TypeScript, and production build are delegated to GitHub CI under the personal incremental frontend policy.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-archive.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-archive.md
new file mode 100644
index 0000000000..0c6b39bf23
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-archive.md
@@ -0,0 +1,372 @@
+# Workflow Archive Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Restore an honest Archive action for active published Workflows, observe authoritative deactivation before success, and separate active and archived catalogue views.
+
+**Architecture:** A focused `workflowArchival.ts` module owns deployment-status normalization, archive eligibility, and bounded read-model observation. `WorkflowsPage.tsx` maps catalogue rows to those facts, submits the existing deployment deactivation command through `servicesApi`, and keeps command acceptance distinct from observed archival. No backend contract or client-side archived store is added.
+
+**Tech Stack:** React, TypeScript, Ant Design, React Query, Jest, Testing Library, Biome.
+
+---
+
+### Task 1: Authoritative Archive Classification And Observation
+
+**Files:**
+- Create: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/workflowArchival.ts`
+- Create: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/workflowArchival.test.ts`
+
+- [ ] **Step 1: Write failing classification tests**
+
+Create tests that use distinct identities and prove status normalization and
+eligibility:
+
+```ts
+expect(isWorkflowArchived({ deploymentStatus: 'Deactivated' })).toBe(true);
+expect(isWorkflowArchived({ deploymentStatus: 'deactivated' })).toBe(true);
+expect(canArchiveWorkflow({
+ activeRevisionId: 'rev-alpha',
+ deploymentId: 'dep-alpha',
+ deploymentStatus: 'Active',
+ hasCommittedSource: true,
+})).toBe(true);
+expect(canArchiveWorkflow({
+ activeRevisionId: '',
+ deploymentId: '',
+ deploymentStatus: '',
+ hasCommittedSource: false,
+})).toBe(false);
+```
+
+- [ ] **Step 2: Run classification tests and verify RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/workflowArchival.test.ts
+```
+
+Expected: FAIL because `workflowArchival.ts` does not exist.
+
+- [ ] **Step 3: Implement minimal classification helpers**
+
+Define a narrow shared shape and normalize enum spelling without guessing any
+other lifecycle:
+
+```ts
+export type WorkflowArchivalFacts = {
+ readonly activeRevisionId: string;
+ readonly deploymentId: string;
+ readonly deploymentStatus: string;
+ readonly hasCommittedSource: boolean;
+};
+
+function normalizeDeploymentStatus(value: string): string {
+ return value.trim().toLowerCase().replace(/[\s_-]+/g, '');
+}
+
+export function isWorkflowArchived(
+ workflow: Pick,
+): boolean {
+ return normalizeDeploymentStatus(workflow.deploymentStatus) === 'deactivated';
+}
+
+export function canArchiveWorkflow(workflow: WorkflowArchivalFacts): boolean {
+ return (
+ workflow.hasCommittedSource &&
+ Boolean(workflow.activeRevisionId.trim()) &&
+ Boolean(workflow.deploymentId.trim()) &&
+ normalizeDeploymentStatus(workflow.deploymentStatus) === 'active'
+ );
+}
+```
+
+- [ ] **Step 4: Run classification tests and verify GREEN**
+
+Run the Task 1 Step 2 command. Expected: all classification tests PASS.
+
+- [ ] **Step 5: Write failing bounded-observation tests**
+
+Add tests for delayed-to-observed state, exact Workflow identity, read failure,
+and exhausted observation:
+
+```ts
+const readWorkflows = jest
+ .fn()
+ .mockResolvedValueOnce([{ workflowId: 'wf-alpha', deploymentStatus: 'Active' }])
+ .mockResolvedValueOnce([{ workflowId: 'wf-alpha', deploymentStatus: 'Deactivated' }]);
+
+await expect(observeWorkflowArchival({
+ delaysMs: [0, 1],
+ readWorkflows,
+ wait: jest.fn(async () => undefined),
+ workflowId: 'wf-alpha',
+})).resolves.toEqual({ kind: 'observed', workflows: expect.any(Array) });
+```
+
+Also assert a list containing only `wf-beta` never satisfies `wf-alpha` and
+returns `{ kind: 'delayed' }`.
+
+- [ ] **Step 6: Run observation tests and verify RED**
+
+Run the Task 1 Step 2 command. Expected: FAIL because
+`observeWorkflowArchival` is not exported.
+
+- [ ] **Step 7: Implement the bounded observer**
+
+Use the existing publication/materialization convention with injectable timing:
+
+```ts
+export const WORKFLOW_ARCHIVAL_OBSERVATION_DELAYS_MS = [
+ 0, 300, 700, 1200, 2000,
+] as const;
+
+export async function observeWorkflowArchival(input: {
+ readonly delaysMs?: readonly number[];
+ readonly readWorkflows: () => Promise;
+ readonly wait?: (delayMs: number) => Promise;
+ readonly workflowId: string;
+}): Promise<
+ | { readonly kind: 'observed'; readonly workflows: readonly ScopeWorkflowSummary[] }
+ | { readonly kind: 'delayed' }
+> {
+ const delays = input.delaysMs ?? WORKFLOW_ARCHIVAL_OBSERVATION_DELAYS_MS;
+ const wait = input.wait ?? defaultWait;
+ for (const delayMs of delays) {
+ if (delayMs > 0) await wait(delayMs);
+ const workflows = await input.readWorkflows();
+ const target = workflows.find((item) => item.workflowId === input.workflowId);
+ if (target && isWorkflowArchived(target)) return { kind: 'observed', workflows };
+ }
+ return { kind: 'delayed' };
+}
+```
+
+- [ ] **Step 8: Run helper tests and commit Task 1**
+
+Run the Task 1 Step 2 command. Expected: all helper tests PASS.
+
+```bash
+git add apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/workflowArchival.ts apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/workflowArchival.test.ts
+git commit -m "Model workflow archival observation"
+```
+
+### Task 2: Catalogue Archive Interaction And Views
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+
+- [ ] **Step 1: Add failing catalogue tests for action eligibility**
+
+Mock `servicesApi.deactivateDeployment`, render an active published row, a
+draft-only row, and a deactivated row, then assert:
+
+```ts
+fireEvent.click(within(activeRow).getByRole('button', {
+ name: 'More actions for Active workflow',
+}));
+expect(await screen.findByRole('menuitem', { name: 'Archive' })).toBeVisible();
+
+fireEvent.click(within(draftRow).getByRole('button', {
+ name: 'More actions for Draft workflow',
+}));
+expect(screen.queryByRole('menuitem', { name: 'Archive' })).not.toBeInTheDocument();
+```
+
+Select `Archived`, assert the deactivated row has status `Archived`, and assert
+it has no Archive menu action.
+
+- [ ] **Step 2: Run the catalogue test and verify RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx
+```
+
+Expected: FAIL because the selector and menu do not expose Archived/Archive.
+
+- [ ] **Step 3: Add deployment facts and view filtering**
+
+Extend `WorkflowRow` with `deploymentId` and `deploymentStatus`; preserve the
+committed values when a draft overlays a committed row. Replace the view type
+with:
+
+```ts
+type WorkflowView = 'active' | 'drafts' | 'archived';
+```
+
+Filter rows by `isWorkflowArchived(row)`:
+
+```ts
+if (view === 'drafts') return draftWorkflowIds.has(item.workflowId);
+if (view === 'archived') return isWorkflowArchived(item);
+return !isWorkflowArchived(item);
+```
+
+Omit `view=active` from the URL, preserve `drafts` and `archived`, and label the
+default option `Active workflows`.
+
+- [ ] **Step 4: Render Archive status and eligible menu action**
+
+Calculate archived status before Published/Draft. Add an `InboxOutlined`
+Archive menu item only when `canArchiveWorkflow(row)` is true. Selecting it must
+set `archiveTarget` and reset archive failure/submission state; it must not call
+the API until modal confirmation.
+
+- [ ] **Step 5: Run the catalogue test and verify the eligibility slice GREEN**
+
+Run the Task 2 Step 2 command. Expected: action and filter assertions PASS;
+interaction assertions added next remain absent.
+
+- [ ] **Step 6: Add failing accepted-versus-observed interaction tests**
+
+Make list responses transition from Active to Deactivated and assert the exact
+identity call:
+
+```ts
+expect(mockServicesApi.deactivateDeployment).toHaveBeenCalledWith(
+ 'wf-alpha',
+ 'dep-alpha',
+ { tenantId: 'scope-alpha', appId: 'default', namespace: 'default' },
+);
+```
+
+Hold the observation read unresolved and assert no `Workflow archived` toast
+appears from the accepted receipt. Resolve Deactivated, then assert the dialog
+closes, the toast appears, and the row moves to the Archived view.
+
+Add a delayed case in which `Check again` invokes only
+`observeWorkflowArchival`/list reads and
+`deactivateDeployment` remains called exactly once.
+
+- [ ] **Step 7: Run interaction tests and verify RED**
+
+Run the Task 2 Step 2 command. Expected: FAIL because the Archive dialog and
+submission state do not exist.
+
+- [ ] **Step 8: Implement confirmation, command submission, and observation**
+
+Add `archiveTarget`, `archiving`, `archiveSubmitted`, and `archivePhase` state.
+The confirm handler must:
+
+```ts
+if (!archiveSubmitted) {
+ await servicesApi.deactivateDeployment(
+ archiveTarget.workflowId,
+ archiveTarget.deploymentId,
+ {
+ tenantId: scopeId,
+ appId: scopeServiceAppId,
+ namespace: scopeServiceNamespace,
+ },
+ );
+ setArchiveSubmitted(true);
+}
+
+const result = await observeWorkflowArchival({
+ workflowId: archiveTarget.workflowId,
+ readWorkflows: () => scopesApi.listWorkflows(scopeId),
+});
+```
+
+On `observed`, refresh the committed query, close/reset the modal, and show
+`Workflow archived`. On `delayed`, retain submitted state and show the delayed
+message with `Check again`. On request/read error, distinguish whether command
+acceptance already occurred so a retry never duplicates the command.
+
+- [ ] **Step 9: Add complete English and Chinese messages**
+
+Add typed locale entries for Active/Archived views, Archived status, Archive
+menu label, confirmation title/body, failure, delayed observation, retry/check,
+and success. Chinese copy must preserve the same operational meaning rather
+than shortening Archive to Delete.
+
+- [ ] **Step 10: Run component and helper tests and commit Task 2**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/workflowArchival.test.ts src/pages/workflow-activity-vnext/index.test.tsx
+```
+
+Expected: both suites PASS.
+
+```bash
+git add apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts
+git commit -m "Restore workflow archive action"
+```
+
+### Task 3: Focused Validation And Delivery
+
+**Files:**
+- Modify only if verification finds a task-specific defect: files listed in Tasks 1-2
+- Verify: `apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-workflow-archive-design.md`
+- Verify: `apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-archive.md`
+
+- [ ] **Step 1: Run the frontend change-scope analyzer**
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base origin/feat/2026-08-04_workflow-activity-vnext
+```
+
+Use only the reported dependency-related Jest targets and explicit changed test
+files. Do not run the full frontend suite, package-wide typecheck, or build.
+
+- [ ] **Step 2: Run focused tests**
+
+At minimum run the two explicit changed suites from Task 2 Step 10. If the
+analyzer reports additional direct dependencies, run those exact files only.
+
+- [ ] **Step 3: Run changed-file static checks and guards**
+
+```bash
+pnpm --dir apps/aevatar-console-web exec biome check \
+ src/pages/workflow-activity-vnext/workflows/workflowArchival.ts \
+ src/pages/workflow-activity-vnext/workflows/workflowArchival.test.ts \
+ src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx \
+ src/pages/workflow-activity-vnext/index.test.tsx \
+ src/locales/workflowActivityVNextMessages.en-US.ts \
+ src/locales/workflowActivityVNextMessages.zh-CN.ts
+bash tools/ci/test_stability_guards.sh
+bash tools/docs/lint.sh
+git diff --check origin/feat/2026-08-04_workflow-activity-vnext...HEAD
+```
+
+Expected: all commands exit 0. Full frontend suite/typecheck/build remains
+delegated to GitHub CI by the personal workflow policy.
+
+- [ ] **Step 4: Verify the browser surface without mutating real data**
+
+Start or reuse the feature-branch frontend with the configured remote backend.
+Use an authenticated session to verify menu/dialog copy and desktop/mobile
+layout. Do not confirm a real Archive command. Use automated component tests as
+the authoritative proof for post-confirmation Active-to-Archived transitions.
+
+- [ ] **Step 5: Review and commit remaining plan/verification changes**
+
+```bash
+git status --short
+git diff --stat origin/feat/2026-08-04_workflow-activity-vnext...HEAD
+git diff --check origin/feat/2026-08-04_workflow-activity-vnext...HEAD
+git add apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-archive.md
+git commit -m "Document workflow archive implementation"
+```
+
+Skip the final commit if the plan was already included in an earlier focused
+commit and the worktree is clean.
+
+- [ ] **Step 6: Push and create the pull request**
+
+Push `fix/2026-08-06_workflow-archive` and create a ready pull request targeting
+`feat/2026-08-04_workflow-activity-vnext`. The PR body must include problem and
+solution, affected paths, the exact focused verification commands/results, and:
+
+```markdown
+- Full frontend suite/typecheck/build: deferred to GitHub CI by personal local workflow policy
+```
+
+Stop after reporting the PR URL; do not babysit CI unless requested.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-list-metadata.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-list-metadata.md
new file mode 100644
index 0000000000..2450233233
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-list-metadata.md
@@ -0,0 +1,222 @@
+# Workflow List Metadata Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make the Workflow catalogue scan-friendly by separating lifecycle status and last update, moving descriptions into an accessible fixed-width popover, and removing technical scope and revision details from the primary list.
+
+**Architecture:** Keep the existing draft/committed merge and query behavior intact. Change only the `WorkflowRow` presentation mapping and table rendering in `WorkflowsPage.tsx`, add narrow table/popover styles in the existing vNext stylesheet, and add the new column and status locale strings to the existing locale dictionaries.
+
+**Tech Stack:** React 19, TypeScript, Ant Design 6 `Popover`, Jest, Testing Library, Biome.
+
+---
+
+## File Map
+
+- `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`: user-observable regression coverage for list hierarchy, hidden technical values, and popover behavior.
+- `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`: merged row model and four-column table rendering.
+- `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`: stable table column sizing, name trigger focus treatment, and fixed-width description content.
+- `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`: English column and Published labels.
+- `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`: Chinese column and Published labels.
+
+### Task 1: Lock The Correct Product Semantics With A Failing Test
+
+**Files:**
+
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [ ] **Step 1: Replace the old mixed-metadata assertion with the desired list behavior**
+
+Use distinct workflow, revision, service, scope, and ownership values. Assert the four column headers and row cell labels, then assert that ownership, scope, revision ID, and Created are absent. The key expectations are:
+
+```tsx
+expect(within(workflowTable).getAllByRole('columnheader')).toHaveLength(4);
+expect(within(workflowTable).getByRole('columnheader', { name: 'Status' })).toBeVisible();
+expect(
+ within(workflowTable).getByRole('columnheader', { name: 'Last updated' }),
+).toBeVisible();
+expect(within(apacRow).getByText('Published')).toBeVisible();
+expect(within(apacRow).queryByText('rev-support-apac-7')).not.toBeInTheDocument();
+expect(within(workflowTable).queryByText('scope-alpha')).not.toBeInTheDocument();
+expect(within(workflowTable).queryByText('APAC operations')).not.toBeInTheDocument();
+expect(within(workflowTable).queryByRole('columnheader', { name: 'Created' })).not.toBeInTheDocument();
+```
+
+- [ ] **Step 2: Assert description disclosure instead of default description rendering**
+
+Give the workflow-name trigger an accessible label such as `Description for Support triage`. Assert the description is absent initially, then use both pointer and keyboard interactions to disclose it. Verify the rendered content owns the fixed-width class:
+
+```tsx
+const descriptionTrigger = within(emeaRow).getByRole('button', {
+ name: 'Description for Support triage',
+});
+expect(screen.queryByText(emeaDescription)).not.toBeInTheDocument();
+fireEvent.mouseEnter(descriptionTrigger);
+const description = await screen.findByText(emeaDescription);
+expect(description).toHaveClass('wa-vnext__workflow-description');
+fireEvent.mouseLeave(descriptionTrigger);
+await waitFor(() => expect(screen.queryByText(emeaDescription)).not.toBeInTheDocument());
+fireEvent.focus(descriptionTrigger);
+expect(await screen.findByText(emeaDescription)).toBeVisible();
+```
+
+Add a committed-only workflow with no description and assert its name is not a description button.
+
+- [ ] **Step 3: Run the focused regression test and confirm RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest src/pages/workflow-activity-vnext/index.test.tsx --selectProjects jsdom --runInBand -t "separates workflow status"
+```
+
+Expected: FAIL because the current table has two headers, permanently renders descriptions, includes ownership and revision values, and has no Status or Last updated columns.
+
+### Task 2: Implement The Four-Column Catalogue
+
+**Files:**
+
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+
+- [ ] **Step 1: Remove ownership from the row presentation model**
+
+Delete `ownershipLabel` from `WorkflowRow` and from both row mappers. Keep `description`, `activeRevisionId`, and `updatedAtUtc` because they still drive search, popover content, status, and sorting.
+
+- [ ] **Step 2: Add the accessible description trigger**
+
+Import `Popover` and render it only for a trimmed non-empty description:
+
+```tsx
+const workflowName = {row.name};
+
+{row.description.trim() ? (
+
+ {row.description}
+
+ }
+ placement="bottomLeft"
+ trigger={['hover', 'focus']}
+ >
+
+
+) : (
+ workflowName
+)}
+```
+
+The trigger is contextual and must not navigate or replace the explicit Open action.
+
+- [ ] **Step 3: Split status and update time into columns**
+
+Add `Status` and `Last updated` headers between Workflow and Actions. Render corresponding cells with matching `data-label` values. Status uses only `Published` or `Draft`; update time uses `formatDate(row.updatedAtUtc)` without an `Updated` prefix.
+
+- [ ] **Step 4: Add stable, viewport-safe styling**
+
+Define table width classes so the Workflow column owns remaining space and Status, Last updated, and Actions have stable widths. Add a native-looking name button reset with a visible focus outline. The popover description content must use:
+
+```css
+.wa-vnext__workflow-description {
+ margin: 0;
+ max-width: calc(100vw - 48px);
+ overflow-wrap: anywhere;
+ white-space: normal;
+ width: 320px;
+}
+```
+
+- [ ] **Step 5: Add locale keys and remove stale primary-list copy**
+
+Add English and Chinese values for `columnStatus`, `columnUpdated`, `publishedStatus`, and `descriptionAria`. Remove `publishedRevision`, `updatedContext`, and `workspaceOwner` only if no remaining references exist in the vNext area.
+
+- [ ] **Step 6: Run the focused test and confirm GREEN**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest src/pages/workflow-activity-vnext/index.test.tsx --selectProjects jsdom --runInBand -t "separates workflow status"
+```
+
+Expected: PASS for the new regression test.
+
+### Task 3: Focused Regression And Static Verification
+
+**Files:**
+
+- Verify all files listed in the File Map.
+
+- [ ] **Step 1: Run the frontend change scope analyzer**
+
+Run from the worktree root:
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base origin/feat/2026-08-04_workflow-activity-vnext
+```
+
+Record `relatedTests` and `staticCheckFiles`. Do not replace missing affected checks with a full suite.
+
+- [ ] **Step 2: Run the complete related page test file**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest src/pages/workflow-activity-vnext/index.test.tsx --selectProjects jsdom --runInBand
+```
+
+Expected: the Workflow Activity vNext test file passes with zero failures.
+
+- [ ] **Step 3: Run changed-file static checks only**
+
+Pass exactly the analyzer-reported frontend source, test, style, and locale files to Biome. Do not run package-wide lint, full typecheck, or production build. If no repository-native affected typecheck exists, record that GitHub CI owns type verification.
+
+- [ ] **Step 4: Review semantics and diff integrity**
+
+Run:
+
+```bash
+rg -n "publishedRevision|Published \{revision\}|updatedContext|ownershipLabel" apps/aevatar-console-web/src/pages/workflow-activity-vnext apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.*.ts
+git diff --check
+git diff --stat origin/feat/2026-08-04_workflow-activity-vnext...HEAD
+git status --short
+```
+
+Expected: no stale primary-list references, no whitespace errors, and only task-owned files changed.
+
+- [ ] **Step 5: Commit the implementation**
+
+Stage only the implementation, test, locale, style, and plan files, then commit:
+
+```bash
+git commit -m "Refine workflow list metadata hierarchy"
+```
+
+### Task 4: Deliver The Pull Request
+
+- [ ] **Step 1: Push the branch**
+
+```bash
+git push -u origin fix/2026-08-06_workflow-list-metadata
+```
+
+- [ ] **Step 2: Create a ready pull request**
+
+Create a PR targeting `feat/2026-08-04_workflow-activity-vnext`. Include the problem and solution, affected Workflow catalogue path, exact focused commands and results, and this statement:
+
+```text
+Full frontend suite/build: deferred to GitHub CI by personal local workflow policy.
+```
+
+Stop after reporting the ready PR URL. Do not wait for CI unless the user asks.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-row-action-hierarchy.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-row-action-hierarchy.md
new file mode 100644
index 0000000000..bcf2b09ff7
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-06-workflow-row-action-hierarchy.md
@@ -0,0 +1,182 @@
+# Workflow Row Action Hierarchy Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Replace the Workflow catalogue's mixed five-action presentation with one primary Open link, one neutral View activity link, and one stable overflow menu without changing any Workflow identity or mutation semantics.
+
+**Architecture:** Keep the change local to the existing Workflow Activity vNext catalogue. Ant Design Button links provide real `href` values while an unmodified left click still enters the existing client-side history; the Dropdown owns all low-frequency row actions and continues to call the existing rename, copy, and delete-confirmation flows. Existing Aevatar interaction classes and vNext CSS define the stable action geometry and touch targets.
+
+**Tech Stack:** React 19, TypeScript, Ant Design 6, Jest, Testing Library, Biome, Umi history and locale catalogues.
+
+---
+
+### Task 1: Prove The Persistent Action Hierarchy And Navigation Contract
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`
+
+- [ ] **Step 1: Write the failing route assertions**
+
+Update the focused Workflow catalogue tests so a row exposes:
+
+```tsx
+const open = within(row).getByRole('link', {
+ name: 'Open Support triage in Workflows',
+});
+const activity = within(row).getByRole('link', {
+ name: 'View activity for Support triage in Workflows',
+});
+
+expect(open).toHaveAttribute(
+ 'href',
+ '/scopes/scope-alpha/workflow-activity-vnext/workflows/wf-draft-alpha',
+);
+expect(activity).toHaveAttribute(
+ 'href',
+ '/scopes/scope-alpha/workflow-activity-vnext/activity?workflowId=wf-draft-alpha',
+);
+expect(within(row).queryByRole('button', { name: /Delete/ })).not.toBeInTheDocument();
+```
+
+Also assert that duplicate-name rows have different Open, View activity, and More actions accessible names because each name includes `ownershipLabel`, and that a modified click is not intercepted by the component.
+
+- [ ] **Step 2: Run the focused test to verify RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx
+```
+
+Expected: FAIL because Open and Activity are buttons, their accessible names omit ownership, and the standalone Delete button is still rendered.
+
+- [ ] **Step 3: Implement real primary and secondary links**
+
+In `WorkflowsPage.tsx`:
+
+```tsx
+const editorHref = buildWorkflowActivityEditorHref(scopeId, row.workflowId);
+const activityHref = `${buildWorkflowActivitySectionHref(
+ scopeId,
+ 'activity',
+)}?workflowId=${encodeURIComponent(row.workflowId)}`;
+
+}
+ onClick={(event) => handleClientLinkClick(event, editorHref)}
+ type="primary"
+>
+ {t('workflowActivityVNext.common.open', 'Open')}
+
+```
+
+Render View activity the same way with `HistoryOutlined`, the exact encoded `workflowId`, a neutral Button type, and a row-specific accessible name. `handleClientLinkClick` must prevent default only for an unmodified primary-button click before calling `history.push`.
+
+- [ ] **Step 4: Run the focused test to verify GREEN**
+
+Run the same focused Jest command. Expected: the persistent hierarchy, real href, exact Workflow identity, and modified-click assertions pass.
+
+### Task 2: Move Destructive Management Into The Overflow Menu
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`
+
+- [ ] **Step 1: Write the failing menu-order and confirmation assertions**
+
+Open a draft row's More actions menu and assert this semantic order:
+
+```tsx
+expect(screen.getAllByRole('menuitem').map((item) => item.textContent)).toEqual([
+ 'Rename',
+ 'Copy workflow reference',
+ 'Delete draft',
+]);
+expect(screen.getByRole('separator')).toBeInTheDocument();
+expect(screen.getByRole('menuitem', { name: 'Delete draft' })).toHaveClass(
+ 'ant-dropdown-menu-item-danger',
+);
+```
+
+Click Delete draft and prove the existing confirmation opens while `deleteWorkflowDraft` remains uncalled. Update the existing deletion, retry, and refresh-failure tests to enter through More actions before confirming. Open a committed-only row menu and prove Copy workflow reference is its only menu item.
+
+- [ ] **Step 2: Run the focused test to verify RED**
+
+Run the focused Jest command from Task 1. Expected: FAIL because Delete draft is not in the Dropdown and the current draft menu has no divider or danger item.
+
+- [ ] **Step 3: Implement the unified menu**
+
+Remove the standalone Tooltip/Delete Button. Build the Dropdown items as Rename when draft-capable, Copy workflow reference for every row, then a divider and a danger Delete draft item when draft-capable:
+
+```tsx
+{ type: 'divider' },
+{
+ danger: true,
+ icon: ,
+ key: 'delete',
+ label: t('workflowActivityVNext.workflows.deleteDraft', 'Delete draft'),
+}
+```
+
+Handle `delete` only by setting `deleteTarget` and resetting the existing retry state. Keep the modal and `confirmDelete` logic unchanged.
+
+- [ ] **Step 4: Run the focused test to verify GREEN**
+
+Run the focused Jest command. Expected: menu hierarchy, published-only stability, confirmation-only first click, delete success, retry, refresh failure, rename, and copy tests pass.
+
+### Task 3: Align Styling, Locales, And The Product Contract
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+- Modify: `apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-design.md`
+
+- [ ] **Step 1: Add the local action-layout contract**
+
+Add narrowly scoped styles for an end-aligned, no-wrap action group with stable button geometry:
+
+```css
+.wa-vnext__workflow-actions-cell { text-align: right; }
+.wa-vnext__workflow-actions { align-items: center; display: inline-flex; white-space: nowrap; }
+.wa-vnext__workflow-actions .ant-btn { flex: 0 0 auto; }
+@media (max-width: 767px) {
+ .wa-vnext__workflow-actions .ant-btn { min-height: 44px; }
+ .wa-vnext__workflow-actions .ant-btn-icon-only { min-width: 44px; width: 44px; }
+}
+```
+
+Apply `AEVATAR_INTERACTIVE_BUTTON_CLASS` to all three persistent controls so global hover, active, focus, disabled, and loading states remain authoritative.
+
+- [ ] **Step 2: Update localized visible and accessible copy**
+
+Change `viewActivity` to `View activity` / `查看活动`. Update Open and More actions templates to include `{owner}`, and add `viewActivityAria` with `{name}` and `{owner}` in both locale catalogues. Preserve identical interpolation variables across locales.
+
+- [ ] **Step 3: Update the vNext design specification**
+
+Add one concise catalogue rule: exactly Open, View activity, and More actions remain persistent; Rename, Copy workflow reference, and Delete draft live in overflow; Delete is last, separated, dangerous, and confirmation-gated.
+
+- [ ] **Step 4: Run scope analysis and focused validation**
+
+Run:
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base origin/feat/2026-08-04_workflow-activity-vnext
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx
+pnpm --dir apps/aevatar-console-web exec biome check
+bash tools/docs/lint.sh
+git diff --check origin/feat/2026-08-04_workflow-activity-vnext...HEAD
+```
+
+Expected: focused Jest passes; changed-file Biome reports no diagnostics; docs lint reports zero errors; diff check is clean. Do not run a full frontend test, lint, typecheck, or production build. Full verification belongs to GitHub CI.
+
+- [ ] **Step 5: Commit and deliver the pull request**
+
+Stage only the plan, specs, Workflows page, vNext styles, focused route test, and two locale files. Commit as `AbigailDeng`, push `fix/2026-08-06_workflow-row-action-hierarchy`, and create a ready PR targeting `feat/2026-08-04_workflow-activity-vnext`. The PR body must include problem and solution, affected paths, exact focused commands/results, the visual approval context, and the statement that full frontend suite/build is delegated to GitHub CI.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-07-workflow-node-reselection.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-07-workflow-node-reselection.md
new file mode 100644
index 0000000000..973265d8b8
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-07-workflow-node-reselection.md
@@ -0,0 +1,219 @@
+# Workflow Node Reselection Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make clicking the already-selected workflow node idempotent while preserving discard confirmation for real node switches.
+
+**Architecture:** Keep the policy in `WorkflowEditorPage`, where the clicked node ID and active node ID are both available. Add rendered route-level tests because the regression depends on the canvas, editor selection state, inspector draft state, and modal collaborating correctly.
+
+**Tech Stack:** React 19, TypeScript, Umi Max, Ant Design, Jest, Testing Library
+
+---
+
+### Task 1: Lock The Selection Boundary With Failing Tests
+
+**Files:**
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [ ] **Step 1: Add the same-node regression test**
+
+Add a rendered editor test that selects `step-root`, changes its Instruction to
+`Updated prompt`, clicks `Select step:step-root` again, and asserts that no
+`Discard node changes?` dialog exists and the Instruction still contains the
+unapplied value.
+
+```tsx
+it('keeps unapplied node configuration when the selected node is clicked again', async () => {
+ renderWithQueryClient();
+
+ const selectedNode = await screen.findByRole('button', {
+ name: 'Select step:step-root',
+ });
+ fireEvent.click(selectedNode);
+ const inspector = await screen.findByRole('complementary', {
+ name: 'Configure step-root',
+ });
+ fireEvent.change(within(inspector).getByLabelText('Instruction'), {
+ target: { value: 'Updated prompt' },
+ });
+
+ fireEvent.click(selectedNode);
+
+ expect(
+ screen.queryByRole('dialog', { name: 'Discard node changes?' }),
+ ).not.toBeInTheDocument();
+ expect(within(inspector).getByLabelText('Instruction')).toHaveValue(
+ 'Updated prompt',
+ );
+});
+```
+
+- [ ] **Step 2: Add the different-node guard test**
+
+Override the workflow fixture with distinct `step-root` and `step-second`
+nodes. Dirty `step-root`, click `step-second`, verify the confirmation appears,
+cancel and verify the root draft remains, then discard and verify the second
+inspector opens.
+
+```tsx
+it('asks before switching from a node with unapplied configuration', async () => {
+ mockStudioApi.getWorkflow.mockResolvedValue({
+ workflowId: 'wf-committed-source',
+ name: 'Committed source',
+ fileName: 'committed-source.yaml',
+ filePath: '',
+ directoryId: '',
+ directoryLabel: '',
+ yaml: [
+ 'name: committed_source',
+ 'roles: []',
+ 'steps:',
+ ' - id: step-root',
+ ' type: llm_call',
+ ' parameters:',
+ ' prompt_prefix: Original prompt',
+ ' - id: step-second',
+ ' type: transform',
+ ' parameters:',
+ ' operation: trim',
+ '',
+ ].join('\n'),
+ updatedAtUtc: '2026-08-04T10:00:00Z',
+ document: {
+ name: 'committed_source',
+ roles: [],
+ steps: [
+ {
+ id: 'step-root',
+ type: 'llm_call',
+ parameters: { prompt_prefix: 'Original prompt' },
+ },
+ {
+ id: 'step-second',
+ type: 'transform',
+ parameters: { operation: 'trim' },
+ },
+ ],
+ },
+ draftExists: false,
+ findings: [],
+ });
+ renderWithQueryClient();
+
+ fireEvent.click(
+ await screen.findByRole('button', { name: 'Select step:step-root' }),
+ );
+ const rootInspector = await screen.findByRole('complementary', {
+ name: 'Configure step-root',
+ });
+ fireEvent.change(within(rootInspector).getByLabelText('Instruction'), {
+ target: { value: 'Updated prompt' },
+ });
+ const secondNode = screen.getByRole('button', {
+ name: 'Select step:step-second',
+ });
+
+ fireEvent.click(secondNode);
+
+ const discardDialog = await screen.findByRole('dialog', {
+ name: 'Discard node changes?',
+ });
+ expect(within(rootInspector).getByLabelText('Instruction')).toHaveValue(
+ 'Updated prompt',
+ );
+ fireEvent.click(
+ within(discardDialog).getByRole('button', { name: 'Cancel' }),
+ );
+ expect(within(rootInspector).getByLabelText('Instruction')).toHaveValue(
+ 'Updated prompt',
+ );
+
+ fireEvent.click(secondNode);
+ fireEvent.click(screen.getByRole('button', { name: 'Discard changes' }));
+
+ expect(
+ await screen.findByRole('complementary', {
+ name: 'Configure step-second',
+ }),
+ ).toBeVisible();
+});
+```
+
+- [ ] **Step 3: Run only the two new tests and verify RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest \
+ --runInBand \
+ --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx \
+ --testNamePattern 'selected node is clicked again|switching from a node with unapplied configuration'
+```
+
+Expected: the same-node test fails because the discard dialog opens. The
+different-node test passes or exposes only a test-fixture issue that must be
+corrected before implementation.
+
+### Task 2: Make Same-Node Selection Idempotent
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowEditorPage.tsx:297`
+
+- [ ] **Step 1: Add the minimal identity guard**
+
+```tsx
+const requestNodeSelect = React.useCallback(
+ (nodeId: string) => {
+ if (nodeId === editor.selectedNodeId) return;
+ requestInspectorDiscard(() => editor.selectNode(nodeId));
+ },
+ [editor.selectNode, editor.selectedNodeId, requestInspectorDiscard],
+);
+```
+
+- [ ] **Step 2: Re-run the two focused tests and verify GREEN**
+
+Run the same Jest command from Task 1. Expected: both selected tests pass with
+zero failures.
+
+### Task 3: Validate And Deliver The Incremental Change
+
+**Files:**
+- Verify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+- Verify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowEditorPage.tsx`
+
+- [ ] **Step 1: Run the complete directly changed test file**
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest \
+ --runInBand \
+ --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx
+```
+
+Expected: the file passes. Do not pass locale catalogs or other high-fan-out
+files to `--findRelatedTests`.
+
+- [ ] **Step 2: Run required focused guards and changed-file checks**
+
+Run `bash tools/ci/test_stability_guards.sh`, the frontend change-scope
+analyzer, the changed-file Biome command selected from the analyzer output,
+and `git diff --check`. Do not run a full frontend test, lint, typecheck, or
+production build. If no repository-native affected typecheck exists, delegate
+typecheck to GitHub CI.
+
+- [ ] **Step 3: Verify the browser interaction**
+
+On the authenticated local vNext editor:
+
+- dirty a node and re-click it: no modal and draft retained;
+- dirty a node and click another node: modal shown;
+- cancel: first-node draft retained;
+- discard: second-node inspector opens.
+
+- [ ] **Step 4: Review, commit, push, and update the pull request**
+
+Stage only this task's source, test, design, and plan files. Commit with the
+imperative message `Fix workflow node reselection`. Push the current branch and
+update PR #3269 with exact focused verification commands, results, the vNext
+design-baseline declaration, and the statement that the full frontend
+suite/build is delegated to GitHub CI.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-10-merge-describe-blank-creation.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-10-merge-describe-blank-creation.md
new file mode 100644
index 0000000000..3e230aa2d6
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-10-merge-describe-blank-creation.md
@@ -0,0 +1,88 @@
+# Merge Describe And Blank Creation Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Present three workflow creation methods and let the fixed `Generate and open` action create either a generated or blank workflow according to whether the user entered a description.
+
+**Architecture:** Keep the existing Describe form as the single owner of name and optional description input. At its submit event, branch on `prompt.trim()`: reuse `generateAndOpen()` for non-empty descriptions and `createBlank()` for empty descriptions, while leaving shared persistence, filename resolution, directory selection, materialization, and recovery unchanged.
+
+**Tech Stack:** React 19, TypeScript, Ant Design, TanStack Query, Jest, Testing Library, Biome, pnpm.
+
+---
+
+### Task 1: Protect The Merged User Contract
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [ ] **Step 1: Update the focused chooser and blank-creation expectations**
+
+Make the component test assert that the chooser exposes `Describe`, `Import YAML`, and `Use template`, does not expose `Start blank`, and that selecting Describe reveals the fixed `Generate and open` action. Change blank-creation cases to enter a Workflow name in Describe, leave the description empty, click `Generate and open`, and assert that `authorWorkflow` and `parseYaml` were not called while `createWorkflowDraft` received the existing minimal blank YAML.
+
+- [ ] **Step 2: Run the component test to verify RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx
+```
+
+Expected: FAIL because `Start blank` still renders and `Generate and open` is disabled without a description.
+
+- [ ] **Step 3: Update route integration expectations**
+
+Replace route-level `Start blank` interactions with `Describe`, enter the same names, leave the description empty, and click `Generate and open`. Where a test switches back to a method after a failed generated submission, select `Describe` again and verify that the failure clears while the shared input remains.
+
+### Task 2: Merge The Creation Modes
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/workflowCreation.ts`
+
+- [ ] **Step 1: Remove the separate blank method**
+
+Delete the `FileAddOutlined` import and `blank` chooser item. Narrow `WorkflowCreationMode` to `'describe' | 'import' | 'template'`, and render the Workflow name field only for Describe.
+
+- [ ] **Step 2: Route the fixed action by description presence**
+
+Keep the action label as the existing `workflowActivityVNext.new.generate` / `Generate and open`. Enable it when a trimmed name and save target exist. In the click handler call `generateAndOpen()` when `prompt.trim()` is non-empty; otherwise call `createBlank()`. Keep both existing functions and their stage-specific failure handling unchanged.
+
+- [ ] **Step 3: Run both affected test files to verify GREEN**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/NewWorkflowPage.test.tsx src/pages/workflow-activity-vnext/index.test.tsx
+```
+
+Expected: both files pass with no test failures.
+
+### Task 3: Focused Validation And Delivery
+
+**Files:**
+- Verify every modified source, test, and documentation file reported by the analyzer.
+
+- [ ] **Step 1: Run the frontend change scope analyzer**
+
+Run from the repository root:
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo .
+```
+
+Read `frontend-incremental-pr/references/framework-commands.md`, then use only analyzer-reported affected tests and static-check files.
+
+- [ ] **Step 2: Run changed-file static and documentation checks**
+
+Run the analyzer-prescribed Biome command for `staticCheckFiles`, the focused baseline verifier required for vNext work, documentation lint when the changed spec/plan is in its scope, and:
+
+```bash
+git diff --check
+```
+
+Do not run package-wide TypeScript, the full frontend suite, or a production build locally. If the repository has no affected TypeScript target, record that GitHub CI owns full typechecking and build verification.
+
+- [ ] **Step 3: Review and deliver the exact task diff**
+
+Compare the diff against the amended design specification: three chooser methods, no Start blank card, one fixed Generate and open action, name-only blank branch, described generation branch, and unchanged import/template/materialization behavior. Stage only the task files, commit with an imperative message, push the current branch, and create or update the pull request with exact local commands, results, the required vNext baseline declaration, and the statement that the full frontend suite/build is delegated to GitHub CI.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-11-vnext-list-return-refresh.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-11-vnext-list-return-refresh.md
new file mode 100644
index 0000000000..767b7a04db
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-11-vnext-list-return-refresh.md
@@ -0,0 +1,114 @@
+# vNext List Return Refresh Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Refresh Workflow Activity vNext collection data whenever users return from an editor or detail surface to Workflows or Activity.
+
+**Architecture:** Keep freshness policy with each list query by forcing a refetch whenever that query mounts. Reproduce the production 30-second cache window in route-level tests so detail-to-list navigation proves a second request is made while cached data is still fresh.
+
+**Tech Stack:** React, TypeScript, TanStack React Query, Jest, Testing Library, pnpm.
+
+---
+
+## File Map
+
+- Modify `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`: make catalogue data revalidate on every list entry.
+- Modify `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.tsx`: apply the same contract to the runs list.
+- Modify `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`: cover Workflows route return with fresh cached data.
+- Modify `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`: cover Activity remount with fresh cached data.
+- Existing `apps/aevatar-console-web/docs/superpowers/specs/2026-08-11-vnext-list-return-refresh-design.md`: approved behavior contract.
+
+### Task 1: Specify Workflows Return Freshness
+
+**Files:**
+
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [ ] **Step 1: Add a production-cache route test**
+
+Render with a local `QueryClient` whose `staleTime` is `30_000`, enter the
+Workflows route, navigate to `/workflows/wf-alpha`, then return to `/workflows`.
+Assert `queryWorkflowCatalogue` has been called twice.
+
+- [ ] **Step 2: Run the exact test and verify RED**
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx --testNamePattern 'refreshes the workflow catalogue when returning from the editor'
+```
+
+Expected: FAIL because the current catalogue remains fresh and is requested
+only once.
+
+### Task 2: Specify Activity Return Freshness
+
+**Files:**
+
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+
+- [ ] **Step 1: Add a production-cache remount test**
+
+Render Activity with a local `QueryClient` whose `staleTime` is `30_000`, wait
+for its first runs result, unmount and remount the page with the same client,
+then assert `listRuns` has been called twice.
+
+- [ ] **Step 2: Run the exact test and verify RED**
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx --testNamePattern 'refreshes runs when returning to activity'
+```
+
+Expected: FAIL because the fresh runs result is reused without a second call.
+
+### Task 3: Implement List-Owned Revalidation
+
+**Files:**
+
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.tsx`
+
+- [ ] **Step 1: Add the explicit mount policy to both queries**
+
+```ts
+refetchOnMount: 'always',
+```
+
+Place the option beside `retry: false` in the catalogue and runs query options.
+
+- [ ] **Step 2: Run both exact regression tests and verify GREEN**
+
+Run the two commands from Tasks 1 and 2. Expected: PASS.
+
+- [ ] **Step 3: Run the changed test files**
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx
+```
+
+Expected: both suites pass.
+
+### Task 4: Focused Verification And Delivery
+
+**Files:**
+
+- Verify only the files listed in the File Map.
+
+- [ ] **Step 1: Analyze the frontend change scope against the requested base**
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base origin/feat/2026-08-04_workflow-activity-vnext
+```
+
+- [ ] **Step 2: Run changed-file static checks and repository guards**
+
+Run the analyzer-approved Biome command, `bash tools/ci/test_stability_guards.sh`,
+the Workflow Activity vNext design-baseline verifier, and `git diff --check`.
+Expected: all pass.
+
+- [ ] **Step 3: Review and deliver**
+
+Stage only this task's files, commit with an imperative single-purpose message,
+push the branch, and create a PR targeting
+`feat/2026-08-04_workflow-activity-vnext`. Record all focused commands and state
+that full frontend validation is delegated to GitHub CI.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-11-workflow-activity-authoritative-contracts.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-11-workflow-activity-authoritative-contracts.md
new file mode 100644
index 0000000000..005df783b4
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-11-workflow-activity-authoritative-contracts.md
@@ -0,0 +1,270 @@
+---
+title: Workflow Activity authoritative contracts implementation plan
+status: approved
+owner: Aevatar frontend
+---
+
+# Workflow Activity Authoritative Contracts Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Replace Workflow Activity's client-side run inference with the completed #3250-#3252 backend feed, recovery, receipt, and lineage contracts.
+
+**Architecture:** Shared models and strict transport decoders own the backend contract. Activity consumes an infinite cursor query keyed by URL-backed server filters, while Run Detail consumes typed recovery and lineage directly from its existing detail query. Public run identities remain separate from technical actor addresses throughout routing and presentation.
+
+**Tech Stack:** React 19, TypeScript, Umi Max, Ant Design 6, TanStack Query 5, Jest, Testing Library, Biome.
+
+---
+
+### Task 1: Typed Feed, Recovery, Receipt, and Lineage Contracts
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/shared/models/workflowActivity.ts`
+- Modify: `apps/aevatar-console-web/src/shared/api/workflowActivityApi.ts`
+- Modify: `apps/aevatar-console-web/src/shared/api/workflowActivityApi.test.ts`
+
+- [ ] **Step 1: Add failing adapter tests**
+
+Add tests whose mocked response includes a paginated envelope and numeric protobuf enum values:
+
+```ts
+await expect(
+ workflowActivityApi.listActivityRuns('scope-alpha', {
+ workflowId: 'wf-alpha',
+ cursor: 'opaque-cursor',
+ includeTotalCount: true,
+ }),
+).resolves.toMatchObject({
+ hasMore: true,
+ nextCursor: 'next-cursor',
+ totalCount: 42,
+});
+
+expect(authFetchMock).toHaveBeenCalledWith(
+ expect.stringContaining('workflowId=wf-alpha'),
+ undefined,
+);
+```
+
+Also assert strict decoding for recovery capability, separate retry/fork and sub-workflow lineage, and required `newRunId` on a successful fork receipt.
+
+- [ ] **Step 2: Run the focused test and confirm RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web jest --runInBand src/shared/api/workflowActivityApi.test.ts
+```
+
+Expected: FAIL because `listActivityRuns`, the new model fields, and `newRunId` decoding do not exist.
+
+- [ ] **Step 3: Add explicit model types**
+
+Add numeric unions matching the protobuf JSON values and separate relationship structures:
+
+```ts
+export type WorkflowRecoveryEligibility = 0 | 1 | 2 | 3;
+export type WorkflowRecoveryRecommendedAction = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
+export type WorkflowRunLineageAvailability = 0 | 1 | 2 | 3;
+
+export interface WorkflowActivityRunFeedPage {
+ readonly items: readonly WorkflowActivityRunFeedRow[];
+ readonly nextCursor: string | null;
+ readonly hasMore: boolean;
+ readonly totalCount: number | null;
+}
+```
+
+Model the complete backend row, action capability, recovery capability, retry/fork lineage, sub-workflow lineage, and lineage run reference without collapsing identities.
+
+- [ ] **Step 4: Implement strict decoders and the additive API method**
+
+Add focused decoder functions and build the endpoint query without modifying `listRuns`:
+
+```ts
+listActivityRuns(scopeId, filter = {}) {
+ return requestActivityJson(
+ withQuery('/api/workflow/observatory/activity-runs', {
+ scope: scopeId.trim(),
+ workflowId: filter.workflowId?.trim(),
+ cursor: filter.cursor?.trim(),
+ includeTotalCount: filter.includeTotalCount,
+ take: filter.take,
+ }),
+ decodeActivityRunFeedPage,
+ );
+}
+```
+
+Extend `decodeDetail` with top-level `recoveryCapability` and `lineage`. Extend `decodeForkReceipt` with a required non-blank `newRunId` while retaining `newRunActorId` as technical data.
+
+- [ ] **Step 5: Re-run the adapter test and confirm GREEN**
+
+Run the Step 2 command. Expected: PASS.
+
+### Task 2: Activity Feed and Cursor Pagination
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.tsx`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+- Modify only if needed for responsive table density: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+
+- [ ] **Step 1: Replace list mocks with failing feed behavior tests**
+
+Use distinct identities in fixtures (`workflowId: 'wf-alpha'`, `runId: 'run-alpha'`, `actorId: 'actor-technical-alpha'`). Assert that:
+
+```ts
+expect(listActivityRunsMock).toHaveBeenCalledWith(
+ 'scope-alpha',
+ expect.objectContaining({
+ workflowId: 'wf-alpha',
+ includeTotalCount: true,
+ }),
+);
+expect(getWorkflowDetailMock).not.toHaveBeenCalled();
+```
+
+Add tests for authoritative initiator/input/failure/waiting/duration presentation, total and loaded counts, cursor-based Load more, preserved rows on next-page failure, refresh-from-start on malformed cursor, and cursor reset after a URL filter changes.
+
+- [ ] **Step 2: Run the Activity test and confirm RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web jest --runInBand src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx
+```
+
+Expected: FAIL because Activity still calls `listRuns`, resolves workflow ids through `scopesApi`, and has no pagination envelope.
+
+- [ ] **Step 3: Implement an infinite Activity query**
+
+Replace the workflow-resolution and list query with `useInfiniteQuery`:
+
+```ts
+const runs = useInfiniteQuery({
+ queryKey: ['workflow-activity-vnext', 'activity-runs', scopeId, filters],
+ initialPageParam: undefined as string | undefined,
+ queryFn: ({ pageParam }) =>
+ workflowActivityApi.listActivityRuns(scopeId, {
+ ...filters,
+ cursor: pageParam,
+ includeTotalCount: pageParam === undefined,
+ take: 50,
+ }),
+ getNextPageParam: (lastPage) =>
+ lastPage.hasMore ? lastPage.nextCursor ?? undefined : undefined,
+ retry: false,
+});
+```
+
+Flatten pages without deducing facts, apply `q` only to loaded rows, and keep first-page and next-page error states separate.
+
+- [ ] **Step 4: Render the authoritative operational row**
+
+Keep the existing responsive table and render workflow/run reference, status context, started/duration, initiator/source, redacted input summary, and exact-run action. Add loaded/total state and Load more. Do not render actor id or raw input.
+
+- [ ] **Step 5: Re-run the Activity test and confirm GREEN**
+
+Run the Step 2 command. Expected: PASS.
+
+### Task 3: Capability-Driven Recovery and Durable Lineage
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/runRecovery.test.ts`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/runRecovery.ts`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/RunDetailPage.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/RunDetailPage.tsx`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+- Modify only if needed for compact related-run presentation: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+
+- [ ] **Step 1: Rewrite recovery unit tests around backend capability**
+
+Delete graph/failed-step inference assertions. Add capability assertions:
+
+```ts
+expect(resolveRunRecovery(capability)).toEqual({
+ retry: expect.objectContaining({ enabled: true, startingStepId: 'step-failed' }),
+ runAgain: expect.objectContaining({ enabled: false, reason: 'Fix access first.' }),
+});
+```
+
+Cover eligible, ineligible, unavailable, missing starting step, reuse, cost, and recommended-action values.
+
+- [ ] **Step 2: Run the recovery test and confirm RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web jest --runInBand src/pages/workflow-activity-vnext/activity/runRecovery.test.ts
+```
+
+Expected: FAIL because `resolveRunRecovery` still accepts steps and graph candidates.
+
+- [ ] **Step 3: Implement the pure capability presentation helper**
+
+Accept only `WorkflowRunRecoveryCapability`. Return two presentations containing enabled state, reason, recommended actions, starting step, reuse, cost, revision id, and revision version. Treat eligible-without-starting-step as unavailable rather than inferring a step.
+
+- [ ] **Step 4: Add failing Run Detail behavior tests**
+
+Assert focusable `aria-disabled` unavailable actions and their reason, modal revision/start/reuse/cost disclosure, fork request input and starting step, accepted-vs-completed wording, Open new run navigation to `run-new`, and no route containing `actor-new`.
+
+Add separate lineage assertions for retry history and sub-workflows, including source/original/parent/root/child run links and absence of actor ids from default content.
+
+- [ ] **Step 5: Run the Run Detail test and confirm RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web jest --runInBand src/pages/workflow-activity-vnext/activity/RunDetailPage.test.tsx
+```
+
+Expected: FAIL because the page still infers recovery and opens general Activity after a fork.
+
+- [ ] **Step 6: Implement recovery confirmation, receipt navigation, and lineage**
+
+Drive actions exclusively from `run.recoveryCapability`. Preserve graph loading only for the Graph tab. Confirm immutable-new-run semantics before fork. Route accepted receipts and lineage through:
+
+```ts
+history.push(buildWorkflowActivityRunHref(scopeId, receipt.newRunId));
+```
+
+Render retry/fork and sub-workflow relationships as separate unframed sections. Keep actor ids in Technical details only.
+
+- [ ] **Step 7: Re-run recovery and Run Detail tests and confirm GREEN**
+
+Run both commands from Steps 2 and 5. Expected: PASS.
+
+### Task 4: Focused Verification and Delivery
+
+**Files:**
+- Review every file changed from `origin/feat/2026-08-04_workflow-activity-vnext`
+
+- [ ] **Step 1: Run the frontend scope analyzer**
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base origin/feat/2026-08-04_workflow-activity-vnext
+```
+
+Read `~/.codex/skills/frontend-incremental-pr/references/framework-commands.md` after the analyzer output.
+
+- [ ] **Step 2: Run every changed test and dependency-related focused test**
+
+At minimum rerun the three commands from Tasks 1-3 together with any additional tests reported by the analyzer. Do not run the complete Jest suite.
+
+- [ ] **Step 3: Run changed-file static checks only**
+
+Use the analyzer's `staticCheckFiles` with Biome. Run TypeScript only if the repository exposes a reliable affected target; otherwise state that GitHub CI owns full type verification. Do not run a local production build.
+
+- [ ] **Step 4: Run the required test stability guard**
+
+```bash
+bash tools/ci/test_stability_guards.sh
+```
+
+- [ ] **Step 5: Review, commit, push, and create the PR**
+
+Inspect the complete diff, stage only this task's files, use imperative focused commits, push `feat/2026-08-11_workflow-activity-contracts`, and create a PR targeting `feat/2026-08-04_workflow-activity-vnext`. Include exact focused commands and state that full frontend suite/typecheck/build are delegated to GitHub CI.
+
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-11-workflow-schedule-design.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-11-workflow-schedule-design.md
new file mode 100644
index 0000000000..84e8d2df17
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-11-workflow-schedule-design.md
@@ -0,0 +1,131 @@
+# Workflow Schedule vNext Design Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use
+> superpowers:subagent-driven-development (recommended) or
+> superpowers:executing-plans to implement this plan task-by-task. Steps use
+> checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Align the six-scene Workflow Schedule design with the existing
+workflow-scoped backend facade from issue #3446.
+
+**Architecture:** A Schedule is an exact child of
+`scopeId + workflowId`. The browser sends workflow-oriented preview and
+configuration requests; the backend resolves published service, active
+revision, and owner binding. Accepted mutations refresh the same Workflow's
+Schedule list or detail before the UI claims final state.
+
+**Tech Stack:** Markdown, deterministic Python Excalidraw generation, Pillow
+PNG rendering, static HTML/CSS/JavaScript, and the baseline verifier.
+
+---
+
+## File Map
+
+- Modify:
+ `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/aevatar-workflow-schedule-design.gen.py`
+- Regenerate:
+ `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/aevatar-workflow-schedule-design.excalidraw`
+- Modify:
+ `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype.html`
+- Modify:
+ `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/render-schedule-png.py`
+- Modify:
+ `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py`
+- Modify:
+ `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/README.md`
+- Modify:
+ `apps/aevatar-console-web/docs/superpowers/specs/2026-08-11-workflow-schedule-design.md`
+- Modify:
+ `apps/aevatar-console-web/docs/superpowers/plans/2026-08-11-workflow-schedule-design.md`
+- Replace:
+ `schedule-authorization-review.png` with `schedule-review.png`
+- Regenerate:
+ `schedule-workflows-list-modal.png`,
+ `schedule-workflow-editor-panel.png`,
+ `schedule-creation-pending.png`, `schedule-detail.png`, and
+ `schedule-edit.png`
+
+The obsolete combined PNGs and authorization-review PNG must not exist.
+
+## Task 1: Lock The Workflow Resource Contract
+
+- [x] Treat `scopeId + workflowId` as the Schedule collection owner.
+- [x] Keep `scheduleId` as the child resource identity.
+- [x] Remove Team/Member requirements and visible published-service identity
+ from the Schedule flow.
+- [x] Keep Team Automation as a separate product resource.
+- [x] Keep Schedule independent from Activity and the Workflow graph.
+
+## Task 2: Add The Failing Semantic Contract
+
+- [x] Require the workflow-scoped collection, preview, create, detail, update,
+ enable, disable, run-now, and delete routes in
+ `verify-baseline.py`.
+- [x] Require `displayName`, `cronExpression`, `timezone`, `enabled`,
+ and `prompt` request fields.
+- [x] Reject Team/Member, generic `/api/schedules`, preflight, credential,
+ grant, policy, and reauthorization concepts.
+- [x] Run the old baseline and confirm it fails because `Review schedule` is
+ missing.
+
+## Task 3: Update The Six Design Scenes
+
+The Schedule source must contain exactly:
+
+```text
+01 · Workflows — quick schedule modal
+02 · Workflow — schedule setup panel
+03 · Schedule — review before creation
+04 · Schedule — creation pending
+05 · Workflow — schedule detail
+06 · Workflow — change schedule
+```
+
+- [x] Keep Workflow-list creation in a modal and editor creation in the right
+ panel.
+- [x] Use the same name, recurrence, timezone, prompt, and enabled fields in
+ both containers.
+- [x] Replace authorization review with a normal creation review containing
+ five server-returned fire times.
+- [x] Show `202 Accepted` and Workflow Schedule refresh without optimistic
+ Active or next-fire state.
+- [x] Limit detail to API-backed Schedule facts and run-now, change,
+ enable/disable, and delete actions.
+- [x] Make the edit scene visibly preserve the observed enabled state.
+
+## Task 4: Migrate The Interactive Prototype
+
+- [x] Use `configure -> previewing -> review -> accepted` for both entry
+ containers.
+- [x] Key Schedule fixtures by exact `scopeId + workflowId`.
+- [x] Model request objects with the workflow-oriented API fields.
+- [x] Model list/detail/update and all colon action routes.
+- [x] Preserve `schedule.enabled` in the update request.
+- [x] Refresh list/detail after every accepted mutation.
+- [x] Keep draft or unpublished Workflows unavailable without falling back to
+ Team Automation.
+
+## Task 5: Render And Inspect Standalone PNGs
+
+- [x] Rename the third PNG to `schedule-review.png`.
+- [x] Render all six scenes at `1440x900`.
+- [x] Keep timezone, Schedule name, recurrence, and preview values consistent
+ across scenes.
+- [x] Inspect every PNG for blank content, clipping, overlap, and stale
+ authorization content.
+- [x] Bind each PNG to the current source and renderer hashes.
+
+## Task 6: Focused Verification And Delivery
+
+- [x] Run the system and bundled-Pillow baseline verifier.
+- [x] Parse inline JavaScript from both prototype HTML files.
+- [x] Run the frontend scope analyzer against
+ `origin/feat/2026-08-04_workflow-activity-vnext`.
+- [x] Run only analyzer-related Jest and changed-file static checks.
+- [x] Run docs lint, Python bytecode compilation, and `git diff --check`.
+- [x] Review the full diff and stage only current task files.
+- [ ] Commit, push, and update Draft PR #3421 without removing
+ `[DO NOT MERGE]`.
+
+The full frontend suite, typecheck, and production build remain delegated to
+GitHub CI by the personal frontend validation policy.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-20-workflow-schedule-quick-modal.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-20-workflow-schedule-quick-modal.md
new file mode 100644
index 0000000000..79341076f2
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-20-workflow-schedule-quick-modal.md
@@ -0,0 +1,149 @@
+# Workflow Schedule Quick Modal Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task with verification checkpoints.
+
+**Goal:** Make Workflow Schedule management match the approved Schedule design baseline by opening a management modal from the catalogue, exposing existing Schedules for editing, and sharing one configure/preview/review/create flow with the editor panel.
+
+**Architecture:** Keep `WorkflowScheduleSurface` as the single owner of Schedule query and mutation state. The catalogue entry opens the same list management view as the editor Drawer; `New schedule` enters the surface-local state machine (`configure`, `previewing`, `review`). After create the catalogue closes with a Toast, while the editor returns to list management with the same Toast. Continue using only workflow-scoped Schedule APIs.
+
+**Tech Stack:** React, TypeScript, Ant Design, TanStack Query, Jest Testing Library, existing Workflow vNext CSS tokens and i18n helpers.
+
+---
+
+### Task 1: Lock the intended entry point and state machine with tests
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [x] **Step 1: Add a failing catalogue test for the Schedule management modal**
+
+ Render the published catalogue row, click `Manage schedules for Published workflow`, and assert that the visible dialog shows `Schedules`, `No schedules yet`, and `New schedule`, without entering the creation form.
+
+- [x] **Step 2: Add failing Schedule-surface tests for configure and review states**
+
+ Assert that the configure view renders `How often`, `Run input (optional)`, the repeat controls, and a `Review schedule` button. Select the custom cron path or use the default weekday preset, click review, resolve `workflowScheduleApi.preview`, and assert the returned five fire times plus `Create schedule` are shown.
+
+- [x] **Step 3: Run the focused tests and confirm RED**
+
+ Run:
+
+ ```bash
+ CI=1 pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx src/pages/workflow-activity-vnext/index.test.tsx --testNamePattern='schedule|Schedule'
+ ```
+
+ Expected result: FAIL because the catalogue currently opens the list modal, the surface renders a nested Create schedule Modal, and no review state exists.
+
+### Task 2: Implement the shared configure/review state machine
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.tsx`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+
+- [x] **Step 1: Replace the nested form Modal state with explicit creation state**
+
+ Add typed state for `creationStep`, `repeatPreset`, `repeatTime`, `cronMode`, and the existing API form fields. Keep list/detail mutations unchanged. Initialize the modal surface in `configure` and initialize panel creation from the list view.
+
+- [x] **Step 2: Render the design-aligned configure view**
+
+ Render the blue Workflow context block, Schedule name, `How often` repeat/time/timezone controls, `write it as cron instead` disclosure, `Run input (optional)`, and footer actions `Cancel` plus `Review schedule`. Map presets to five-field cron without calculating fire times in the browser.
+
+- [x] **Step 3: Render previewing and review states**
+
+ `Review schedule` calls `workflowScheduleApi.preview(scopeId, workflowId, { cronExpression, timezone, count: 5 })`. Previewing shows a pending state, errors return to configure while preserving fields, review shows the Workflow/name/repeat/timezone/enabled/prompt/five returned fire times, and create submits the existing `WorkflowScheduleConfigurationInput`. After `202 Accepted`, close the catalogue modal or return the editor panel to its list, show a Toast, and continue refreshing the workflow-scoped list.
+
+- [x] **Step 4: Add only the required localized labels and messages**
+
+ Add matching English and Chinese keys for the human repeat builder, context/status labels, review actions, accepted state, and explanation text. Do not add Team/member/service identity copy.
+
+- [x] **Step 5: Run the focused Schedule tests and confirm GREEN**
+
+ Run:
+
+ ```bash
+ CI=1 pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx
+ ```
+
+ Expected result: all Schedule surface tests pass.
+
+### Task 3: Make the catalogue entry open the Schedule management modal
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowsPage.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+
+- [x] **Step 1: Pass a direct-create intent to the modal surface**
+
+ Keep the existing workflow identity and availability guard, but make the catalogue `Schedules` action render the surface in `list` mode. The list exposes `New schedule` and existing row actions; the editor panel continues to use the same list state.
+
+- [x] **Step 2: Update the catalogue regression assertion**
+
+ Assert that clicking the published row Schedules action opens exactly one dialog with `Schedules`, `No schedules yet`, and `New schedule`, while `Review schedule` is absent until the user chooses to create one.
+
+- [x] **Step 3: Run the targeted catalogue test**
+
+ ```bash
+ CI=1 pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx --testNamePattern='direct.*schedule|schedule.*quick|Schedule'
+ ```
+
+ Expected result: the targeted catalogue tests pass.
+
+### Task 4: Align the modal and panel presentation with the design baseline
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.tsx`
+
+- [x] **Step 1: Add dedicated schedule quick-modal tokens and layout rules**
+
+ Match the baseline proportions: 820px modal, compact header, blue context panel, two-column form grid on desktop, stacked controls on mobile, section dividers, review and next-fire panels, and a footer with one primary action.
+
+- [x] **Step 2: Scope portal tokens and focus styles**
+
+ Keep the existing Modal/Drawer portal token scope, but apply it to the new quick surface and make the close control use a restrained 2px focus indicator without the oversized blue rectangle.
+
+- [x] **Step 3: Verify the editor panel reuses the same flow**
+
+ Open the editor Schedule panel, enter New schedule, move to Review schedule, go back, and confirm the canvas remains visible and no nested Modal is created.
+
+### Task 5: Focused verification and delivery
+
+**Files:**
+- Verify only the files changed by Tasks 1-4.
+
+- [x] **Step 1: Run the affected test files**
+
+ ```bash
+ CI=1 pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx src/pages/workflow-activity-vnext/index.test.tsx --testNamePattern='schedule|Schedule'
+ CI=1 pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/shared/api/workflowScheduleApi.test.ts
+ ```
+
+- [x] **Step 2: Run changed-file static checks**
+
+ ```bash
+ pnpm --dir apps/aevatar-console-web exec biome check src/locales/workflowActivityVNextMessages.en-US.ts src/locales/workflowActivityVNextMessages.zh-CN.ts src/pages/workflow-activity-vnext/index.test.tsx src/pages/workflow-activity-vnext/styles.ts src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.tsx
+ git diff --check
+ ```
+
+- [x] **Step 3: Inspect the existing Chrome tab at `http://127.0.0.1:5175`**
+
+ Verify the direct catalogue quick modal, configure/review transition, mobile stacking if available, and editor panel reuse against the Schedule PNG baseline.
+
+- [x] **Step 4: Review, stage only current-task files, commit, push, and update PR #3498**
+
+ Commit message: `Align workflow schedule creation with design`
+
+ Record exact focused commands and results in PR #3498. Full frontend suite, typecheck, and production build remain delegated to GitHub CI.
+
+### Verification record
+
+- Focused rendered Jest: 2 suites passed, 9 matching tests passed, 113 tests skipped (`CI=1 pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx src/pages/workflow-activity-vnext/index.test.tsx --testNamePattern='schedule|Schedule'`).
+- Workflow Schedule API Jest: 1 suite passed, 3 tests passed (`CI=1 pnpm --dir apps/aevatar-console-web exec jest --runInBand --runTestsByPath src/shared/api/workflowScheduleApi.test.ts`).
+- Changed-file Biome: 6 files checked with no fixes.
+- `bash tools/ci/test_stability_guards.sh`: passed.
+- `git diff --check HEAD~3..HEAD`: passed.
+- `python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py`: passed; 17/17 baseline frames, 6/6 Schedule frames, 6 standalone 1440x900 Schedule PNGs, and byte-identical generator output.
+- Chrome visual verification: desktop and 390px mobile Catalogue quick modal, editor Schedule Drawer reuse, and Review state with five returned fire times; no horizontal overflow at 390px. No Schedule was created.
+- Accepted create observation: React Query refetches the workflow-scoped list every second after `202 Accepted` and stops only when the receipt `scheduleId` appears in the read model; the regression test covers two misses followed by an observed item.
+- Full frontend suite, full typecheck, and production build remain delegated to GitHub CI under the local incremental validation policy.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-21-workflow-schedule-history-implementation.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-21-workflow-schedule-history-implementation.md
new file mode 100644
index 0000000000..647e9d43b5
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-21-workflow-schedule-history-implementation.md
@@ -0,0 +1,105 @@
+# Workflow Schedule Management and History Implementation Plan
+
+> Execute this plan with `superpowers:executing-plans`, `superpowers:test-driven-development`, and `frontend-incremental-pr`.
+
+**Goal:** Replace the flat Schedule detail surface with the approved List -> Overview/History -> Edit state model and expose Schedule-origin filter context in Activity without changing backend contracts.
+
+**Architecture:** Keep `WorkflowScheduleSurface` as the single Workflow-scoped Schedule owner. The existing Schedule detail query remains the authoritative source for both Overview and bounded recent attempts, while Activity remains the only owner of actual Workflow Runs. UI state distinguishes the selected Schedule, active detail tab, and temporary edit mode; it never derives a Run identity from Schedule attempt metadata.
+
+**Stack:** React, TypeScript, Ant Design, TanStack Query, Jest, Testing Library, Umi history/i18n.
+
+---
+
+## Task 1: Approve the baseline and establish the selected-Schedule shell
+
+**Files:**
+- Modify: `apps/aevatar-console-web/docs/superpowers/specs/2026-08-21-workflow-schedule-history-design.md`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.tsx`
+
+1. Change the focused specification status from awaiting review to approved.
+2. Replace the legacy flat-detail test with a failing test proving that selecting a Schedule renders its name and owning Workflow in the stable header, defaults to Overview, hides History content until selected, and returns to the list through the header Back control.
+3. Run only the named Jest test and confirm it fails for the missing approved shell.
+4. Add an `overview | history` tab state that resets to Overview whenever a Schedule is newly selected; keep Edit as the existing temporary form mode.
+5. Render a stable selected-Schedule title with header Back and the container's existing Close control. Remove the footer Back action.
+6. Re-run the named test and confirm it passes.
+
+## Task 2: Implement the Overview information hierarchy and actions
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.tsx`
+
+1. Add a failing test for the approved Overview order and optional-data rules: status, human recurrence, timezone, next scheduled time, last attempt, counts, non-empty Run input, and collapsed raw cron.
+2. In the same test, prove Run now and Edit schedule are direct actions while Pause/Enable and Delete schedule live in More; confirm Delete opens a confirmation naming the Schedule.
+3. Run the named test and confirm the expected failure.
+4. Extract reusable recurrence and application-locale date presentation helpers. Format dates with `getLocale()` and the Schedule timezone, with a safe fallback for invalid timezone values.
+5. Refactor the collection rows to show human recurrence, timezone, and next scheduled time, with the row as the only selection target.
+6. Render the Overview facts and action hierarchy. Keep raw cron in collapsed Advanced details and omit empty Run input.
+7. Use accepted-operation Toast feedback without implementation narration. Keep authoritative data refresh through the existing Workflow-scoped query prefix.
+8. Re-run the named test and confirm it passes.
+
+## Task 3: Implement recent-attempt History and Activity handoff
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.tsx`
+
+1. Add a failing test that opens History and expects the compact columns Scheduled time, Source, Result, and Completed time.
+2. Assert scheduled/manual and succeeded/failed mappings, generic failed-attempt copy, raw error only under Technical details, no guessed per-attempt Run link, and the empty `No attempts yet` state.
+3. Assert `View related runs in Activity` closes Schedule management and pushes exactly `workflowId`, `schedule`, and `origin=schedule`.
+4. Run the named test and confirm the expected failure.
+5. Implement the History table from `recentFires` without deriving new categories or identities.
+6. Add tab-specific request failure copy and Retry while preserving the selected Schedule and active tab.
+7. Implement the Activity handoff through the shared history adapter.
+8. Re-run the named test and confirm it passes.
+
+## Task 4: Make Activity filter context visible and removable
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.tsx`
+
+1. Add a failing test for a Schedule-origin Activity URL that proves the Workflow, Schedule, and Source filters are all visible and the API receives the existing typed filters.
+2. Assert that each visible context control removes only its owned query parameter.
+3. Run the named test and confirm the expected failure.
+4. Extend the existing filter-context strip with Schedule and human-readable Source controls; reuse the current URL replacement path and do not add a new API.
+5. Re-run the named test and confirm it passes.
+
+## Task 5: Align durable copy and styling
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx`
+
+1. Add or update locale messages for Overview, History, attempt source/result, failure summaries, More actions, Activity filter context, and accepted Toasts in both locales.
+2. Replace legacy Schedule detail/fire styles with the approved compact tabbed layout, stable fact grid, action hierarchy, table, disclosure, and responsive behavior.
+3. Run the full `WorkflowScheduleSurface.test.tsx` and `ActivityPage.test.tsx` files only.
+4. Fix only failures attributable to these changed behaviors and rerun the same focused files.
+
+## Task 6: Focused validation and PR delivery
+
+**Files:**
+- Review all files changed by Tasks 1-5.
+- Keep `.planning/` untracked and unstaged.
+
+1. Run the frontend scope analyzer with `--base origin/feat/2026-08-04_workflow-activity-vnext`.
+2. Run every analyzer-selected related test plus the two explicitly changed test files; do not run the full frontend suite.
+3. Run Biome only on analyzer `staticCheckFiles`. Do not run full `tsc` or a production build; delegate unavailable affected typechecking and the full suite/build to GitHub CI.
+4. Run `bash tools/ci/test_stability_guards.sh` because frontend tests changed.
+5. Run documentation lint for the modified specification and `git diff --check`.
+6. Review the complete base diff for semantic drift, unrelated changes, leaked backend identifiers, false Run claims, placeholders, and responsive/accessibility regressions.
+7. Stage only current task files, commit with an imperative focused message, push `feat/2026-08-20_workflow-schedule-frontend`, and update Draft PR #3498.
+8. Preserve `[DO NOT MERGE]`, Draft status, and base `feat/2026-08-04_workflow-activity-vnext`; record exact focused commands and state that full frontend verification is delegated to GitHub CI.
+
+## Implementation Status
+
+- Tasks 1–5: completed. The selected-Schedule shell, Overview, bounded History,
+ Activity handoff, locale copy, and responsive styles are implemented.
+- Task 6 focused tests: passed for `WorkflowScheduleSurface.test.tsx`,
+ `ActivityPage.test.tsx`, `index.test.tsx`, and `workflowScheduleApi.test.ts`.
+- Task 6 static checks: Biome, test stability guards, documentation lint, and
+ `git diff --check` passed. Full frontend typecheck, suite, and production
+ build remain delegated to GitHub CI under the incremental PR policy.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-21-workflow-schedule-recent-fires.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-21-workflow-schedule-recent-fires.md
new file mode 100644
index 0000000000..fed6600e1b
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-21-workflow-schedule-recent-fires.md
@@ -0,0 +1,228 @@
+# Workflow Schedule Recent Fires Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Restore the approved Schedule detail state so users can inspect authoritative recent fire history before changing a Schedule.
+
+**Architecture:** Keep `WorkflowScheduleSurface` as the single Schedule management state machine and add a `detail` view between the collection and edit form. The detail view reads `workflowScheduleApi.get(scopeId, workflowId, scheduleId)` through TanStack Query, renders only returned summary and `recentFires` facts, and reuses the existing mutation functions. Editing starts from the observed detail and Cancel returns to detail.
+
+**Tech Stack:** React 19, TypeScript, Ant Design, TanStack Query, Jest, Testing Library, existing Workflow vNext tokens and locale catalogues.
+
+---
+
+### Task 1: Protect Schedule detail and recent-fire behavior
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx`
+
+- [ ] **Step 1: Extend the existing Schedule fixture with an authoritative detail response**
+
+ Add one reusable `WorkflowScheduleSummary` fixture and configure `workflowScheduleApi.get` to return:
+
+ ```ts
+ {
+ schedule: scheduleSummary,
+ recentFires: [
+ {
+ scheduledFireAt: '2026-08-20T01:00:00Z',
+ completedAt: '2026-08-20T01:02:00Z',
+ idempotencyKey: 'schedule-alpha:fire:1',
+ error: '',
+ manual: false,
+ },
+ {
+ scheduledFireAt: '2026-08-19T03:00:00Z',
+ completedAt: '2026-08-19T03:01:00Z',
+ idempotencyKey: 'schedule-alpha:manual:1',
+ error: 'Workflow invocation failed',
+ manual: true,
+ },
+ ],
+ }
+ ```
+
+- [ ] **Step 2: Write the failing detail-to-edit integration test**
+
+ Render the management modal with `initialView="list"`, click `View Daily workflow run`, and assert:
+
+ ```ts
+ expect(workflowScheduleApi.get).toHaveBeenCalledWith(
+ 'scope-alpha',
+ 'wf-alpha',
+ 'schedule-alpha',
+ );
+ expect(screen.getByText('Schedule details')).toBeVisible();
+ expect(screen.getByText('Recent fires')).toBeVisible();
+ expect(screen.getByText('Succeeded')).toBeVisible();
+ expect(screen.getByText('Failed')).toBeVisible();
+ expect(screen.getByText('Scheduled')).toBeVisible();
+ expect(screen.getByText('Manual')).toBeVisible();
+ expect(screen.getByText('Workflow invocation failed')).toBeVisible();
+ ```
+
+ Then click `Change schedule`, assert the editable form is visible, click `Cancel`, and assert the same Schedule detail and recent fires are visible again.
+
+- [ ] **Step 3: Write the failing empty/error distinction test**
+
+ For a successful detail response with `recentFires: []`, assert `No fires yet`. For a rejected detail request, assert `Schedule details couldn't be loaded`, a `Retry` action, and absence of `No fires yet`; clicking Retry must call the exact detail API again.
+
+- [ ] **Step 4: Run the new tests and verify RED**
+
+ Run:
+
+ ```bash
+ CI=1 pnpm --dir apps/aevatar-console-web exec jest \
+ --runInBand \
+ --runTestsByPath src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx \
+ --testNamePattern='detail|recent fires'
+ ```
+
+ Expected: FAIL because the current list exposes `Edit`, never calls `workflowScheduleApi.get`, and has no detail/history view.
+
+### Task 2: Restore the Schedule detail state
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+
+- [ ] **Step 1: Add typed detail selection and query state**
+
+ Extend the local state without adding another data owner:
+
+ ```ts
+ type ScheduleSurfaceView = 'list' | 'detail' | 'form';
+
+ const [selectedSchedule, setSelectedSchedule] =
+ React.useState(null);
+
+ const scheduleDetail = useQuery({
+ enabled: open && available && Boolean(selectedSchedule),
+ queryKey: [...queryKey, 'detail', selectedSchedule?.scheduleId],
+ queryFn: () =>
+ workflowScheduleApi.get(
+ scopeId,
+ workflowId,
+ selectedSchedule?.scheduleId ?? '',
+ ),
+ retry: false,
+ });
+ ```
+
+ Reset `selectedSchedule` when the surface opens for another Workflow. `openDetail(schedule)` selects the exact summary and enters `detail`; `openEdit()` hydrates from `scheduleDetail.data.schedule` and enters `form`.
+
+- [ ] **Step 2: Render backend-honest detail states**
+
+ Replace the list's pencil action with an eye action named `View {name}`. The detail view must render:
+
+ ```text
+ Schedule details
+ Workflow and enabled state
+ Schedule name
+ Next fire / Last fire
+ Total fires / Failed fires
+ Cron expression / Timezone
+ Run input
+ Recent fires
+ ```
+
+ Each recent fire uses `error.trim()` to choose `Failed` or `Succeeded`, uses `manual` to choose `Manual` or `Scheduled`, and formats only `scheduledFireAt` and `completedAt` returned by the API. Do not display the idempotency key or derive a Run URL.
+
+ Loading uses a stable status region. A successful empty array renders `No fires yet`. A rejected detail query renders an inline retryable error and never the empty state.
+
+- [ ] **Step 3: Keep detail, edit, and mutation transitions coherent**
+
+ `Change schedule` enters edit from `scheduleDetail.data.schedule`. Cancel from edit returns to detail. Back from detail returns to the Schedule list. Update returns to detail and refreshes the list/detail query prefix. Enable/disable and run-now refresh detail in place. Delete refreshes authoritatively and then returns to the list.
+
+- [ ] **Step 4: Add localized operational copy**
+
+ Add matching English and Chinese entries for:
+
+ ```text
+ Schedule details
+ View {name}
+ Change schedule
+ Loading schedule details…
+ Schedule details couldn't be loaded
+ Recent fires
+ No fires yet
+ Total fires
+ Failed fires
+ Last fire
+ Scheduled / Manual
+ Succeeded / Failed
+ Scheduled {date}
+ Completed {date}
+ Back to schedules
+ ```
+
+- [ ] **Step 5: Add compact detail styles using existing tokens**
+
+ Add `wa-vnext__schedule-detail-*` classes for a two-column facts grid, a bordered recent-fire list, status/source rows, wrapped error text, and a footer. Collapse the facts grid to one column below the existing mobile breakpoint; do not add nested cards or new color tokens.
+
+- [ ] **Step 6: Run the focused component tests and verify GREEN**
+
+ Run:
+
+ ```bash
+ CI=1 pnpm --dir apps/aevatar-console-web exec jest \
+ --runInBand \
+ --runTestsByPath src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx
+ ```
+
+ Expected: one suite passes, including detail success, empty, failure/retry, edit/Cancel, creation, and mutation behavior.
+
+### Task 3: Verify the owning route and frontend boundaries
+
+**Files:**
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/index.test.tsx`
+- Test: `apps/aevatar-console-web/src/shared/api/workflowScheduleApi.test.ts`
+- Verify: changed source, test, locale, style, spec, and plan files
+
+- [ ] **Step 1: Run the catalogue integration test file**
+
+ ```bash
+ CI=1 pnpm --dir apps/aevatar-console-web exec jest \
+ --runInBand \
+ --runTestsByPath src/pages/workflow-activity-vnext/index.test.tsx
+ ```
+
+ Expected: the published Workflow still opens one Schedule management modal and all tests pass.
+
+- [ ] **Step 2: Run the existing Workflow Schedule adapter test**
+
+ ```bash
+ CI=1 pnpm --dir apps/aevatar-console-web exec jest \
+ --runInBand \
+ --runTestsByPath src/shared/api/workflowScheduleApi.test.ts
+ ```
+
+ Expected: the exact Workflow-scoped detail route and `recentFires` decoder remain passing without adapter changes.
+
+- [ ] **Step 3: Run changed-file static and repository checks**
+
+ ```bash
+ pnpm --dir apps/aevatar-console-web exec biome check \
+ src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.tsx \
+ src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx \
+ src/pages/workflow-activity-vnext/styles.ts \
+ src/locales/workflowActivityVNextMessages.en-US.ts \
+ src/locales/workflowActivityVNextMessages.zh-CN.ts
+ bash tools/ci/test_stability_guards.sh
+ bash tools/docs/lint.sh
+ git diff --check
+ ```
+
+ Expected: all commands exit 0. Do not run full frontend tests, package-wide typecheck, or production build locally; GitHub CI owns those under the incremental frontend policy.
+
+- [ ] **Step 4: Review, commit, push, and update the existing PR**
+
+ Run the frontend scope analyzer with base `origin/feat/2026-08-04_workflow-activity-vnext`, inspect the complete diff, stage only this task's files, and commit:
+
+ ```bash
+ git commit -m "Show workflow schedule recent fires"
+ git push origin feat/2026-08-20_workflow-schedule-frontend
+ ```
+
+ Append the exact focused commands and results to PR #3498 while preserving its Draft and `[DO NOT MERGE]` state. Leave `.planning/` untracked.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-activity-filter-spacing.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-activity-filter-spacing.md
new file mode 100644
index 0000000000..55afef65f6
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-activity-filter-spacing.md
@@ -0,0 +1,137 @@
+# Activity Filter Spacing Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Separate URL-backed Activity scope chips from editable query controls with a conditional 12px vertical interval.
+
+**Architecture:** Keep the existing filter state, URL ownership, query payload, and toolbar unchanged. Give the already-conditional scope-chip `Space` an Activity-specific class and let that class own the inter-group interval, so no chips means no extra element or blank space. Lock the contract in the Activity component test and verify the rendered geometry in the user's existing Chrome tab.
+
+**Tech Stack:** React, TypeScript, Ant Design `Space`, CSS-in-TypeScript, Jest, Testing Library, Chrome browser verification.
+
+---
+
+### Task 1: Prove The Missing Context Interval
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+
+- [ ] **Step 1: Add a rendered-structure assertion for scoped Activity**
+
+Extend `filters by Schedule without inventing a Run source` after the two scope buttons become visible:
+
+```tsx
+const scopeContext = document.querySelector(
+ '.wa-vnext__activity-filter-context',
+);
+expect(scopeContext).toBeInTheDocument();
+expect(scopeContext).toContainElement(
+ screen.getByRole('button', { name: 'Remove workflow filter wf-alpha' }),
+);
+expect(scopeContext).toContainElement(
+ screen.getByRole('button', {
+ name: 'Remove schedule filter schedule-alpha',
+ }),
+);
+```
+
+This asserts the semantic owner of the spacing rather than a browser-specific computed pixel value.
+
+- [ ] **Step 2: Assert the CSS interval contract**
+
+Import `workflowActivityVNextCss` from `../styles` and add one focused assertion:
+
+```tsx
+expect(workflowActivityVNextCss).toContain(
+ '.wa-vnext__activity-filter-context { margin-bottom: 12px; }',
+);
+```
+
+- [ ] **Step 3: Run the exact Activity test and confirm RED**
+
+Run from `apps/aevatar-console-web`:
+
+```bash
+pnpm exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx
+```
+
+Expected: FAIL because the context `Space` has no Activity-specific class and the CSS does not define the 12px interval.
+
+### Task 2: Implement The Conditional Spacing Owner
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+
+- [ ] **Step 1: Classify the existing conditional scope group**
+
+Change only the existing `filterContext` root:
+
+```tsx
+
+```
+
+Do not wrap the chips in another card or container and do not change their clear actions.
+
+- [ ] **Step 2: Add the stable interval**
+
+Add beside the shared toolbar rules:
+
+```css
+.wa-vnext__activity-filter-context { margin-bottom: 12px; }
+```
+
+Do not add margin to `.wa-vnext__toolbar`; Workflows and editor toolbars do not own Activity scope context. Because `filterContext` remains conditionally rendered, unscoped Activity reserves no extra space.
+
+- [ ] **Step 3: Run the exact Activity test and confirm GREEN**
+
+```bash
+pnpm exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx
+```
+
+Expected: PASS with the class present around both chips and the 12px CSS contract defined.
+
+### Task 3: Focused Verification And Delivery
+
+**Files:**
+- Review all files changed by Tasks 1-2 and the committed Activity specification.
+
+- [ ] **Step 1: Analyze affected frontend scope**
+
+Run from the repository root:
+
+```bash
+python3 /Users/abigaildeng/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base origin/feat/2026-08-04_workflow-activity-vnext
+```
+
+- [ ] **Step 2: Run related and changed tests**
+
+Run from `apps/aevatar-console-web`:
+
+```bash
+pnpm exec jest --findRelatedTests src/pages/workflow-activity-vnext/activity/ActivityPage.tsx src/pages/workflow-activity-vnext/styles.ts --runInBand
+pnpm exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx
+```
+
+Do not run a bare package test command, full typecheck, or production build.
+
+- [ ] **Step 3: Run changed-file static checks and guards**
+
+```bash
+pnpm exec biome check src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx src/pages/workflow-activity-vnext/activity/ActivityPage.tsx src/pages/workflow-activity-vnext/styles.ts
+bash tools/docs/lint.sh
+bash tools/ci/test_stability_guards.sh
+git diff --check
+curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5175/
+```
+
+Expected: all commands exit 0 and the frontend returns HTTP 200. Full frontend suite, typecheck, and production build remain delegated to GitHub CI.
+
+- [ ] **Step 4: Verify geometry in the existing Chrome tab**
+
+Reuse the claimed local Activity tab. Confirm the URL and filter behavior are unchanged, measure 12px between `.wa-vnext__activity-filter-context` and `.wa-vnext__toolbar`, inspect the desktop screenshot, and verify no new tab/window/browser was opened.
+
+- [ ] **Step 5: Commit, push, and update PR #3498**
+
+Stage only the implementation plan, Activity component/test, and styles. Commit with an imperative message, push `feat/2026-08-20_workflow-schedule-frontend`, and append exact verification evidence to PR #3498 while preserving Draft, `[DO NOT MERGE]`, and base `feat/2026-08-04_workflow-activity-vnext`.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-activity-search-feedback.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-activity-search-feedback.md
new file mode 100644
index 0000000000..a2dd9f6187
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-activity-search-feedback.md
@@ -0,0 +1,131 @@
+# Activity Search Feedback Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Give the Activity Search command immediate, request-owned loading feedback without hiding committed ledger rows or confusing background fetching with user intent.
+
+**Architecture:** Keep URL parameters as the committed Activity filter source and React Query as the request owner. Track only the normalized query string submitted by the current Search command; same-query searches await `refetch()`, while changed-query searches remain pending until the URL reaches that target and its query settles. The existing page skeleton continues to own initial loading, and the button alone acknowledges Search over committed content.
+
+**Tech Stack:** React, TypeScript, TanStack React Query, Ant Design Button, Jest, Testing Library, Chrome remote-backed verification.
+
+---
+
+### Task 1: Prove The Missing Same-Query Feedback
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+
+- [ ] **Step 1: Add a deferred same-query Search test**
+
+Render a committed Activity row, make the second feed request a deferred promise, press Search without changing filters, and assert that the Search button immediately has `aria-busy="true"`, `ant-btn-loading`, and `disabled` while the committed row remains visible.
+
+- [ ] **Step 2: Assert duplicate submission is blocked**
+
+Press the pending Search button again and assert `listActivityRuns` is still called only twice: one initial load and one explicit Search request.
+
+- [ ] **Step 3: Assert completion restores the command**
+
+Resolve the deferred response and assert the button returns to `aria-busy="false"`, loses `ant-btn-loading`, and becomes enabled.
+
+- [ ] **Step 4: Run the exact test and confirm RED**
+
+Run from `apps/aevatar-console-web`:
+
+```bash
+pnpm exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx --testNamePattern 'shows pending feedback for an unchanged Activity Search'
+```
+
+Expected: FAIL because Search has no command-owned pending state.
+
+### Task 2: Preserve Feedback Across URL-Backed Search
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+
+- [ ] **Step 1: Add a deferred changed-filter test**
+
+Make the test history mock apply `history.replace()` to `mockSearch` and rerender the page. Change the draft search value, press Search, and defer the feed request for the new URL-backed filter.
+
+- [ ] **Step 2: Assert target-owned lifecycle**
+
+Assert the URL contains `q=support`, the button remains loading and disabled during the new feed request, and it returns to idle only after the target request settles.
+
+- [ ] **Step 3: Run the focused test and confirm RED**
+
+```bash
+pnpm exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx --testNamePattern 'keeps Search pending while URL-backed Activity filters load'
+```
+
+Expected: FAIL because the current button does not retain feedback across URL navigation.
+
+### Task 3: Implement Command-Owned Search Pending State
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.tsx`
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx`
+
+- [ ] **Step 1: Track the submitted normalized query string**
+
+Add `pendingSearchTarget` state. Ignore another submission while it is non-null, set it before issuing Search, and use the normalized `URLSearchParams.toString()` value as the command target.
+
+- [ ] **Step 2: Own both request paths**
+
+For unchanged filters, await `runs.refetch()` and clear the target in `finally`. For changed filters, replace the URL and clear only after the current URL matches the submitted target and `runs.isFetching` becomes false.
+
+- [ ] **Step 3: Expose honest button feedback**
+
+Set the Search button's `loading`, `disabled`, and `aria-busy` from `pendingSearchTarget !== null`. Keep its stable Search label and preserve the committed ledger content.
+
+- [ ] **Step 4: Run the exact Activity test and confirm GREEN**
+
+```bash
+pnpm exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx
+```
+
+Expected: all Activity tests pass.
+
+### Task 4: Focused Verification And Delivery
+
+**Files:**
+- Review the specification, plan, Activity component, and Activity test changed by Tasks 1-3.
+
+- [ ] **Step 1: Analyze affected frontend scope**
+
+Run from the repository root:
+
+```bash
+python3 /Users/abigaildeng/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base origin/feat/2026-08-04_workflow-activity-vnext
+```
+
+- [ ] **Step 2: Run related and explicit tests**
+
+Run from `apps/aevatar-console-web`:
+
+```bash
+pnpm exec jest --findRelatedTests src/pages/workflow-activity-vnext/activity/ActivityPage.tsx --runInBand
+pnpm exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx
+```
+
+Do not run a bare package test, full typecheck, or production build.
+
+- [ ] **Step 3: Run changed-file checks and repository guards**
+
+```bash
+pnpm exec biome check src/pages/workflow-activity-vnext/activity/ActivityPage.tsx src/pages/workflow-activity-vnext/activity/ActivityPage.test.tsx
+bash tools/docs/lint.sh
+bash tools/ci/test_stability_guards.sh
+git diff --check
+curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5175/
+```
+
+Full frontend suite, typecheck, and production build remain delegated to GitHub CI.
+
+- [ ] **Step 4: Verify in the existing Chrome tab**
+
+Reuse the user's existing local Activity tab. Press Search, confirm immediate spinner/disabled feedback, confirm committed rows stay mounted, confirm a second click cannot submit, and confirm the button returns to idle after the remote request settles. Do not open a browser, window, or duplicate tab.
+
+- [ ] **Step 5: Commit, push, and update PR #3498**
+
+Stage only this task's specification, plan, Activity component, and Activity test. Commit with an imperative message, push `feat/2026-08-20_workflow-schedule-frontend`, append the audit and exact verification commands to PR #3498, and verify that it remains Draft, `[DO NOT MERGE]`, based on `feat/2026-08-04_workflow-activity-vnext`, with matching local/remote/PR head SHAs.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-run-detail-refresh-feedback.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-run-detail-refresh-feedback.md
new file mode 100644
index 0000000000..a957e45eeb
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-run-detail-refresh-feedback.md
@@ -0,0 +1,98 @@
+# Run Detail Refresh Feedback Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make manual Run Detail refresh visibly acknowledge the request and report whether all authoritative sources refreshed successfully.
+
+**Architecture:** Keep React Query as the owner of detail, graph, and history data. Add one page-local grouped refresh command that awaits all three existing `refetch()` calls, owns only the manual-action pending state, and reports a single success or failure toast while leaving committed content mounted.
+
+**Tech Stack:** React, TypeScript, TanStack React Query, Ant Design Button, ConsoleToast, Jest, Testing Library.
+
+---
+
+### Task 1: Lock The Refresh Interaction Contract
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/RunDetailPage.test.tsx`
+
+- [x] **Step 1: Add a failing pending/success test**
+
+Render the loaded page, defer the second `getRun`, `getRunGraph`, and `listRuns` calls, click `Refresh`, and assert that the button becomes disabled with the accessible name `Refreshing…`. Resolve all three requests and assert one `Run details refreshed` success toast before the button returns to `Refresh`.
+
+- [x] **Step 2: Add a failing partial-failure test**
+
+Render the loaded page, make the second graph request reject, click `Refresh`, and assert one `Some run details couldn't be refreshed` error toast with no success toast.
+
+- [x] **Step 3: Run the focused test and verify RED**
+
+```bash
+pnpm exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/activity/RunDetailPage.test.tsx
+```
+
+Expected: both new tests fail because the existing Refresh button never changes state and never emits completion feedback.
+
+### Task 2: Implement The Grouped Refresh Command
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/RunDetailPage.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+
+- [x] **Step 1: Add page-local manual refresh state**
+
+Add `refreshing` state and an async `refreshRunDetail` callback. Guard duplicate execution, start pending state, await all three `refetch()` calls with `Promise.allSettled`, and inspect both rejected promises and fulfilled React Query results with `isError`.
+
+- [x] **Step 2: Report one completion result**
+
+When every source succeeds, call `toast.success('Run details refreshed', { key: 'run-detail-refresh' })`. When any source fails, call `toast.error("Some run details couldn't be refreshed", { key: 'run-detail-refresh' })`. Clear pending state in `finally`.
+
+- [x] **Step 3: Connect every manual refresh entry point**
+
+Use the grouped callback for the loaded header button, fallback header button, and failure-action retry/reload path. While pending, set the header button `loading` prop and render `Refreshing…`; otherwise render `Refresh`.
+
+Keep the header action width stable across both labels and provide matching English and Chinese feedback messages.
+
+- [x] **Step 4: Run the focused test and verify GREEN**
+
+```bash
+pnpm exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/activity/RunDetailPage.test.tsx
+```
+
+Expected: all Run Detail tests pass with no warnings.
+
+### Task 3: Run Incremental Verification And Deliver
+
+**Files:**
+- Verify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/RunDetailPage.test.tsx`
+- Verify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/RunDetailPage.tsx`
+- Verify: `apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-design.md`
+
+- [x] **Step 1: Analyze the frontend change scope**
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base HEAD
+```
+
+- [x] **Step 2: Run dependency-related tests and exact static checks**
+
+```bash
+pnpm exec jest --findRelatedTests src/pages/workflow-activity-vnext/activity/RunDetailPage.tsx --runInBand
+pnpm exec biome check src/locales/workflowActivityVNextMessages.en-US.ts src/locales/workflowActivityVNextMessages.zh-CN.ts src/pages/workflow-activity-vnext/activity/RunDetailPage.test.tsx src/pages/workflow-activity-vnext/activity/RunDetailPage.tsx src/pages/workflow-activity-vnext/styles.ts
+```
+
+Expected: every related Jest suite passes and Biome reports no diagnostics. Do not run a full frontend suite, typecheck, or production build.
+
+- [x] **Step 3: Run applicable repository guards**
+
+```bash
+python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py
+bash tools/docs/lint.sh
+bash tools/ci/test_stability_guards.sh
+git diff --check
+curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5175/
+```
+
+- [x] **Step 4: Review, commit, push, and update PR #3498**
+
+Stage only the Run Detail component/test, its shared vNext styles, the English and Chinese vNext messages, and this plan. Commit with `Improve Run detail refresh feedback`, push `feat/2026-08-20_workflow-schedule-frontend`, and append the exact focused commands and results to the existing Draft PR. Confirm its `[DO NOT MERGE]` title and `feat/2026-08-04_workflow-activity-vnext` base remain unchanged.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-run-detail-refresh-overlay.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-run-detail-refresh-overlay.md
new file mode 100644
index 0000000000..368606002a
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-run-detail-refresh-overlay.md
@@ -0,0 +1,214 @@
+# Run Detail Refresh Overlay Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Give manual Run Detail refresh a clear workspace-level loading state without hiding committed content or shifting the page shell.
+
+**Architecture:** Keep the existing page-local grouped refresh command as the only owner of manual refresh state. When `refreshing` is true, mark the Run workspace busy, make its history/stage content inert, and render one absolute overlay with a compact status indicator; the header and global navigation remain outside that ownership boundary.
+
+**Tech Stack:** React, TypeScript, Ant Design icons, TanStack React Query, Jest, Testing Library, vNext CSS tokens.
+
+---
+
+### Task 1: Lock The Workspace Refresh Contract
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/RunDetailPage.test.tsx`
+
+- [ ] **Step 1: Extend the existing grouped-refresh test**
+
+After clicking `Refresh`, assert one accessible status named `Refreshing run details…`, assert the `.wa-vnext-run-detail` workspace has `aria-busy="true"`, assert `.wa-vnext-run-detail__refresh-content` is inert, and keep the existing assertion that `Incident review` remains mounted.
+
+```tsx
+const refreshStatus = screen.getByRole('status', {
+ name: 'Refreshing run details…',
+});
+expect(refreshStatus).toHaveClass('wa-vnext-run-detail__refresh-overlay');
+expect(document.querySelector('.wa-vnext-run-detail')).toHaveAttribute(
+ 'aria-busy',
+ 'true',
+);
+expect(
+ document.querySelector('.wa-vnext-run-detail__refresh-content'),
+).toHaveAttribute('inert');
+```
+
+After resolving all requests, assert the status is gone, `aria-busy` is `false`, and `inert` is removed.
+
+```tsx
+expect(
+ screen.queryByRole('status', { name: 'Refreshing run details…' }),
+).not.toBeInTheDocument();
+expect(document.querySelector('.wa-vnext-run-detail')).toHaveAttribute(
+ 'aria-busy',
+ 'false',
+);
+expect(
+ document.querySelector('.wa-vnext-run-detail__refresh-content'),
+).not.toHaveAttribute('inert');
+```
+
+- [ ] **Step 2: Run the exact test and verify RED**
+
+```bash
+cd apps/aevatar-console-web
+pnpm exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/activity/RunDetailPage.test.tsx
+```
+
+Expected: the grouped-refresh test fails because the workspace status, busy attribute, and inert content boundary do not exist yet.
+
+### Task 2: Implement The Non-Destructive Refresh Overlay
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/activity/RunDetailPage.tsx`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+
+- [ ] **Step 1: Add the refresh overlay component**
+
+Place the component next to `RunDetailLoadingWorkspace` and reuse the imported `LoadingOutlined` icon.
+
+```tsx
+function RunDetailRefreshOverlay() {
+ const label = t(
+ 'workflowActivityVNext.run.refreshingDetail',
+ 'Refreshing run details…',
+ );
+
+ return (
+
+
+
+ {label}
+
+
+ );
+}
+```
+
+- [ ] **Step 2: Connect the loaded and fallback workspaces**
+
+For both rendered `.wa-vnext-run-detail` roots, add `aria-busy={refreshing}`. Immediately inside each root, open the following boundary before the history rail and close it after the complete stage section:
+
+```tsx
+
+ {/* Keep the current history rail and complete stage section here. */}
+
+```
+
+Do not change `runHref`, the valid filtered-Activity fallback, route queries, new-tab behavior, or accessible destination labels.
+
+- [ ] **Step 3: Rebalance CSS and give the icon a stable action target**
+
+Set the five column widths to `27 / 14 / 19 / 29 / 11`. Keep Completed time on one line. Center the Action header/cell and render the link as a stable inline-flex icon target with vNext blue focus/hover treatment.
+
+- [ ] **Step 4: Run the exact changed test and confirm GREEN**
+
+```bash
+pnpm exec jest --runInBand --runTestsByPath src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx
+```
+
+Expected: PASS with five semantic columns, valid links in cell index 4, and failed rows without an Action link.
+
+### Task 3: Align The Deterministic Schedule Baseline
+
+**Files:**
+- Modify: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/aevatar-workflow-schedule-design.gen.py`
+- Regenerate: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/aevatar-workflow-schedule-design.excalidraw`
+- Regenerate: all seven `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-*.png` scene files
+- Modify: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py`
+- Modify: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/README.md`
+
+- [ ] **Step 1: Change the History frame to five columns**
+
+Use 492px-wide columns matching the approved proportions:
+
+```python
+widths = [133, 69, 93, 143, 54]
+labels = ("SCHEDULED", "SOURCE", "RESULT", "COMPLETED", "ACTION")
+```
+
+Render `Run started` for error-free attempts and a blue trailing arrow in the Action column only for those rows. Keep the failed row Action empty.
+
+- [ ] **Step 2: Regenerate the Schedule Excalidraw and all source-linked PNGs**
+
+```bash
+python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/aevatar-workflow-schedule-design.gen.py
+/Users/abigaildeng/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/bin/python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/render-schedule-png.py
+```
+
+- [ ] **Step 3: Update verifier and README hashes from the regenerated files**
+
+Update the Schedule design SHA-256 and all seven PNG SHA-256 values. Add a verifier requirement for `ACTION` so a four-column design cannot return silently.
+
+- [ ] **Step 4: Verify the baseline and inspect the History PNG**
+
+```bash
+python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py
+bash tools/docs/lint.sh
+```
+
+Expected: 17/17 main frames, 7/7 Schedule frames, 7 standalone nonblank Schedule PNGs, byte-identical generator output, and docs lint with zero errors.
+
+### Task 4: Focused Verification And Delivery
+
+**Files:**
+- Review all files changed by Tasks 1-3.
+
+- [ ] **Step 1: Analyze the frontend dependency scope**
+
+```bash
+python3 /Users/abigaildeng/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py --repo . --base HEAD
+```
+
+- [ ] **Step 2: Run related Jest and every changed test explicitly**
+
+Run Jest only for source files reported by the analyzer and rerun `WorkflowScheduleSurface.test.tsx` directly. Do not run a bare package test command.
+
+- [ ] **Step 3: Run changed-file static checks and applicable guards**
+
+Run Biome only on the analyzer's `staticCheckFiles`, then run:
+
+```bash
+bash tools/ci/test_stability_guards.sh
+git diff --check
+curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5175/
+```
+
+Do not run a local full frontend typecheck, test suite, or production build. GitHub CI owns full validation.
+
+- [ ] **Step 4: Reuse the user's existing Chrome for visual verification**
+
+Claim the existing `127.0.0.1:5175` tab if the Chrome connection exposes it. Verify the Action header, independent completed-time cell, action-cell alignment, and multiple rows. Do not open a new window, duplicate tab, separate browser, or the in-app browser.
+
+- [ ] **Step 5: Commit, push, and update PR #3498**
+
+Stage only the current task's implementation, test, locale, CSS, plan, and design-baseline files. Commit with an imperative message, push `feat/2026-08-20_workflow-schedule-frontend`, and append the exact verification evidence to PR #3498 while preserving Draft, `[DO NOT MERGE]`, and base `feat/2026-08-04_workflow-activity-vnext`.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-workflow-create-result-unknown.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-workflow-create-result-unknown.md
new file mode 100644
index 0000000000..0ca84747b0
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-25-workflow-create-result-unknown.md
@@ -0,0 +1,40 @@
+# Workflow Create Result Unknown Implementation Plan
+
+**Goal:** Report an interrupted draft-create response honestly without
+resubmitting or guessing Workflow identity.
+
+**Scope:** Frontend-only Workflow Activity vNext creation flow.
+
+**Tech stack:** React, TypeScript, TanStack React Query, ConsoleToast, Jest,
+Testing Library.
+
+## Task 1: Protect The Product Contract
+
+Add a focused `NewWorkflowPage` test that rejects `createWorkflowDraft` with a
+`504`. Assert the warning copy, retained YAML, one create call, drafts-query
+refetch, and absence of navigation. Run only this test and confirm the expected
+failure before implementation.
+
+## Task 2: Classify Only The Create Stage
+
+Add a narrow create-result classifier for `408`, `504`, `TypeError`, and
+aborted transport errors. Catch these errors directly around
+`createWorkflowDraft`, emit an internal result-unconfirmed signal, and leave
+the outer catch responsible for all definite failures.
+
+The result-unconfirmed path must not call `finishSave`, retry create, inspect
+drafts to identify a Workflow, or navigate.
+
+## Task 3: Refresh And Warn
+
+On the result-unconfirmed signal, refresh the existing scoped drafts query and
+show the localized warning. Preserve current form state. Treat refresh failure
+as non-authoritative for the create outcome and keep the warning visible.
+
+## Task 4: Focused Verification And Delivery
+
+Run the owning test, analyzer-selected related tests, changed-file Biome
+checks, test stability guard, docs lint, and `git diff --check`. Reuse the
+existing Chrome tab for a non-destructive UI check, review the diff, stage only
+task files, commit, push, and update Draft PR #3498 with exact commands and
+results. Full frontend validation remains owned by GitHub CI.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-26-run-detail-selection-loading.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-26-run-detail-selection-loading.md
new file mode 100644
index 0000000000..1baa6142f7
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-26-run-detail-selection-loading.md
@@ -0,0 +1,71 @@
+# Run Detail Selection Loading Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use
+> superpowers:subagent-driven-development (recommended) or
+> superpowers:executing-plans to implement this plan task-by-task. Steps use
+> checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Keep committed Run history visible and interactive while a newly
+selected Run's detail and graph load.
+
+**Architecture:** Retain the independent TanStack Query boundaries for Run
+history, detail, and graph. Split the existing structural skeleton into a
+complete initial workspace and a reusable right-side detail stage. A selection
+with committed history renders the normal rail beside the loading stage; a
+manual grouped Refresh continues to own the whole-workspace busy state.
+
+**Tech Stack:** React, TypeScript, TanStack Query, Ant Design, Jest, Testing
+Library.
+
+---
+
+### Task 1: Lock The Loading Boundary
+
+**Files:**
+- Modify: `src/pages/workflow-activity-vnext/activity/RunDetailPage.test.tsx`
+
+- [x] Add a focused route-selection test with deferred detail and graph reads.
+- [x] Assert the committed rail remains mounted, the selected row changes, the
+ history query is not reissued, and only the right stage reports loading.
+- [x] Run the named test and confirm RED against the complete-workspace
+ skeleton.
+
+### Task 2: Split Initial And Selection Loading
+
+**Files:**
+- Modify: `src/pages/workflow-activity-vnext/activity/RunDetailPage.tsx`
+- Modify: `src/pages/workflow-activity-vnext/styles.ts`
+- Modify: `src/shared/ui/AevatarLoading.tsx`
+- Modify: `src/shared/ui/AevatarLoading.test.tsx`
+- Modify: `src/global.less`
+
+- [x] Extract the existing right-side skeleton into an accessible loading
+ stage without changing its visual structure.
+- [x] Keep the complete workspace skeleton for initial loading without
+ committed history.
+- [x] Render the committed, workflow-scoped history rail beside the loading
+ stage when another history Run is selected.
+- [x] Keep the workspace itself usable and expose one right-stage loading
+ status.
+- [x] Add a shared committed-content overlay using the canonical three-dot
+ motion, then replace both page-local Run Detail refresh indicators.
+
+### Task 3: Persist The Product Rule
+
+**Files:**
+- Modify: `AGENTS.md`
+- Modify:
+ `docs/superpowers/specs/2026-08-04-workflow-activity-vnext-user-paths.md`
+
+- [x] Record the reusable region-owned loading rule in the frontend guidance.
+- [x] Record initial loading, Run selection, and grouped Refresh boundaries in
+ UP-10.
+
+### Task 4: Verify And Deliver
+
+- [x] Run the focused Run Detail test and bounded related tests.
+- [x] Run changed-file Biome, test stability, docs lint, and diff checks.
+- [x] Reuse the user's existing Chrome tab to verify the remote-backed page.
+- [x] Review and stage only current-task files, excluding `.planning/`.
+- [x] Commit, push, and append exact evidence to Draft PR #3498 without
+ changing its title, base, or merge gate.
diff --git a/apps/aevatar-console-web/docs/superpowers/plans/2026-08-26-schedule-cron-readability.md b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-26-schedule-cron-readability.md
new file mode 100644
index 0000000000..119f8c9489
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/plans/2026-08-26-schedule-cron-readability.md
@@ -0,0 +1,283 @@
+# Schedule Cron Readability Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make expanded Schedule recurrence details understandable in product language while preserving the exact cron expression as secondary technical information.
+
+**Architecture:** Keep `scheduleRecurrenceSummary` as the single frontend interpretation path and reuse it inside the existing Overview disclosure. Add only locale-backed presentation labels and small scoped styles, then update the deterministic Schedule design sources and regression verifier so raw cron cannot become the only local explanation again.
+
+**Tech Stack:** React 19, TypeScript, Umi locale catalogs, Testing Library/Jest, CSS, deterministic HTML/Excalidraw/Pillow design generators, Python baseline verifier.
+
+---
+
+### Task 1: Protect the readable disclosure contract with TDD
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx`
+
+- [ ] **Step 1: Extend the existing Overview behavior test before production code**
+
+Add the interaction and scoped assertions to `presents Schedule Overview with primary actions and guarded lifecycle actions` immediately after the existing collapsed-state assertion:
+
+```tsx
+const advancedDetails = screen
+ .getByText('Advanced details')
+ .closest('details');
+expect(advancedDetails).not.toBeNull();
+if (!advancedDetails) {
+ throw new Error('Expected Schedule advanced details disclosure');
+}
+
+fireEvent.click(within(advancedDetails).getByText('Advanced details'));
+
+expect(within(advancedDetails).getByText('Runs')).toBeVisible();
+expect(
+ within(advancedDetails).getByText('Every weekday at 09:00'),
+).toBeVisible();
+expect(within(advancedDetails).getByText('Technical format')).toBeVisible();
+expect(within(advancedDetails).getByText('0 9 * * 1-5')).toBeVisible();
+```
+
+- [ ] **Step 2: Run the named test and verify RED**
+
+Run:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest \
+ --runInBand \
+ --runTestsByPath src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx \
+ --testNamePattern 'presents Schedule Overview with primary actions and guarded lifecycle actions'
+```
+
+Expected: FAIL because the expanded disclosure does not render `Runs` or `Technical format`. A syntax, fixture, or selector error is not the required RED state and must be corrected before continuing.
+
+### Task 2: Render product meaning before technical syntax
+
+**Files:**
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.tsx`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts`
+- Modify: `apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts`
+- Modify: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/styles.ts`
+- Test: `apps/aevatar-console-web/src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx`
+
+- [ ] **Step 1: Replace the single raw-cron row with the readable-to-technical hierarchy**
+
+Keep the existing `` and `
+```
+
+Do not add another cron parser. Unsupported valid expressions continue to use the existing `Custom schedule` fallback and preserve the exact cron string in the second row.
+
+- [ ] **Step 2: Add synchronized locale keys**
+
+Add to the English catalog:
+
+```ts
+'workflowActivityVNext.schedule.runs': 'Runs',
+'workflowActivityVNext.schedule.technicalFormat': 'Technical format',
+```
+
+Add to the Chinese catalog:
+
+```ts
+'workflowActivityVNext.schedule.runs': '运行周期',
+'workflowActivityVNext.schedule.technicalFormat': '技术格式',
+```
+
+- [ ] **Step 3: Clarify row rhythm and secondary code treatment**
+
+Keep the existing compact definition-list layout. Increase the list gap to `8px`, keep the labels subdued, and give only the `` value a subtle background, small radius, and horizontal padding using existing `--wa-*` tokens. Do not introduce another card, divider panel, or tooltip-only explanation.
+
+- [ ] **Step 4: Rerun the named test and verify GREEN**
+
+Run the same named Jest command from Task 1.
+
+Expected: PASS with `Runs`, `Every weekday at 09:00`, `Technical format`, and `0 9 * * 1-5` visible inside the expanded disclosure.
+
+### Task 3: Keep design artifacts semantically aligned
+
+**Files:**
+- Modify: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/prototype.html`
+- Modify: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/aevatar-workflow-schedule-design.gen.py`
+- Generate: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/aevatar-workflow-schedule-design.excalidraw`
+- Generate: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-workflows-list-modal.png`
+- Generate: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-workflow-editor-panel.png`
+- Generate: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-review.png`
+- Generate: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-creation-pending.png`
+- Generate: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-detail.png`
+- Generate: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-history.png`
+- Generate: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/schedule-edit.png`
+- Modify: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py`
+- Modify: `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/README.md`
+
+- [ ] **Step 1: Update the interactive prototype Overview disclosure**
+
+In `scheduleOverviewMarkup`, replace the single `Cron expression` row with the same hierarchy, using the already-computed human summary:
+
+```html
+
+ Advanced details
+
+
+```
+
+Adjust only the existing scoped `.schedule-advanced` styles as needed for two readable rows.
+
+- [ ] **Step 2: Update the deterministic Schedule Overview frame**
+
+Change frame `05 · Workflow — schedule overview` so Advanced details is visibly expanded and contains these exact facts:
+
+```text
+Runs Every weekday at 09:00
+Technical format 0 9 * * 1-5
+```
+
+Keep the recurrence meaning visually stronger than the monospace technical format. Do not add cron-field instructions or duplicate timezone.
+
+- [ ] **Step 3: Strengthen deterministic semantic verification**
+
+Require the Overview frame to contain all of:
+
+```python
+(
+ "advanced details",
+ "runs",
+ "every weekday at 09:00",
+ "technical format",
+ "0 9 * * 1-5",
+)
+```
+
+For the dynamic prototype function, require the `Runs` row to bind to
+`editModel.humanSummary` and the `Technical format` row to bind to
+`schedule.cronExpression`. This must fail if raw cron again becomes the only
+explanation or if either row disconnects from its owning value.
+
+- [ ] **Step 4: Regenerate the Schedule board and standalone PNGs**
+
+Run from the repository root:
+
+```bash
+python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/aevatar-workflow-schedule-design.gen.py
+python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/render-schedule-png.py
+```
+
+Expected: the Excalidraw board and seven declared Schedule PNGs are generated deterministically.
+
+- [ ] **Step 5: Update declared deterministic hashes**
+
+Compute the regenerated board and PNG SHA-256 values, then update `EXPECTED_SCHEDULE_SHA256`, `SCHEDULE_PNG_SHA256`, and the README `Schedule design SHA-256` declaration to the exact generated values. The README and verifier must declare the same board hash.
+
+- [ ] **Step 6: Verify the baseline twice**
+
+Run twice from `apps/aevatar-console-web/`:
+
+```bash
+python3 docs/design-baselines/workflow-activity-vnext/verify-baseline.py
+```
+
+Expected: both runs pass, proving the generator output, declared hashes, frame inventory, PNG inventory, and readable Overview semantics are stable.
+
+- [ ] **Step 7: Inspect the generated Schedule Overview PNG**
+
+Open `schedule-detail.png` at original detail and confirm the two rows fit without overlap, truncation, or competing with the primary Overview hierarchy.
+
+### Task 4: Run focused verification and deliver through the existing Draft PR
+
+**Files:**
+- Review every file changed in Tasks 1-3
+- Never stage: `.planning/`
+
+- [ ] **Step 1: Run the frontend scope analyzer**
+
+```bash
+python3 ~/.codex/skills/frontend-incremental-pr/scripts/frontend_change_scope.py \
+ --repo . \
+ --base origin/feat/2026-08-04_workflow-activity-vnext
+```
+
+Use its `staticCheckFiles` exactly; do not run a full frontend typecheck, test suite, lint, or build.
+
+- [ ] **Step 2: Preflight related tests for only the behavior-owning component**
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest \
+ --listTests \
+ --findRelatedTests \
+ src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.tsx
+```
+
+Run the matching `--findRelatedTests --runInBand` command only if the complete list contains at most 10 files and stays within the Schedule/workflow-activity-vNext domain. Never include locale catalogs or shared styles in dependency discovery.
+
+- [ ] **Step 3: Run the changed test file explicitly**
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest \
+ --runInBand \
+ --runTestsByPath src/pages/workflow-activity-vnext/workflows/WorkflowScheduleSurface.test.tsx
+```
+
+Expected: PASS with zero test failures.
+
+- [ ] **Step 4: Run changed-file static and repository checks**
+
+Run Biome only against the analyzer's `staticCheckFiles`, followed by:
+
+```bash
+bash tools/ci/test_stability_guards.sh
+bash tools/docs/lint.sh
+git diff --check
+```
+
+Expected: every command exits `0`. Full frontend suite, full `tsc`, and production build remain delegated to GitHub CI by the personal incremental frontend policy.
+
+- [ ] **Step 5: Reuse the existing Chrome tab for targeted visual verification**
+
+Confirm the server at `http://127.0.0.1:5175/` belongs to this worktree and still points at the configured remote backend. In the user's existing Chrome tab, open a Schedule Overview, expand Advanced details, and verify English and Chinese labels, readable recurrence, exact cron value, responsive fit, focusability, and no sibling-content loading regression. Do not open a second browser session or duplicate tab.
+
+- [ ] **Step 6: Review and stage only this task's files**
+
+Review `git diff`, confirm there are no unrelated changes, and explicitly stage the plan, specification, component, test, locale, styles, prototype, generator, generated board, seven Schedule PNGs, verifier, and README. Never stage `.planning/`.
+
+- [ ] **Step 7: Commit and push**
+
+```bash
+git commit -m "Make Schedule cron details readable"
+git push origin feat/2026-08-20_workflow-schedule-frontend
+```
+
+- [ ] **Step 8: Update Draft PR #3498 and verify delivery state**
+
+Keep title `[DO NOT MERGE] Wire workflow-scoped Schedule management`, base `feat/2026-08-04_workflow-activity-vnext`, and Draft status. Add the semantic decision and exact focused command results to the PR, including:
+
+```markdown
+## Local verification
+
+- Related tests: ``
+- Changed-file static checks: ``
+- Design baseline: ``
+- Full frontend suite/build: deferred to GitHub CI by personal local workflow policy
+```
+
+Finally confirm local `HEAD`, `origin/feat/2026-08-20_workflow-schedule-frontend`, and PR #3498 head resolve to the same commit SHA.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-design.md
new file mode 100644
index 0000000000..71a68350c1
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-design.md
@@ -0,0 +1,1123 @@
+# Workflow + Activity + Settings vNext Design
+
+## Status
+
+Proposed for review on 2026-08-04. This document is the deliverable for the
+design-first phase. It does not authorize implementation until the product and
+engineering contracts below have been reviewed.
+
+Implementation branch: `feat/2026-08-04_workflow-activity-vnext`.
+
+After approval, this document is the normative product and engineering
+contract for the Workflow Activity vNext frontend. It must be read together
+with the in-repository design baseline at
+`apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/` and
+the normative user paths in
+`2026-08-04-workflow-activity-vnext-user-paths.md`.
+
+This phase changes documentation only. The eventual implementation is strictly
+frontend-only: no backend source, endpoint, DTO, projection, or persistence
+change is in scope.
+
+## Sources And Precedence
+
+The design is based on these sources, in descending order of authority:
+
+1. The in-repository
+ `apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/`
+ `aevatar-workflow-activity-vnext.excalidraw`, containing 17 named frames.
+ Its SHA-256 is
+ `30e74d7b410ae72c4c91432355436679033679c54c10b1702908435b001577de`.
+2. The backend contracts in `origin/feature/integrate` at commit
+ `72093afa4490adf6eb25d83c3db0b9c182cd6ff0`.
+3. The frontend architecture and identity rules in the repository root
+ `AGENTS.md`, `apps/aevatar-console-web/AGENTS.md`, and
+ `docs/canon/frontend-design.md`.
+4. The PNGs and interactive HTML beside the Excalidraw file. These are useful
+ render references, but they do not override the Excalidraw or backend truth.
+
+When sources conflict, the Excalidraw controls intended product flow and visual
+hierarchy, while the backend contract controls what the UI may claim, filter,
+persist, or execute. The frontend must degrade honestly rather than inventing
+data to make a mockup appear complete.
+
+The user-provided source package was imported byte-for-byte. The repository
+copy is the reviewable, portable branch artifact and must be used by later
+implementation work; no developer-local source path is a dependency.
+
+## Production Data Truth Rule
+
+Production code must never make mock, fixture, demonstration, generated,
+cached, or hard-coded data appear to be an API result.
+
+- Remote Workflows, Runs, Run details, settings, identities, receipts,
+ statuses, timestamps, revisions, usage, lineage, and availability come only
+ from real API responses or real user actions acknowledged by those APIs.
+- Pending requests render loading or accepted/observing states. Failed
+ requests render error or unavailable states. Neither may fall back to
+ successful-looking demonstration data.
+- Empty state means a successful authoritative query returned no records. It
+ must never be used to conceal a request or decoder failure.
+- Browser storage is not an authoritative Workflow, Activity, account, or
+ settings data source. A request-recovery helper may retain only the explicit
+ non-authoritative receipt facts allowed by this specification.
+- Test fixtures must remain in clearly named test-only modules and must not be
+ imported by production routes, components, hooks, queries, or API adapters.
+- The prototype's hard-coded records, `localStorage`, and timers exist only to
+ make a serverless design demonstration interactive. They are explicitly
+ prohibited as production implementation patterns.
+- Bundled Workflow templates are allowed only as explicitly modeled frontend
+ product content. They must never be labeled or decoded as an API catalogue.
+
+## Goals
+
+- Deliver an isolated Workflows, Activity, and Settings workbench under a new
+ scope-aware route namespace.
+- Let a user create a Workflow draft directly through Describe, Start blank,
+ Import YAML, or Template, without creating a team member first.
+- Reuse the connected-node Workflow Studio interaction model for every draft
+ creation method.
+- Make `Run` the regular execution action and Activity the single history
+ surface inside this new workbench.
+- Present each observed Run as an immutable record; Retry and Run again create
+ a new Run rather than mutating the source record.
+- Preserve authoritative identifiers, accepted-command receipts, projection
+ latency, and state versions throughout the UI.
+- Reframe current AI defaults, Account, and Advanced settings into the new
+ design without changing their backend behavior.
+- Reuse the existing Aevatar authentication, return-to, session, service-access
+ review, and internationalization logic while styling their presentation to
+ match the vNext visual direction.
+- Provide complete loading, empty, error, disabled, accepted, observing,
+ observed, and recovery states on desktop and mobile.
+
+## Non-Goals
+
+- No backend modification, new endpoint, DTO enrichment, projection change,
+ migration, or persistence change.
+- No replacement, redirect, or behavior change of existing
+ `/runtime/workflows`, `/runtime/runs`, `/settings`, `/studio`, Team, member,
+ login, callback, or authentication routes. Login, callback, language, and
+ account presentation may adopt the vNext visual system without changing
+ their behavior.
+- No global menu expansion. The vNext routes remain hidden from the existing
+ menu; the console home uses the account resolver described below.
+- No assumption that `memberId`, `workflowId`, `definitionActorId`, or
+ `publishedServiceId` are interchangeable.
+- No locally fabricated revision history, Run lineage, Activity records,
+ duration, usage, outcome, approval state, or published runtime identity.
+- No use of browser storage as an authoritative Workflow or Activity database.
+- No mock, fixture, example, timer-generated, or hard-coded server entity in a
+ production loading, success, empty, delayed, or error path.
+- No attempt to reproduce prototype-only behavior that the reviewed backend
+ cannot support truthfully.
+
+## Product And Identity Contract
+
+### Product Semantics
+
+The vNext model is:
+
+```text
+Workflow draft
+ -> edit
+ -> run request
+ -> accepted or streaming execution
+ -> observed Activity record
+ -> immutable Run detail
+ -> optional Retry or Run again creates another Run
+```
+
+A draft is a first-class Workflow resource. Team and member creation are not
+prerequisites for entering its editor. Publication and callable service
+identity remain separate concerns and must not be inferred from authoring
+identity.
+
+### Identifier Rules
+
+- `scopeId` comes directly from the route. Every scoped request passes it to a
+ server endpoint that validates access. Display names, cached selections, and
+ other IDs must never be used to reconstruct it.
+- `workflowId` identifies a workspace draft or scope Workflow definition. It
+ is not a member ID and is not automatically callable.
+- `workflowId` from a catalogue row is the URL-owned Workflow filter context.
+ Activity resolves that exact scoped identity through `ScopeWorkflowDetail`;
+ only the returned `definitionActorId` may become the observatory
+ `definition` request filter.
+- `memberId` remains Team-member authority. The vNext draft routes do not
+ invent one or route through member APIs.
+- `publishedServiceId` is a callable runtime identity. It must be supplied by
+ an explicit response or read model before a published Run action is enabled.
+- `runId` identifies an immutable observed Run record.
+- `stateVersion` is the authoritative projection version. The UI may use it to
+ detect fresh observation, but must never create or increment a local version.
+- `acceptedCommandId`, `correlationId`, and `statusUrl` are command-receipt
+ facts. They indicate acceptance and observation targets, not completion.
+
+## Route And Navigation Model
+
+The 2026-09-14 console home decision adds `/workflows` as an unscoped account
+resolver. It refreshes `/api/auth/me` and opens the catalogue using the returned
+`scopeId`; it never uses a fixed workspace, browser subject, or stale cached scope.
+The repository-root `docs/superpowers/specs/2026-08-31-workflow-vnext-home-design.md`
+defines its recovery behavior and supersedes the original preview-only entry.
+This is the explicit exception to the original global-navigation non-goal.
+
+All vNext resource pages live below one scoped namespace:
+
+| Route | Purpose | Existing route impact |
+| --- | --- | --- |
+| `/scopes/:scopeId/workflow-activity-vnext` | Namespace entry; redirects only within the namespace to `workflows` | None |
+| `/scopes/:scopeId/workflow-activity-vnext/workflows` | Workflow catalogue | None |
+| `/scopes/:scopeId/workflow-activity-vnext/workflows/new` | Direct creation chooser | None |
+| `/scopes/:scopeId/workflow-activity-vnext/workflows/:workflowId` | Draft editor and Run console | None |
+| `/scopes/:scopeId/workflow-activity-vnext/activity` | Scope Activity ledger | None |
+| `/scopes/:scopeId/workflow-activity-vnext/activity/:runId` | Immutable Run detail and recovery actions | None |
+| `/scopes/:scopeId/workflow-activity-vnext/settings` | AI defaults, Account, and Advanced | None |
+
+Route requirements:
+
+- Declare the literal `workflows/new` route before the dynamic
+ `workflows/:workflowId` route.
+- Set every vNext route to `hideInMenu: true`. Do not add or alter a global
+ menu item during the isolated implementation.
+- `/`, `/overview`, `/scopes`, and default login/callback fallback open
+ `/workflows`. Preserve explicit safe deep links. The scoped namespace entry
+ redirects to its own `workflows` child.
+- Keep the existing authenticated app and runtime providers. Do not set
+ `layout: false`, because that would bypass established shell behavior.
+- The new surface may hide the current ProLayout chrome through a narrowly
+ scoped display-route predicate that matches this prefix only. It must retain
+ authentication, React Query, runtime configuration, and error boundaries.
+- Workflows, Activity, and Settings use a vNext-local navigation rail or mobile
+ switcher. All internal links preserve the route `scopeId`.
+- Unknown Workflow and Run IDs render a scoped not-found state inside the
+ workbench and do not redirect to legacy pages.
+
+The first implementation is intentionally discoverable by direct URL only.
+Adding an entry from an existing menu or Team page is a separate, explicit
+product decision because it changes an existing navigation surface.
+
+## Authentication And Localization Reuse Contract
+
+### Existing Login Path
+
+The vNext namespace is protected by the existing application runtime. It must
+not introduce another login page, callback, guard, token cache, or session
+provider.
+
+The preserved flow is:
+
+```text
+Protected vNext URL
+ -> ProtectedRouteRedirectGate
+ -> /login?redirect=
+ -> NyxIDAuthClient.loginWithRedirect({ returnTo })
+ -> /auth/callback
+ -> NyxIDAuthClient.handleRedirectCallback()
+ -> window.location.replace(result.returnTo)
+ -> original protected vNext URL
+```
+
+The implementation reuses these current boundaries:
+
+- `src/app.tsx` public-route and auth-session bootstrap behavior;
+- `src/shared/auth/ProtectedRouteRedirectGate.tsx`;
+- `src/pages/login/index.tsx`;
+- `src/pages/auth/callback/index.tsx`;
+- `src/shared/auth/client.ts`, `config.ts`, and `session.ts`;
+- current Account sign-in, sign-out, and service-access review actions.
+
+`sanitizeReturnTo` remains the authority for the redirect target. Restorable
+session handling continues through `hasRestorableAuthSession` and
+`ensureActiveAuthSession`. Login configuration errors, callback errors,
+service-access review errors, retry, and back actions retain their current
+logic and security behavior.
+
+### Login And Auth Presentation
+
+Login, callback progress/error, language, and account controls should look like
+the new workbench while remaining the same functional surfaces.
+
+- Replace the current decorative gradient, oversized radius, and high card
+ shadow with the Operational Automation Ledger palette, neutral borders,
+ 4-6 px radii, compact type, and restrained status treatment.
+- Keep Aevatar as the first-viewport brand signal and retain the existing
+ NyxID action, pending state, configuration error, and callback recovery
+ content.
+- Do not add a marketing hero, new credential form, new auth choice, or
+ vNext-only callback route.
+- Preserve keyboard access, visible focus, pending button behavior, live error
+ announcements, and reduced-motion expectations.
+- A visual-only auth change must remain compatible with every existing route
+ that uses `/login` and `/auth/callback`, not only the vNext return path.
+
+### Existing Internationalization Path
+
+The vNext workbench reuses the configured Umi locale system:
+
+- `config/config.ts` remains the locale configuration authority;
+- `getLocale`/`setLocale` and `ConsoleLanguageSwitch` retain language selection
+ and persistence behavior;
+- `normalizeConsoleLocale`, `resolveAntdLocale`, and `resolveProIntl` continue
+ to supply the Ant Design and Pro Components locales;
+- `t`, `T`, `ConsoleMessage`, and `useIntl` remain the message formatting
+ paths;
+- `src/locales/en-US.ts`, `zh-CN.ts`, and their project-message catalogues own
+ all new vNext copy.
+
+The supported product locales remain `en-US` and `zh-CN`. Every new message ID
+must exist in both catalogues and preserve interpolation variables. New pages
+must not use hard-coded visible copy, create a vNext locale context, write a
+second locale storage key, or infer language from scope/user data.
+
+If the vNext display route hides ProLayout header actions, the vNext-local
+shell must render the existing `ConsoleLanguageSwitch` and existing account
+action behavior in a style-compatible location. Reuse may be through the
+current components or a presentation extraction that leaves their public
+behavior and existing consumers intact.
+
+## Primary Flow
+
+```mermaid
+%%{init: {"maxTextSize": 100000, "flowchart": {"useMaxWidth": false, "nodeSpacing": 10, "rankSpacing": 50}, "themeVariables": {"fontSize": "10px"}}}%%
+flowchart LR
+ A["Workflows catalogue"] --> B["New workflow"]
+ B --> C["Describe"]
+ B --> D["Start blank"]
+ B --> E["Import YAML"]
+ B --> F["Template"]
+ C --> G["Created draft"]
+ D --> G
+ E --> G
+ F --> G
+ G --> H["Connected-node editor"]
+ H --> I["Run dialog"]
+ I --> J["Accepted or running"]
+ J --> K["Activity observation"]
+ K --> L["Immutable Run detail"]
+ L --> M["Retry or Run again"]
+ M --> N["New accepted Run"]
+ N --> K
+```
+
+The transition from `Accepted or running` to `Activity observation` is
+eventually consistent. An accepted HTTP or SSE request does not itself prove
+that the Activity read model contains a Run.
+
+## Frame-To-Product Specification
+
+The Excalidraw frame order is the review order and acceptance checklist:
+
+| Frame | Required page or behavior | Backend-honest interpretation |
+| --- | --- | --- |
+| 01 Workflows - catalogue | Searchable, compact Workflow table with New workflow, Open, and conditional Run actions | Do not show Last Run, revision numbers, or callable state unless returned by an authoritative contract |
+| 02 New workflow - direct creation | Describe, Start blank, Import YAML, and Template create Workflow drafts directly | Every successful path persists through the draft API before navigating to the editor |
+| 03 Describe - generated Workflow draft | Description generates connected nodes and opens the common editor | Generation is not persistence; create the returned document as a draft, observe it until readable when creation returns `202`, then navigate using the returned `workflowId` |
+| 04 Start blank - empty Workflow draft | Empty connected-node canvas with Add node and Edit YAML | Publish and Run remain disabled until the current document passes real validation |
+| 05 Import YAML - imported Workflow draft | YAML is parsed and validated before draft creation | Parse errors stay in the creation surface and do not create partial drafts |
+| 06 Template - populated Workflow draft | A chosen template creates an independent editable draft | Templates are versioned frontend product content unless an authoritative catalogue already exists; they are never pretend backend records |
+| 07 Run - unified execution dialog | Confirm target, input, connections, and external effects | Show only facts supported by the current document and capability preview; do not claim that Activity has already saved the Run |
+| 08 Running draft - Studio canvas and Run console | Keep the editor context while streaming accepted/running output | Show `Accepted` or `Running`, then separately show `Observed in Activity` only after the observatory returns the Run |
+| 09 Activity - filtered by Workflow | Enter Activity with the row's `workflowId` visible in the URL and page | Resolve `definitionActorId` from Workflow detail before querying Runs; never filter by display name or guessed actor ID, and never fall back to global Runs when resolution fails |
+| 10 Activity - all retained Runs | Newest-first scope ledger with server filters | The endpoint is bounded by `take`; call it recent retained Runs, never claim a complete lifetime total |
+| 11 Run detail - immutable record | Input, output/error, diagnostics, steps, timeline, graph, statistics, and usage | Detail-level duration and usage are derived only from detail DTO fields; the source record is read-only |
+| 12 Failed Run - recovery creates a new record | Explain failure and preview Retry or Run again | Fork returns a new accepted receipt; it never edits the source Run and durable lineage is not claimed |
+| 13 Workflows and Activity - states | Loading, empty, errors, responsive reduction, and recovery | Every state keeps its primary next action and distinguishes no-data from unavailable-data |
+| 14 Settings - AI defaults | Preferred service and Default model are the normal-state decisions | Reuse current LLM settings contracts and catalogue state rather than storing preferences locally |
+| 15 Settings - save and recovery states | Dirty save bar, accepted observation, fallback, catalogue unavailable, and save error | A `202 Accepted` receipt is shown as confirming until the authoritative settings query observes the saved value |
+| 16 Settings - Account | Identity, claims, authentication, session actions, and service access | Reuse `/api/auth/me` and existing auth actions; do not infer claims or expose credential material |
+| 17 Settings - Advanced and responsive | One read-only effective runtime/request view plus tablet/mobile layouts | Reuse `/api/user-config/runtime`; no duplicate or editable representation of the same effective value |
+
+## Workflows Experience
+
+### Catalogue
+
+The desktop catalogue is a dense operational table, not a card gallery. Its
+authoritative row union keeps draft and scope Workflow identities explicit.
+The minimum columns are Workflow name, kind/state, last updated, and actions.
+Search is client-side over the currently loaded authoritative fields. Filters
+may distinguish draft and scope Workflow only when the adapter can do so from
+their source DTOs.
+
+The page must not join Activity rows to Workflows by `workflowName`. The
+observatory summary lacks a Workflow ID and names are neither unique nor
+stable. Consequently, the Excalidraw `Last Run` cell is omitted until an
+authoritative join key exists.
+
+`Open` always uses the row's real `workflowId` and preserves the row's source
+kind. A draft row opens its authoritative draft. A committed-only row may load
+through the existing committed fallback with `draftExists=false`; it is an
+immutable source until the user explicitly saves a draft copy. That first
+save uses draft creation, not draft update. The create response's
+`workflowId` becomes canonical after materialization, and the editor replaces
+the route if it differs from the committed source ID.
+
+Each catalogue row keeps exactly three persistent actions: primary `Open`,
+neutral `View activity`, and neutral `More actions`. `Rename`, `Copy workflow
+reference`, and `Delete draft` belong in the overflow menu. `Delete draft` is
+the final item, separated from ordinary actions, presented as dangerous, and
+opens the existing confirmation before any mutation occurs.
+
+`Run` follows this policy:
+
+- A validated draft may run through the reviewed scope draft-run endpoint.
+- A published Workflow may run only if an explicit callable
+ `publishedServiceId` is available from an authoritative frontend contract.
+- If no callable identity is available, keep `Open` enabled and render Run as
+ disabled with a concise reason. Never send `workflowId`, `serviceKey`,
+ `ActorId`, or `definitionActorId` where a service ID is required.
+
+### Direct Creation
+
+All four creation choices end in the same persisted draft and same editor:
+
+1. Describe calls `/api/workflows/generator`, validates or parses the generated
+ document as required, then creates the Workflow draft.
+2. Start blank creates a minimal valid draft document with a generated display
+ name, then opens the empty Studio canvas.
+3. Import YAML parses the user's YAML through `/api/editor/parse-yaml`; only a
+ successful parse can be submitted to the draft create endpoint.
+4. Template copies a versioned bundled template into a new document. A
+ template ID is frontend product content, never reused as the new
+ `workflowId`.
+
+Creation buttons enter a stable pending state and reject duplicate submission.
+A scoped create may return either a materialized draft or a `202 Accepted`
+receipt whose `readiness.readable` is `false` and whose stage is
+`projection_pending`. For that receipt, retain the real `workflowId`,
+`commandId`, and readiness message; show an observing state; and poll the
+existing draft GET by exact scope and ID until the read model is readable.
+Navigate only after the materialized response or a successful authoritative
+GET returns the draft.
+
+A bounded observation timeout is delayed materialization, not create failure.
+It keeps the accepted receipt and creation input available and offers another
+readiness check for the same ID. It must not automatically repeat the create
+POST, fabricate a readable draft, or route into a transient `404`. Non-`404`
+query failures remain real errors. There is no optimistic route built from a
+locally generated Workflow ID.
+
+### Common Editor
+
+The vNext editor reuses the behavior and visual language of:
+
+- `src/pages/team-member-workflow-studio/components/WorkflowStudioCanvas.tsx`;
+- `WorkflowStudioNodeLibrary.tsx`;
+- `WorkflowStudioNodeDetailPanel.tsx`;
+- `WorkflowStudioYamlPanel.tsx`;
+- `src/shared/graphs/GraphCanvas.tsx`.
+
+Reuse should happen at component or shared-state boundaries where the current
+code is genuinely independent. The new route must not impersonate a Team,
+create a fake member, or call the existing member route with a Workflow ID.
+Any extraction must preserve the existing Team Member Workflow Studio API and
+behavior.
+
+Canvas, node library, node configuration, YAML editor, Save, validation, and
+Run all operate on one document state. Switching panels must not create a
+second source of truth. Unsaved edits are explicit; leaving the editor prompts
+only when real changes would be lost.
+
+## Run And Observation Experience
+
+### Unified Run Dialog
+
+The dialog identifies the current Workflow, uses `Current draft` when no
+revision DTO exists, accepts the real Run input, and shows any verified
+external-capability requirements. A single Run command is the primary action.
+
+The dialog does not say “saved in Activity” before observation. Its lifecycle
+copy and actions are:
+
+```text
+Ready -> Submitting -> Accepted/Running -> Observing -> Observed
+ | |
+ +-> Failed +-> Observation delayed
+```
+
+An execution failure and an observation delay are different states. A run may
+have been accepted even if the Activity projection is not visible yet.
+
+### Accepted-To-Observed Reconciliation
+
+- Preserve receipt and stream facts in route-owned memory while the editor is
+ open. Session storage may support crash recovery only if existing run-session
+ helpers already define that non-authoritative behavior.
+- Poll or invalidate the scope Activity query after acceptance with bounded
+ backoff. Compare server-provided `runId` and `stateVersion` only.
+- Display `Observed in Activity` and enable the Run-detail link only when the
+ observatory returns the matching authoritative Run.
+- If the execution response does not expose a Run ID that can be matched, show
+ the live console and a general `Open Activity` action, not a fabricated row.
+- When the observation window expires, keep the accepted receipt visible and
+ label Activity as delayed. Do not reinterpret delay as execution failure.
+
+## Activity Experience
+
+### Ledger
+
+Activity is an immutable operational ledger. Desktop rows prioritize status,
+Workflow name, Run ID, origin, started time, updated time, and the detail
+action. Status, origin, definition, schedule, time range, and result limit map
+to server query parameters. `scope=:scopeId` is sent when needed to retain the
+route's explicit scope contract; the server remains the access authority.
+
+Supported UI status values map only from reviewed observatory values:
+
+- `running`;
+- `completed`;
+- `timed_out`;
+- `failed`;
+- `stopped`;
+- `not_found`;
+- `disabled`;
+- `unknown`.
+
+Unknown values remain visible as Unknown and keep their raw value in an
+accessible detail, rather than being coerced to success or failure.
+
+The list does not show duration, token usage, outcome text, source revision,
+or linked Runs because `ObservatoryRunSummary` does not contain those facts.
+It also does not expose a `Needs you` filter: step-level suspension and tool
+approval data live in detail and do not form a stable list filter contract.
+
+### Workflow Filter
+
+Opening Activity from a catalogue row immediately encodes that row's exact
+`workflowId` in the Activity URL. Activity keeps that filter visible and
+removable, resolves the exact scoped Workflow detail, then sends only its
+`ScopeWorkflowDetail.source.definitionActorId` as the observatory `definition`
+filter. Refresh, copied URLs, back, and forward therefore restore the same
+Workflow context. If the URL identity is missing, the detail request fails, or
+the source has no definition identity, the UI shows an honest invalid, error,
+or unavailable state and does not issue an unfiltered Runs query. Removing the
+filter returns to global Activity. The UI must not substitute Workflow name,
+`memberId`, `publishedServiceId`, or a parsed actor string.
+
+Applied Workflow, Schedule, and source scope chips form a contextual row above
+the editable Activity search, status, source, and date controls. These two
+groups must not visually collapse into one control row: when contextual chips
+exist, keep a 12px vertical interval before the editable toolbar, including
+when chips wrap on narrower viewports. When no contextual chip exists, do not
+render an empty context row or reserve its interval.
+
+Submitting the editable Activity controls is an explicit Search command. The
+Search button enters a stable pending state immediately, blocks duplicate
+submission, and remains pending until the request for that submitted filter
+set settles. Keep already committed ledger rows mounted while Search is
+pending when the active query already owns those rows; a new URL-backed filter
+key may use the existing ledger loading surface. Initial page loading and
+background query revalidation must not impersonate a user-issued Search
+command.
+
+### Immutable Run Detail
+
+Run detail loads summary/detail and graph independently so a graph failure does
+not hide the committed Run record. It may display:
+
+- input, final output, and final error;
+- diagnostics and recovery hints;
+- step status, timing, request parameters, output preview, error, and usage;
+- full timeline content, tool calls, and event data;
+- graph nodes and edges using explicit node and step fields;
+- total steps, completed steps, reply count, type counts, token totals, and
+ cost.
+
+The source record contains no edit, overwrite, or destructive action. Copy and
+download actions may export data already visible to the caller, while
+preserving existing secret-redaction rules.
+
+### Retry And Run Again
+
+Recovery calls `POST /api/workflow/runs/fork` and always creates a new Run.
+
+- Retry is enabled only when detail provides an unambiguous failed
+ `stepId`; that ID becomes `startAtStepId`.
+- Run again is enabled only when the first executable step can be identified
+ unambiguously from returned detail or graph fields.
+- The confirmation dialog shows source Run ID, chosen start step, input, and
+ any supported override fields. It never implies the old Run will change.
+- A successful `202 Accepted` response shows `newRunActorId`, command ID,
+ correlation ID, and observation status. It then reconciles through Activity.
+- The UI may keep the current fork receipt while mounted and offer navigation
+ to the new Run once observed. It must not use `localStorage` to manufacture a
+ durable source/child Run graph.
+
+## Settings Experience
+
+The Settings route has three local sections: AI defaults, Account, and
+Advanced. Desktop uses a compact secondary navigation; mobile uses a select or
+menu that preserves the same section labels and focus behavior.
+
+### AI Defaults
+
+Normal state contains two decisions: Preferred service and Default model.
+Changing service refreshes the valid model choices. Dirty state alone reveals
+the shell-fixed Restore and Save changes dock. Settings keeps the shell footer
+slot mounted in clean and dirty states so showing the dock does not remount the
+form or move focus. Save uses the existing LLM settings contract and follows
+accepted-to-observed semantics; success appears only when the authoritative
+query observes the selected values.
+
+Fallback, provider unavailable, and catalogue unavailable are exceptional
+states with direct recovery actions. The UI must not silently select and save
+a different provider or model.
+
+### Account
+
+Account reuses the authenticated session response and existing sign-in,
+sign-out, and service-access actions. It may show user ID, roles, groups,
+provider, scope, and expiry when present. It never renders bearer tokens,
+credentials, or guessed claims.
+
+### Advanced
+
+Advanced renders one read-only set of effective runtime/request values from
+the runtime settings contract. It avoids duplicate presentations of the same
+base URL or runtime mode and offers copy affordances only for non-secret
+values. This section has no save action.
+
+## Information Priority
+
+| Surface | Primary | Secondary | On demand or omitted |
+| --- | --- | --- | --- |
+| Workflows | Name, state/kind, Open, Run availability | Workflow ID, updated time | Raw YAML and source details in editor; Last Run omitted |
+| Creation | Creation method, required input, create action | Validation and generated summary | Raw generated/parsed details after validation |
+| Editor | Canvas, selected node, save state, Run | Node library, YAML, live Run console | Diagnostics and raw events in drawers/panels |
+| Activity | Status, Workflow, Run ID, start/update time | Origin and active filters | Usage, duration, output, diagnostics in detail only |
+| Run detail | Status, immutable identity, failure/output, step trace | Graph, usage, diagnostics | Raw timeline event data on expansion |
+| Settings | Current decision or identity | Scope and effective values | Exceptional diagnostics only when relevant |
+
+Primary commands remain visible without scrolling on common desktop sizes.
+Secondary information may collapse on mobile, but immutable identity and
+recovery actions remain reachable.
+
+## Visual Brief
+
+Direction: **Operational Automation Ledger**.
+
+The interface should let operators scan Workflow definitions and immutable Run
+records quickly, then move directly into editing or failure recovery.
+
+- Density: compact on desktop, balanced on mobile.
+- Typography: use the existing `AlibabaSans` application family. Render Run
+ IDs, timestamps, versions, usage, and other dense data with the existing
+ monospace data style.
+- Palette: dark navigation rail, white work surface, neutral gray dividers,
+ blue primary actions. Green, orange, red, and purple are reserved for status
+ meaning rather than decoration.
+- Geometry: 4-6 px radii, 1 px borders, low or no shadow, stable table columns,
+ stable toolbars, and stable icon-button dimensions.
+- Signature element: Activity rows read as a chronological immutable ledger;
+ the editor reads as a connected-node workbench.
+- Motion: short state transitions only when they communicate panel or request
+ state. Respect `prefers-reduced-motion`.
+- Avoid gradients, glass effects, decorative blobs, KPI card walls, nested
+ cards, oversized headings, marketing composition, and decorative animation.
+
+The PNGs are visual calibration references. Implementation should match their
+hierarchy and density while using production tokens and real data states.
+
+## Responsive And Accessibility Contract
+
+### Desktop, At Least 1200 px
+
+- Fixed-width local navigation, flexible main work area, and optional detail
+ pane.
+- Workflows and Activity use stable tables with persistent headers and no
+ hover-driven layout shift.
+- The editor canvas stays full-bleed inside the workbench; node library and
+ node detail are functional side panels, not decorative cards.
+
+### Tablet, 768-1199 px
+
+- Local navigation compacts; tables drop low-priority columns before enabling
+ horizontal scrolling.
+- Editor side panels overlay or use a mutually exclusive panel region.
+- Settings navigation remains visible without nesting cards.
+
+### Mobile, Below 768 px
+
+- Preserve Workflow search/create/open, Activity status/identity/detail, Run
+ recovery, and Settings save/recovery.
+- Each Activity row becomes a structured list row with status, Workflow, short
+ Run ID, and time. It remains one accessible link or has clearly separated
+ actions.
+- The editor keeps Run, Add node, Edit YAML, Save, and Publish availability.
+ Node detail becomes a bottom sheet with a bounded height and a visible close
+ action.
+- Sticky action areas account for safe-area insets and never cover focused
+ inputs or validation messages.
+- Long IDs and unbroken error strings wrap or truncate with an accessible full
+ value; they never widen the viewport.
+
+### Accessibility
+
+- All primary paths are keyboard operable with visible focus.
+- Icon-only buttons use existing icon components and accessible names; any
+ unfamiliar icon has a tooltip.
+- Status is conveyed by text and semantics, not color alone.
+- Loading announcements use appropriate live regions without repeatedly
+ announcing polling.
+- Dialog focus is trapped and restored to the invoking control. Destructive or
+ external-effect confirmations identify the exact target.
+- Tables retain semantic headers; responsive list rows retain equivalent
+ labels.
+- Canvas nodes and side-panel controls expose an operable non-pointer path.
+
+## State Matrix
+
+| Surface | State | Required presentation | Primary recovery or next action |
+| --- | --- | --- | --- |
+| Workflows | Loading | Stable table skeleton and disabled row actions | Wait/cancel navigation remains possible |
+| Workflows | Empty | No Workflows in this scope | New workflow |
+| Workflows | Error | Inline error with no invented empty state | Retry |
+| Workflows | Partial source failure | Identify unavailable draft or scope source | Retry failed source; keep valid rows |
+| Creation | Ready | Four methods and method-specific inputs | Create draft |
+| Creation | Invalid | Field/YAML error near its source | Correct input |
+| Creation | Submitting | Stable pending button, duplicate submission blocked | Wait |
+| Creation | Failure | Preserve user input and error | Retry |
+| Editor | Loading | Stable canvas shell | Wait or return to catalogue |
+| Editor | Empty draft | Empty canvas guidance | Add node or Edit YAML |
+| Editor | Dirty | Visible unsaved state | Save or discard on leave |
+| Editor | Invalid | Exact validation feedback; Run/Publish disabled | Fix document |
+| Editor | Save accepted | Receipt or confirming status | Continue editing while observing |
+| Run | Ready | Exact Workflow and input | Run |
+| Run | Accepted/running | Receipt/live console, no Activity completion claim | Open Activity or stay |
+| Run | Observation delayed | Accepted receipt plus projection-delay copy | Retry observation/Open Activity |
+| Run | Submission failed | Safe error, editable input | Retry |
+| Activity | Loading | Stable ledger skeleton | Wait |
+| Activity | Searching | Committed ledger remains visible; Search is pending and disabled | Wait |
+| Activity | Empty | No observed Runs for active server filters | Clear filters or Run a Workflow |
+| Activity | Error | Query failure distinct from empty | Retry |
+| Activity | Unknown status | Neutral Unknown label plus raw value | Open detail |
+| Run detail | Loading | Stable Run detail workspace skeleton with one accessible status; history, graph, logs, and inspector geometry stay visible without invented facts or repeated loading copy | Wait or return to Activity |
+| Run detail | Not found | Scoped non-disclosure-safe not-found state | Back to Activity |
+| Run detail | Running | Partial committed trace | Refresh through bounded polling |
+| Run detail | Completed | Final output and immutable trace | Run again when eligible |
+| Run detail | Failed | Error, diagnostic, failed step | Retry when eligible |
+| Run detail | Graph unavailable | Keep all other detail visible | Retry graph |
+| Fork | Ineligible | Disabled action with backend-fact reason | Inspect trace |
+| Fork | Accepted | New Run receipt; source remains unchanged | Observe new Run |
+| Fork | Failed | Source remains unchanged; inputs preserved | Retry request |
+| Settings | Loading | Stable controls without default invention | Wait |
+| Settings | Clean | Quiet current values | Edit |
+| Settings | Dirty | Shell-fixed Restore and Save changes dock | Save or restore |
+| Settings | Save accepted | Confirming saved values | Continue observing |
+| Settings | Save failed | Preserve dirty choices | Retry or discard |
+| Settings | Catalogue unavailable | Existing choice remains visible | Retry catalogue |
+| Account | Unauthenticated/expired | Clear session state | Sign in |
+| Advanced | Unavailable | Read-only query error | Retry |
+
+## Interaction Contract
+
+- All list rows have one clear navigation target. Secondary actions do not
+ trigger row navigation through event bubbling.
+- Search inputs debounce only network-backed filters. Client-side Workflow
+ search responds immediately and never changes source data.
+- Server Activity filters are encoded in the URL query so refresh and back
+ navigation preserve operator context. Invalid values are ignored with a
+ visible reset path, not sent blindly.
+- Query keys include authoritative `scopeId` and normalized filters. Changing
+ scope cancels or isolates in-flight results so one scope cannot flash in
+ another scope's workbench.
+- Mutations disable duplicate submission and preserve a request receipt. They
+ do not optimistically synthesize authoritative IDs or projection versions.
+- User-issued async commands acknowledge immediately, remain pending until
+ their own request settles, and reject duplicate submission. Background
+ fetching does not activate an unrelated command's pending presentation.
+- Save and Run failures keep user-authored input. A retry repeats only the
+ user's explicit action.
+- `Open Activity` from a Workflow carries the catalogue row's authoritative
+ `workflowId` in the URL and resolves the definition filter at the Activity
+ boundary. `Back to Workflow` appears only when the Run response provides a
+ trustworthy association; name matching is insufficient.
+- Browser back closes modal/detail layers before leaving the owning workbench
+ when those layers are URL-addressable.
+- Manual Run detail refresh preserves the currently committed history, graph,
+ logs, and inspector while revalidating all three sources. The Refresh action
+ changes to a disabled `Refreshing…` state immediately. The full Run workspace
+ simultaneously enters one accessible busy state that keeps committed content
+ visible beneath a light interaction-blocking overlay and a single centered
+ refresh indicator. It never returns to the initial skeleton, never shifts the
+ page shell, and leaves global navigation available. Completion removes the
+ overlay and reports complete success or any partial failure through one toast;
+ unchanged data is still a successful refresh and never causes a blank-state
+ flash.
+- Copy actions report success or failure without changing layout. Export uses
+ structured serialization where available rather than string concatenation.
+
+## Backend Contract Mapping
+
+All mappings below were reviewed from `origin/feature/integrate` at
+`72093afa4490adf6eb25d83c3db0b9c182cd6ff0`. The implementation may add or
+extend frontend adapters, but may not change these backend contracts.
+
+### Workflow Drafts
+
+```text
+GET /api/workspace/workflow-drafts?scopeId=:scopeId
+POST /api/workspace/workflow-drafts?scopeId=:scopeId
+GET /api/workspace/workflow-drafts/:workflowId?scopeId=:scopeId
+PUT /api/workspace/workflow-drafts/:workflowId?scopeId=:scopeId
+DELETE /api/workspace/workflow-drafts/:workflowId?scopeId=:scopeId
+```
+
+The current `src/shared/studio/api.ts` already exposes
+`listWorkflowDrafts`, `getWorkflowDraft`, `createWorkflowDraft`,
+`updateWorkflowDraft`, `deleteWorkflowDraft`, `listWorkflows`, `getWorkflow`,
+and `saveWorkflow`. Reuse those typed boundaries rather than issuing ad hoc
+fetches from components.
+
+For a scoped create, `createWorkflowDraft` can return
+`StudioWorkflowSaveResult.kind = "accepted"` with a
+`StudioWorkflowDraftCreateAcceptedReceipt`. The receipt is command acceptance,
+not a readable draft. Reuse the existing accepted-to-materialized behavior:
+query `getWorkflowDraftFile(receipt.workflowId, scopeId)`, treat bounded `404`
+responses as projection pending, and enter the editor only after the query
+succeeds. Preserve the receipt on timeout so retry observes the same command
+result rather than creating a duplicate draft.
+
+`getWorkflow` can fall back to a committed scope Workflow and marks it with
+`draftExists=false`. `saveWorkflow` intentionally creates a new workspace
+draft on its first save. The implementation must use the returned draft ID,
+wait through the same accepted-to-materialized contract, and replace the
+committed-only route identity if the created draft ID differs.
+
+### Generation And Editing
+
+```text
+POST /api/workflows/generator
+POST /api/editor/parse-yaml
+POST /api/editor/serialize-yaml
+POST /api/editor/validate
+POST /api/editor/normalize
+```
+
+Use `studioApi.authorWorkflow`, `parseYaml`, and `serializeYaml` where their
+current typed contracts match. Validation and normalization remain explicit
+server operations; UI success must not be based solely on client parsing.
+
+### Scope Workflows
+
+```text
+GET /api/scopes/:scopeId/workflows
+GET /api/scopes/:scopeId/workflows/:workflowId
+PUT /api/scopes/:scopeId/workflows/:workflowId
+POST /api/scopes/:scopeId/workflows:save-and-bind
+```
+
+`ScopeWorkflowDetail.source.definitionActorId` is the Activity definition
+filter identity. `ScopeWorkflowSummary.workflowId`, `serviceKey`, `actorId`,
+and `activeRevisionId` retain their own meanings and are not automatically
+callable service IDs.
+
+### Draft Run
+
+```text
+POST /api/scopes/:scopeId/workflow/draft-run
+Accept: text/event-stream
+```
+
+Use `src/shared/api/runtimeRunsApi.ts` and its `streamDraftRun` adapter. A
+successful connection or accepted command is not equivalent to an observed
+Activity row.
+
+### Activity
+
+```text
+GET /api/workflow/observatory/runs
+GET /api/workflow/observatory/runs/:runId
+GET /api/workflow/observatory/runs/:runId/graph
+```
+
+List filters reviewed from the endpoint are `scope`, `status`, `origin`,
+`definition`, `schedule`, `from`, `to`, and `take`. Normal callers remain
+limited to their own server-resolved scope; cross-scope access is enforced by
+the backend.
+
+The list summary contains only `runId`, `workflowName`, `status`, `success`,
+`startedAtUtc`, `updatedAtUtc`, `stateVersion`, `scopeId`, and `runOrigin`.
+Detail adds input, final output/error, diagnostics, steps, timeline,
+statistics, and usage totals. Graph is a separate query.
+
+### Run Fork
+
+```text
+POST /api/workflow/runs/fork
+```
+
+The request includes `sourceRunId`, `startAtStepId`, and optional supported
+YAML, sub-YAML, variable, input, command, and correlation overrides. The
+`202 Accepted` response contains source Run ID, new Run actor ID, Workflow
+name, accepted command ID, correlation ID, and status URL. It is a receipt, not
+a completed Run detail.
+
+### Settings And Identity
+
+```text
+GET /api/user-config/llm
+PUT /api/user-config/llm
+GET /api/user-config/runtime
+GET /api/auth/me
+```
+
+Reuse existing Settings selection and save-observation behavior in
+`src/pages/settings/`, especially `userLlmSelection.ts` and
+`userLlmSaveObservation.ts`. Reuse existing auth/session actions rather than
+implementing a parallel authentication flow.
+
+## Excalidraw-To-Backend Deviations
+
+These deviations are mandatory. They are not optional polish deferrals:
+
+| Excalidraw idea | Reviewed contract reality | Required frontend behavior |
+| --- | --- | --- |
+| `Current draft · revision N` | Workspace draft DTO has no historical revision number | Show `Current draft` and real updated time only |
+| Revision on every Activity row | Summary has no revision field | Omit it |
+| Duration, usage, and outcome in Activity rows | Summary has no such fields | Show them in detail only when returned |
+| `Needs you` Activity filter | No stable list filter; suspension detail is per Run | Omit the filter; surface actionable detail inside Run detail |
+| Last Run in Workflow catalogue | Activity summary has no Workflow ID join key | Omit Last Run; never join by Workflow name |
+| Published Workflow Run inferred from catalogue IDs | Callable `publishedServiceId` is distinct | Disable published Run until explicit callable identity is available |
+| Run is immediately “saved in Activity” | execution and projection are eventually consistent | Show Accepted/Running, then Observed only after query confirmation |
+| Retry and Run again create persistent linked records | fork receipt exists, but Activity DTO has no durable lineage fields | Keep source immutable; show current receipt and observed new Run without claiming durable linkage |
+| Run again always available | first executable step may be ambiguous | Enable only when an explicit first step is determinable |
+| Retry always available on failure | failed step ID may be missing or ambiguous | Enable only with an explicit failed `stepId` |
+| All retained Runs implies complete history | list is bounded by `take` | Label and present the returned recent window; do not display a fabricated total |
+| Prototype uses `localStorage` for Runs and settings | backend endpoints are authoritative | Do not persist authoritative Runs or LLM defaults in browser storage |
+
+No backend work is proposed to close these gaps. If a future product decision
+requires exact mockup parity, that must be scoped separately and must not be
+smuggled into this frontend-only branch.
+
+## Proposed Frontend Structure
+
+The implementation should keep route ownership local and transport adaptation
+shared:
+
+```text
+apps/aevatar-console-web/
+ config/routes.ts
+ src/pages/workflow-activity-vnext/
+ index.tsx
+ WorkflowActivityVNextShell.tsx
+ navigation.ts
+ models.ts
+ workflows/
+ WorkflowsPage.tsx
+ NewWorkflowPage.tsx
+ WorkflowEditorPage.tsx
+ workflowCreation.ts
+ activity/
+ ActivityPage.tsx
+ RunDetailPage.tsx
+ runRecovery.ts
+ settings/
+ SettingsPage.tsx
+ components/
+ styles.ts
+ src/shared/api/
+ workflowActivityApi.ts
+ workflowActivityApi.test.ts
+ src/shared/models/
+ workflowActivity.ts
+ src/locales/
+```
+
+This is a boundary proposal, not a mandate to create one file per label. Keep
+view-only state near the page, reuse existing Studio and Settings helpers, and
+put request construction/response decoding in typed shared adapters. Do not
+duplicate existing APIs merely to give vNext new names.
+
+New user-facing copy must use the existing locale system. New icons come from
+the enabled Ant Design icon set. The implementation should not add a UI or
+graph dependency unless an existing capability is demonstrably insufficient.
+
+## Implementation Phases
+
+### Phase 0: Review And Contract Lock
+
+- Approve this specification and explicitly accept the backend-honest
+ deviations.
+- Confirm the account-resolving home entry and the scoped namespace spelling.
+- Recheck `origin/feature/integrate` before implementation in case its contract
+ commit has moved; record any change in this document before coding.
+
+### Phase 1: Isolated Shell And Typed Boundaries
+
+- Add hidden scoped routes and the account-resolving home described above.
+- Add the vNext-local workbench shell and scoped navigation.
+- Add typed Activity/fork decoders and query keys at shared boundaries.
+- Prove route scope and identity isolation before building product pages.
+
+### Phase 2: Workflows And Direct Creation
+
+- Implement the authoritative Workflow catalogue.
+- Implement Describe, blank, YAML import, and versioned template creation.
+- Reconcile `202 projection_pending` receipts to a readable draft before
+ navigation, without resubmitting create on observation timeout.
+- Open committed-only Workflows as explicit source state; first Save creates a
+ draft and replaces the route with the returned materialized draft ID.
+
+### Phase 3: Common Editor And Run
+
+- Extract or reuse team-independent canvas and panel capabilities while
+ preserving existing Studio behavior.
+- Implement document save/validate states and the unified Run dialog.
+- Implement live Run console and accepted-to-observed reconciliation.
+
+### Phase 4: Activity And Recovery
+
+- Implement scoped server filters, immutable Run detail, and independent graph
+ loading.
+- Implement conservative Retry/Run again eligibility and fork receipts.
+- Verify source Run immutability across every recovery path.
+
+### Phase 5: Settings And Responsive Completion
+
+- Recompose current AI defaults, Account, and Advanced behavior.
+- Complete desktop, tablet, and mobile layouts and keyboard operation.
+- Apply realistic long IDs, errors, empty lists, unavailable catalogues, and
+ delayed observation data during visual verification.
+
+## Focused Testing Strategy
+
+Tests must protect observable contracts rather than internal implementation.
+The expected high-value coverage is:
+
+- route/config integration proves all vNext URLs resolve, `new` is not captured
+ as a Workflow ID, the home resolves the account scope, and explicit deep links
+ and scoped Team routes keep their destinations;
+- auth-route integration proves an unauthenticated vNext URL uses the current
+ sanitized Login redirect and callback return path, while existing Login,
+ callback, retry, service-access review, and other protected routes retain
+ their behavior;
+- localization coverage proves new message IDs exist in `en-US` and `zh-CN`,
+ language switching uses the existing locale system, and the vNext shell does
+ not own a second locale state;
+- Activity adapter tests prove query encoding, scope handling, response
+ decoding, unknown status preservation, detail/graph separation, and fork
+ receipt decoding;
+- a Workflows route test proves each creation path handles both materialized
+ and `202 projection_pending` create results, enters the editor only after an
+ authoritative draft GET succeeds, retains the receipt/input on timeout, and
+ never resubmits create as an observation retry;
+- a committed-only editor test proves first Save calls draft create rather
+ than update, waits for materialization, and replaces the route with the
+ returned draft ID without treating the committed source ID as the draft ID;
+- an editor/Run route test proves Accepted/Running does not claim Activity
+ persistence and Observed appears only after an authoritative Run response;
+- Activity route tests cover loading, empty, error, server filter changes,
+ immutable detail, and graph-only failure;
+- recovery tests prove Retry requires a failed step, Run again requires a
+ first executable step, and the source record remains unchanged after fork;
+- Settings route tests cover clean, dirty, accepted/confirming, observed,
+ catalogue unavailable, and save-failure recovery states.
+
+Use a small explicit set of colocated Jest files or individual test names.
+Do not run the complete frontend suite, all jsdom tests, integration tests,
+end-to-end tests, or smoke tests unless the user explicitly requests broader
+verification in that implementation task.
+
+## Verification Commands
+
+For this documentation-only phase:
+
+```bash
+python3 apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/verify-baseline.py
+bash tools/docs/lint.sh
+git diff --check
+```
+
+For the later implementation, choose exact incremental Jest paths based on the
+files actually changed, for example:
+
+```bash
+pnpm --dir apps/aevatar-console-web exec jest \
+ --runInBand \
+ --runTestsByPath \
+ src/shared/api/workflowActivityApi.test.ts \
+ src/pages/workflow-activity-vnext/index.test.tsx
+pnpm --dir apps/aevatar-console-web tsc
+pnpm --dir apps/aevatar-console-web biome:lint
+pnpm --dir apps/aevatar-console-web build
+bash tools/docs/lint.sh
+git diff --check
+```
+
+The final test file names must match the implementation. Route changes require
+the production build. Biome should be narrowed to affected files when that
+meaningfully validates the changed surface; use package lint when cross-cutting
+scope makes a narrow command misleading.
+
+Before installing dependencies, starting the frontend, opening it for browser
+QA, or running an integration-style command in a linked worktree, follow the
+active local `AGENTS.md` worktree environment synchronization instructions.
+Do not copy a developer-specific hook path into shared implementation docs.
+
+Before OAuth browser verification, confirm without printing dotenv values that
+the actual origin exactly matches both `NYXID_REDIRECT_URI` and
+`AEVATAR_CONSOLE_FRONTEND_PORT` in scheme, host, and port. When another server
+occupies the configured port, update both coupled values in this worktree to a
+free accepted origin and leave the primary checkout unchanged.
+
+## Acceptance Criteria
+
+The later frontend implementation is complete only when all of the following
+are true:
+
+- The diff changes no backend file and requires no backend deployment or
+ contract change.
+- Production routes contain no mock fallback, demonstration rows, timer-made
+ API responses, or browser-storage substitute for unavailable remote state.
+- API loading, empty, delayed, unavailable, decoding-failure, and request-error
+ states remain visibly distinct when tested without fixture fallbacks.
+- All vNext resource pages resolve under
+ `/scopes/:scopeId/workflow-activity-vnext`; the unscoped `/workflows` home
+ resolves the account scope. Home aliases follow this entry; scoped Team and
+ runtime page behavior remains intact.
+- Unauthenticated vNext entry uses the existing protected-route redirect,
+ `/login`, `/auth/callback`, sanitized `returnTo`, session restoration, and
+ service-access review behavior, returning to the original scoped URL.
+- Login, callback, language, and account controls use the vNext visual system
+ without changing their shared behavior for any existing route.
+- Every vNext message exists in `en-US` and `zh-CN`, and language selection
+ continues through the existing Umi locale and `ConsoleLanguageSwitch`
+ behavior without a second locale state or storage key.
+- The route `scopeId` is retained across navigation and sent only through
+ scope-validating backend contracts.
+- A user can create a persisted draft with all four creation methods and enter
+ one common connected-node editor without creating a member; a `202`
+ create receipt remains Accepted/Observing until the exact scoped draft GET
+ confirms readability.
+- A committed-only Workflow remains a distinct source: first Save creates a
+ draft, observes materialization, and adopts the returned draft ID instead of
+ updating or assuming identity equality with the committed Workflow.
+- Workflow, member, definition actor, published service, and Run identities are
+ never inferred from one another.
+- Run shows Accepted/Running separately from Activity observation and never
+ fabricates an Activity record.
+- Activity URLs preserve the catalogue row's authoritative `workflowId`, and
+ Activity sends only the resolved definition identity through supported
+ server parameters. Missing or invalid Workflow context never falls back to
+ an unfiltered Runs query.
+- Activity list rows contain only summary facts; duration, usage, diagnostics,
+ and outputs appear only from Run detail.
+- Run detail is immutable, and Retry/Run again always create a new accepted
+ Run with conservative eligibility.
+- AI defaults, Account, and Advanced use current backend settings/auth
+ contracts and expose complete dirty, accepted, observed, error, and recovery
+ states.
+- Desktop, tablet, and mobile layouts preserve primary actions, do not overlap
+ or horizontally escape, and remain keyboard accessible.
+- The implementation task and pull request carry the baseline declaration from
+ `docs/design-baselines/workflow-activity-vnext/README.md`, including the
+ Excalidraw hash, user-path specification, and the real-API-only data-source
+ statement.
+- Every user path defined in
+ `2026-08-04-workflow-activity-vnext-user-paths.md` reaches its documented
+ completion evidence and recovery state without mock fallback data.
+- Focused affected tests, TypeScript, affected lint, production build, docs
+ lint, and `git diff --check` pass. The final report lists exact commands and
+ explicitly states that the full frontend test suite was not run.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-user-paths.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-user-paths.md
new file mode 100644
index 0000000000..24cd2de614
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-04-workflow-activity-vnext-user-paths.md
@@ -0,0 +1,897 @@
+# Workflow + Activity + Settings vNext User Paths
+
+## Status
+
+Proposed as the normative user-path companion to
+`2026-08-04-workflow-activity-vnext-design.md` on 2026-08-04.
+
+This document defines how a scoped Aevatar Console user moves through the 17
+Excalidraw frames, which route owns each step, which real API fact advances the
+journey, and what the UI must do when that fact is unavailable. It does not
+authorize frontend implementation until the design package is approved.
+
+Implementation branch: `feat/2026-08-04_workflow-activity-vnext`.
+
+## Required Reading And Precedence
+
+Anyone implementing or reviewing these paths must read, in order:
+
+1. `apps/aevatar-console-web/docs/design-baselines/`
+ `workflow-activity-vnext/aevatar-workflow-activity-vnext.excalidraw` for
+ visual hierarchy, information architecture, and interaction intent.
+2. `2026-08-04-workflow-activity-vnext-design.md` for routes, identity, API,
+ state, responsive behavior, deviations, and acceptance criteria.
+3. This document for end-to-end user movement, decision points, recovery, and
+ path completion evidence.
+4. The PNGs and `prototype.html` for visual and interaction calibration only.
+
+The backend contract in `origin/feature/integrate` at
+`72093afa4490adf6eb25d83c3db0b9c182cd6ff0` remains authoritative for data.
+The prototype's hard-coded objects, `localStorage`, timers, and simulated
+receipts are never production data sources.
+
+## User And Job Model
+
+### Primary User
+
+The primary user is an authenticated automation builder or operator working
+inside one authorized Aevatar `scopeId`. The same person may move between three
+jobs without leaving the vNext workbench:
+
+- author a Workflow draft;
+- run and inspect an automation;
+- manage personal AI defaults and account/runtime context.
+
+The product does not require the user to understand actor prefixes, projection
+internals, member bindings, or service-key conventions. Those facts remain
+technical details unless a real backend response makes them useful for
+diagnosis.
+
+### User Mental Model
+
+```text
+Workflows = what I can create and edit
+Run = execute the current valid Workflow
+Activity = immutable records the system has actually observed
+Settings = my AI defaults, identity, and effective runtime context
+```
+
+The user must never be led to believe that:
+
+- a generated Workflow has been saved before draft creation succeeds;
+- an accepted Run is already visible in Activity;
+- a failed API request means the collection is empty;
+- Retry edits or replaces the failed Run;
+- a local prototype record is a server record;
+- a `workflowId`, `memberId`, `definitionActorId`, and
+ `publishedServiceId` are the same identity.
+
+## Entry, Navigation, And Exit
+
+### Entry Contract
+
+The default console home is `/workflows`. On each entry it refreshes
+`GET /api/auth/me`, waits for the authoritative account `scopeId`, and replaces
+the URL with the scoped Workflows catalogue. Request failure or missing scope
+stays recoverable through Retry; stale cache and browser user IDs never supply
+the scope. Explicitly unauthenticated sessions use the existing login route.
+This 2026-09-14 decision supersedes the original preview-only entry restriction.
+
+A direct authenticated scoped entry also remains available:
+
+```text
+/scopes/:scopeId/workflow-activity-vnext
+```
+
+It redirects only within the new namespace to:
+
+```text
+/scopes/:scopeId/workflow-activity-vnext/workflows
+```
+
+The home aliases `/`, `/overview`, and `/scopes`, plus default login/callback
+fallback, open `/workflows`. Explicit safe return targets retain their path,
+query, and fragment. Scoped Team/member and runtime routes keep their existing
+owners; opening Workflows does not require visiting a Teams or members home.
+
+If authentication is missing or expired, existing authentication behavior owns
+the interruption and return. If the server rejects the route `scopeId`, the
+workbench renders the existing access/not-found behavior. It must not switch to
+a cached scope or show sample data.
+
+### Local Navigation
+
+The vNext shell has three stable destinations:
+
+| Destination | Route | User job |
+| --- | --- | --- |
+| Workflows | `/scopes/:scopeId/workflow-activity-vnext/workflows` | Create, open, edit, and run Workflows |
+| Activity | `/scopes/:scopeId/workflow-activity-vnext/activity` | Scan and inspect observed Runs |
+| Settings | `/scopes/:scopeId/workflow-activity-vnext/settings` | Manage AI defaults and inspect identity/runtime context |
+
+Every link carries the route `scopeId` unchanged. Switching local sections does
+not rewrite it from a display name, remembered selection, or API response.
+
+### Exit Contract
+
+- Back navigation returns to the previous route without rewriting legacy
+ history.
+- Leaving a dirty editor or dirty Settings form requires a loss-of-changes
+ decision.
+- Leaving an accepted or running Run does not cancel it unless the real API
+ exposes and the user explicitly invokes a supported cancel action.
+- Returning later reloads authoritative API state. It does not reconstruct a
+ successful screen from prototype or browser-storage records.
+
+## Path Map
+
+```mermaid
+%%{init: {"maxTextSize": 100000, "flowchart": {"useMaxWidth": false, "nodeSpacing": 10, "rankSpacing": 50}, "themeVariables": {"fontSize": "10px"}}}%%
+flowchart TD
+ A["Open protected scoped URL"] --> A1{"Existing session usable?"}
+ A1 -->|"Yes"| B["vNext scoped entry"]
+ A1 -->|"No"| A2["Existing Login route"]
+ A2 --> A3["Existing NyxID redirect"]
+ A3 --> A4["Existing Auth callback"]
+ A4 --> B
+ B --> B1["Workflows catalogue"]
+ B1 --> C["New workflow"]
+ C --> D["Describe"]
+ C --> E["Start blank"]
+ C --> F["Import YAML"]
+ C --> G["Template"]
+ D --> H["Persisted Workflow draft"]
+ E --> H
+ F --> H
+ G --> H
+ B1 --> I["Open existing Workflow"]
+ H --> J["Connected-node editor"]
+ I --> J
+ J --> K["Save and validate"]
+ K --> L["Unified Run dialog"]
+ L --> M["Accepted or running"]
+ M --> N["Observed Activity row"]
+ B1 --> O["Activity filtered by Workflow"]
+ N --> P["Immutable Run detail"]
+ O --> P
+ P --> Q["Retry failed Run"]
+ P --> R["Run again"]
+ Q --> S["New accepted Run"]
+ R --> S
+ S --> N
+ B --> T["Settings"]
+ T --> U["AI defaults"]
+ T --> V["Account"]
+ T --> W["Advanced"]
+```
+
+## Path Inventory
+
+| ID | Path | Excalidraw frames | Completion evidence |
+| --- | --- | --- | --- |
+| UP-00 | Authenticate and enter the scoped workbench | 01, 13 | Existing callback returns to the original vNext URL and real scoped queries begin |
+| UP-01 | Browse and search Workflows | 01, 13 | Authoritative catalogue rows or a truthful empty/error state |
+| UP-02 | Create by Describe | 02, 03 | Draft create returns a real `workflowId`, and the exact scoped draft GET confirms it is readable before editor navigation |
+| UP-03 | Start blank and add the first node | 02, 04 | Persisted draft opens; valid document state controls Save/Run/Publish |
+| UP-04 | Import YAML | 02, 05 | Server parse/validation succeeds before draft creation |
+| UP-05 | Create from Template | 02, 06 | Bundled template is copied into a newly persisted independent draft |
+| UP-06 | Open, edit, validate, and save | 03-06, 08 | Authoritative draft update succeeds; unsaved state clears |
+| UP-07 | Run a current draft | 07, 08 | Real draft-run stream/receipt reaches Accepted or Running |
+| UP-08 | Observe a Run in Activity | 08-10, 13 | Observatory returns an authoritative `runId` and `stateVersion` |
+| UP-09 | Filter Activity by Workflow | 09 | The row's exact `workflowId` remains visible in the URL and resolves to an exact `definitionActorId`, or the filtered view is honestly unavailable |
+| UP-10 | Inspect immutable Run detail | 11 | Detail response renders committed facts; graph may load independently |
+| UP-11 | Retry a failed Run | 12 | Fork API accepts an explicit failed `stepId`; source Run remains unchanged |
+| UP-12 | Run a completed Workflow again | 11, 12 | Fork API accepts an explicit first execution step; source remains unchanged |
+| UP-13 | Change AI defaults | 14, 15 | Authoritative settings query observes the selected service and model |
+| UP-14 | Inspect Account and session | 16 | `/api/auth/me` or existing auth state renders real identity facts |
+| UP-15 | Inspect Advanced runtime values | 17 | `/api/user-config/runtime` returns the read-only effective values |
+| UP-16 | Complete key jobs on tablet/mobile | 13, 17 | Same API and identity evidence is reachable without desktop-only controls |
+
+## UP-00: Authenticate And Enter The Scoped Workbench
+
+**Intent:** Reach the isolated vNext experience through Aevatar's existing
+login, callback, session, return-to, and locale behavior without affecting
+other console routes.
+
+**Start:** An authenticated or unauthenticated user opens the direct namespace
+URL for an authorized `scopeId`.
+
+**Steps:**
+
+1. The existing app runtime classifies `/login` and `/auth/callback` as public
+ and the vNext URL as protected.
+2. If a stored or restorable session exists, current
+ `AuthSessionBootstrap`/`ensureActiveAuthSession` behavior restores it.
+3. Without a usable session, `ProtectedRouteRedirectGate` sends the complete
+ scoped path, search, and hash through `sanitizeReturnTo` into the existing
+ `/login?redirect=...` route.
+4. The existing Login page calls
+ `NyxIDAuthClient.loginWithRedirect({ returnTo })`.
+5. NyxID returns through the unchanged `/auth/callback` route.
+6. The existing callback calls `handleRedirectCallback()` and replaces the
+ location with its sanitized `result.returnTo`.
+7. The user returns to the original vNext URL. Only the namespace root
+ redirects within the new namespace to Workflows; a requested child route
+ retains its own path, query, and hash.
+8. The local Workflows, Activity, and Settings navigation appears.
+9. Workflows begins its real scoped queries.
+10. Language and account controls reuse `ConsoleLanguageSwitch` and existing
+ account behavior, even if their placement/style changes in the vNext shell.
+
+**Completion:** The callback returns to the original scoped URL, the existing
+session is active, the selected `en-US` or `zh-CN` locale is preserved, and the
+page shows a loading, populated, empty, partial-source, or error state derived
+from real requests.
+
+**Recovery:** Login configuration, callback, service-access review,
+unauthorized, forbidden, missing-scope, and network failures retain their
+current distinct retry/back behavior. No alternate scope, second login flow,
+mock user, or demonstration catalogue is substituted.
+
+**Presentation:** Login, callback, language, and account controls adopt vNext
+tokens, density, border, radius, and status styling only. Authentication,
+return, session, language switching, language persistence, and error behavior
+remain the existing Aevatar logic.
+
+The Excalidraw contains no separate Login frame. Login presentation therefore
+inherits only the approved vNext visual direction; it must not introduce new
+auth decisions, fields, providers, or navigation that the design does not
+define.
+
+## UP-01: Browse And Search Workflows
+
+**Intent:** Find an existing Workflow or start creating one.
+
+**Route:**
+`/scopes/:scopeId/workflow-activity-vnext/workflows`.
+
+**Authoritative reads:**
+
+```text
+GET /api/workspace/workflow-drafts?scopeId=:scopeId
+GET /api/scopes/:scopeId/workflows
+```
+
+The existing typed `studioApi.listWorkflows` boundary may coordinate these
+sources. The UI keeps draft and scope Workflow source types explicit.
+
+**Steps:**
+
+1. The user scans Workflow name, source/state, updated time, and available
+ actions.
+2. Search filters only the currently returned authoritative fields.
+3. `Open` navigates using that row's real `workflowId`.
+4. `New workflow` navigates to the creation chooser.
+5. `Run` is enabled for a validated runnable draft. Published Run is enabled
+ only when an explicit callable `publishedServiceId` is available.
+
+**Completion:** The selected row opens or the user reaches creation. A
+successful empty response produces the New workflow empty state.
+
+**Not allowed:**
+
+- Do not show Last Run by joining Activity on `workflowName`.
+- Do not invent revision numbers or published availability.
+- Do not use sample rows when one catalogue source fails. Preserve valid rows
+ from the successful source and identify the failed source.
+
+### Shared Draft Create Observation Contract
+
+UP-02 through UP-05 share one create-completion rule. A scoped draft create may
+return a materialized draft or a `202 Accepted` receipt with a real
+`workflowId`, command receipt, and `readiness.stage = projection_pending` while
+`readiness.readable = false`.
+
+- A materialized draft can enter the editor immediately using its returned ID.
+- An accepted receipt enters an explicit Accepted/Observing state and polls
+ `GET /api/workspace/workflow-drafts/:workflowId?scopeId=:scopeId`.
+- A bounded `404` during that observation window means the projection is still
+ pending; it does not mean the create failed and it does not authorize a
+ second create request.
+- Navigation occurs only after the exact scoped GET returns the readable draft.
+- A timeout preserves the receipt, input, and returned ID. Retry checks the
+ same ID again. It never silently resubmits the create POST or fabricates a
+ local draft.
+- Any non-`404` read failure is shown as its real authorization, decoding, or
+ network error and remains distinct from projection delay.
+
+## UP-02: Create A Workflow By Description
+
+**Intent:** Turn a natural-language automation goal into an editable Workflow
+draft.
+
+**Frames:** 02 New workflow and 03 Describe generated draft.
+
+**Route transition:**
+
+```text
+.../workflows/new
+ -> .../workflows/:workflowId
+```
+
+**Steps and real evidence:**
+
+1. The user chooses `Describe` and enters a concrete goal.
+2. Submit calls `POST /api/workflows/generator` through the existing typed
+ authoring boundary.
+3. The generated Workflow is parsed, normalized, or validated through the
+ reviewed editor endpoints as required by its response form.
+4. The user sees a generated summary or correctable validation problem.
+5. Confirming creation calls
+ `POST /api/workspace/workflow-drafts?scopeId=:scopeId`.
+6. Only the draft-create response establishes the real `workflowId`.
+7. If creation is accepted but not readable, the UI follows the shared create
+ observation contract for that exact scope and ID.
+8. The router opens the common connected-node editor only after the draft is
+ materialized or the authoritative GET confirms readability.
+
+**Completion:** The editor loads the persisted draft and shows the generated
+nodes and edges. Generation success alone is not completion.
+
+**Recovery:** Generator, parser, validator, and draft-create failures preserve
+the user's description and any safe generated preview. A create-observation
+timeout preserves the accepted receipt and retries only the GET for the same
+ID. Retry repeats only the failed explicit action. No local ID or demonstration
+draft is routed as saved.
+
+## UP-03: Start Blank And Add The First Node
+
+**Intent:** Build a Workflow manually without a generated starting structure.
+
+**Frames:** 02 New workflow and 04 Start blank.
+
+**Steps:**
+
+1. The user chooses `Start blank`.
+2. The frontend creates a minimal, well-formed Workflow document as explicit
+ frontend authoring content.
+3. The draft-create API returns its authoritative `workflowId` and follows the
+ shared create observation contract when the draft is not readable yet.
+4. The editor opens an empty connected-node canvas only after readability is
+ confirmed.
+5. `Add node` opens the searchable Node library.
+6. Choosing a node adds it to the shared document state.
+7. Selecting the node opens Node configuration; `Edit YAML` opens the same
+ document through the YAML panel.
+8. Validation controls Save, Run, and Publish availability.
+
+**Completion:** At least one meaningful node is configured and the user can
+save a valid authoritative draft.
+
+**Recovery:** An empty draft may remain saved, but Run and Publish stay
+disabled when the real validator says it is not executable. The UI does not
+insert a sample node and call the draft valid.
+
+## UP-04: Import YAML
+
+**Intent:** Create a Workflow from YAML the user already owns.
+
+**Frames:** 02 New workflow and 05 Import YAML.
+
+**Steps:**
+
+1. The user chooses `Import YAML` and enters or pastes YAML.
+2. Submit calls `POST /api/editor/parse-yaml`.
+3. Validation or normalization runs through the reviewed editor contracts when
+ needed.
+4. Parse and validation feedback is shown against the YAML source.
+5. Only a successful document can be submitted to the draft-create API.
+6. The returned `workflowId` follows the shared create observation contract;
+ the common editor opens with parsed nodes and edges only after readability
+ is confirmed.
+
+**Completion:** The editor represents the same authoritative document in
+canvas and YAML modes.
+
+**Recovery:** Invalid YAML never creates a partial draft. Network or decoder
+failure is not relabeled as invalid YAML or empty content. User input remains
+available for correction and retry.
+
+## UP-05: Create From A Template
+
+**Intent:** Begin from a known useful structure while creating an independent
+Workflow.
+
+**Frames:** 02 New workflow and 06 Template.
+
+**Steps:**
+
+1. The user chooses `Template`.
+2. The UI identifies templates as bundled frontend product content unless a
+ real template API is introduced and reviewed later.
+3. The user selects and previews a versioned template.
+4. Its Workflow document is copied, parsed, and validated.
+5. Draft creation returns a new authoritative `workflowId` and follows the
+ shared create observation contract when projection is pending.
+6. The common editor opens the copied nodes and edges only after readability
+ is confirmed.
+
+**Completion:** Editing the new draft cannot mutate the bundled template or
+another user's Workflow.
+
+**Not allowed:** The UI must not render bundled templates as “loaded from the
+server,” attach fake server timestamps, or reuse the template ID as a
+`workflowId`.
+
+## UP-06: Open, Edit, Validate, Save, And Publish Boundary
+
+**Intent:** Change one Workflow through a single connected-node authoring
+model.
+
+**Route:**
+`/scopes/:scopeId/workflow-activity-vnext/workflows/:workflowId`.
+
+**Authoritative operations:**
+
+```text
+GET /api/workspace/workflow-drafts/:workflowId?scopeId=:scopeId
+PUT /api/workspace/workflow-drafts/:workflowId?scopeId=:scopeId
+POST /api/workspace/workflow-drafts?scopeId=:scopeId
+GET /api/scopes/:scopeId/workflows/:workflowId
+POST /api/editor/parse-yaml
+POST /api/editor/serialize-yaml
+POST /api/editor/validate
+POST /api/editor/normalize
+```
+
+**Steps:**
+
+1. The route loads the exact Workflow under the route scope and retains whether
+ the source is an existing draft or a committed-only Workflow.
+2. Canvas, Node library, Node configuration, and YAML panel edit one document
+ state.
+3. A node or YAML change marks the draft dirty.
+4. Validation exposes exact actionable problems and controls executable
+ actions.
+5. Save serializes and updates an existing authoritative draft.
+6. For a committed-only source (`draftExists=false`), first Save creates a new
+ draft instead of calling draft update. It follows the shared create
+ observation contract and replaces the route with the returned readable
+ draft ID, which may differ from the committed source ID.
+7. Save success clears dirty state only after the real update succeeds or the
+ newly created draft becomes readable.
+8. Leaving while dirty prompts Save, Discard, or Stay.
+
+**Publish boundary:** The Excalidraw retains a Publish control because the
+existing Studio supports publication/binding behavior. Publication must use
+the reviewed scope Workflow/save-and-bind contracts and explicit capability
+confirmations. It is not a prerequisite for draft Run, and it must not invent a
+`publishedServiceId` after save acceptance.
+
+**Completion:** The saved document can be reloaded from the API with the
+expected draft identity and content. For a committed-only source, that identity
+is the create response's materialized draft ID, not an assumed reuse of the
+committed Workflow ID.
+
+**Identity guard:** This path never creates a fake Team/member, never sends
+`workflowId` to a member endpoint, and never assumes publication makes the
+Workflow callable through the same ID.
+
+## UP-07: Run A Current Draft
+
+**Intent:** Execute the current valid Workflow without leaving the editor.
+
+**Frames:** 07 Unified Run dialog and 08 Running draft.
+
+**Authoritative execution:**
+
+```text
+POST /api/scopes/:scopeId/workflow/draft-run
+Accept: text/event-stream
+```
+
+**Steps:**
+
+1. The user selects `Run` from the catalogue or editor.
+2. The unified dialog identifies the exact Workflow and says `Current draft`
+ when no real revision number exists.
+3. The user reviews input, connections, and verified external effects.
+4. Required explicit capability confirmations must be derived from the real
+ preview/validation contract.
+5. Submit serializes the current valid document and starts
+ `runtimeRunsApi.streamDraftRun`.
+6. The button becomes stable and duplicate submission is blocked.
+7. Real stream/receipt facts move the UI into Accepted or Running.
+8. The editor remains visible and the Run console opens without replacing the
+ current document.
+
+**Completion:** A real stream or command receipt proves execution acceptance.
+It does not yet prove an Activity row exists.
+
+**Recovery:** Submission failure keeps the input and returns to an actionable
+dialog. A disconnected stream shows the last authoritative receipt/event and a
+reconnect or Activity action; it does not fabricate completion.
+
+## UP-08: Observe A Run In Activity
+
+**Intent:** Move from live execution to an immutable observed record.
+
+**Frames:** 08 Running draft, 09 filtered Activity, 10 all retained Runs, and
+13 states.
+
+```mermaid
+%%{init: {"maxTextSize": 100000, "flowchart": {"useMaxWidth": false, "nodeSpacing": 10, "rankSpacing": 50}, "themeVariables": {"fontSize": "10px"}}}%%
+flowchart LR
+ A["Run submitted"] --> B["Real stream or receipt accepted"]
+ B --> C["Live console running"]
+ B --> D["Query scoped Activity"]
+ D --> E{"Matching authoritative Run observed?"}
+ E -->|"No"| F["Observation delayed"]
+ F --> D
+ E -->|"Yes"| G["Observed with runId and stateVersion"]
+ G --> H["Enable immutable Run detail link"]
+```
+
+**Authoritative read:**
+
+```text
+GET /api/workflow/observatory/runs
+```
+
+with supported `scope`, `status`, `origin`, `definition`, `schedule`, `from`,
+`to`, and `take` parameters.
+
+**Steps:**
+
+1. Acceptance invalidates or polls the correctly keyed scope Activity query
+ with bounded backoff.
+2. While no matching authoritative record exists, the UI says `Observing` or
+ `Activity delayed`.
+3. When observatory returns the Run, its `runId` and `stateVersion` establish
+ the immutable Activity identity.
+4. Only then does the UI say `Observed in Activity` and enable the detail link.
+5. If the execution response exposes no trustworthy match key, the UI offers
+ general `Open Activity` rather than guessing a row.
+
+**Completion:** An actual observatory summary is visible in Activity.
+
+**Not allowed:** A timer, local array, `localStorage`, stream completion, or
+HTTP success status cannot insert an Activity row or invent a `stateVersion`.
+
+## UP-09: Filter Activity By Workflow
+
+**Intent:** Review observed executions belonging to one Workflow definition.
+
+**Start:** The user invokes `View Activity` from a Workflow context.
+
+**Steps:**
+
+1. The catalogue action encodes that row's exact draft `workflowId` in the
+ Activity route query string without substituting a member, service, display
+ name, or actor identity.
+2. Activity keeps the Workflow filter visible and removable, and resolves the
+ scope Workflow detail through
+ `GET /api/scopes/:scopeId/workflows/:workflowId`.
+3. Only `ScopeWorkflowDetail.source.definitionActorId` supplies the
+ observatory `definition` filter.
+4. Refresh, copied URLs, back, and forward restore the same `workflowId` and
+ repeat the same authoritative resolution.
+5. The ledger shows only the server-filtered response window. Removing the
+ Workflow filter removes it from the URL and returns to global Activity.
+
+**Completion:** The active `workflowId` filter is URL-backed, visible,
+removable, and backed by the exact resolved definition identity.
+
+**Recovery:** A missing or invalid `workflowId`, failed Workflow-detail read,
+or draft-only Workflow without a definition actor produces an honest
+invalid, error, or unavailable state. The Runs query remains disabled so the
+page never silently shows global Activity. The UI does not substitute Workflow
+name, `memberId`, `publishedServiceId`, `serviceKey`, or a parsed actor prefix.
+
+## UP-10: Inspect Immutable Run Detail
+
+**Intent:** Understand what happened in one observed Run.
+
+**Route:**
+`/scopes/:scopeId/workflow-activity-vnext/activity/:runId`.
+
+**Authoritative reads:**
+
+```text
+GET /api/workflow/observatory/runs/:runId
+GET /api/workflow/observatory/runs/:runId/graph
+```
+
+**Steps:**
+
+1. Selecting an Activity row opens its real `runId`.
+2. Initial entry may use the complete Run-detail skeleton until committed
+ history is available. After that, selecting another Run in the history rail
+ keeps the authoritative rail mounted, preserves its scroll position, and
+ immediately marks the target Run as selected.
+3. Only the selected Run's detail and graph region enters loading during that
+ selection. The committed history rail remains visible and usable because
+ its query is unchanged. Detail and graph continue to load independently.
+4. Manual Refresh is a separate grouped operation: because it refreshes
+ detail, graph, and history together, it may block and mark the complete
+ workspace busy while preserving committed content beneath its overlay.
+5. The page prioritizes status, identity, final output/error, diagnostics, and
+ step trace.
+6. The user may expand input, request parameters, full timeline content, tool
+ calls, statistics, and usage.
+7. Copy/export actions operate only on returned, permitted data.
+
+**Completion:** The user can identify success/failure and the relevant step
+without altering the record.
+
+**Recovery:** A graph failure leaves the committed detail visible. A missing
+or cross-scope Run renders the server-safe not-found state. Partial/running
+detail is labeled partial and refreshed through bounded observation.
+
+**Not allowed:** No edit, overwrite, delete, local usage calculation, or fake
+source/child Run timeline appears on the source record.
+
+## UP-11: Retry A Failed Run
+
+**Intent:** Continue from an unambiguous failed step while preserving the
+failed Run as evidence.
+
+**Frame:** 12 Failed Run recovery.
+
+**Authoritative command:**
+
+```text
+POST /api/workflow/runs/fork
+```
+
+**Steps:**
+
+1. Run detail identifies a failed step from the returned step/diagnostic data.
+2. `Retry` is enabled only when one explicit `stepId` is a valid recovery
+ start.
+3. The confirmation dialog shows source Run ID, start step, input, and
+ supported overrides.
+4. Confirm submits `sourceRunId` and `startAtStepId`.
+5. A real `202 Accepted` response shows the accepted command ID, correlation
+ ID, new Run actor ID, and status URL as receipt facts.
+6. The source Run remains on screen and unchanged.
+7. The new execution follows UP-08 until an authoritative Activity `runId` is
+ observed.
+
+**Completion:** The user can open the newly observed Run; the failed source
+still shows its original status and trace.
+
+**Recovery:** If no exact failed step exists, Retry stays disabled with a
+reason. A failed fork request preserves the confirmation input. The frontend
+does not use `newRunActorId` as a Run-detail route unless an authoritative
+contract establishes the corresponding `runId`.
+
+## UP-12: Run A Completed Workflow Again
+
+**Intent:** Create a new execution from the beginning of an earlier successful
+Run.
+
+**Steps:**
+
+1. Detail/graph data must identify the first executable step unambiguously.
+2. `Run again` opens the same immutable-source confirmation model as Retry.
+3. The user reviews the source Run, first step, input, and supported overrides.
+4. Fork submission returns a real accepted receipt.
+5. The new execution follows UP-08; the original success record remains
+ unchanged.
+
+**Completion:** A new authoritative Activity Run is observed.
+
+**Recovery:** If the first execution step is missing or ambiguous, Run again
+stays disabled. The UI does not pick the first array item, parse a graph node
+ID, or pretend a new linked record exists before observation.
+
+## UP-13: Change AI Defaults
+
+**Intent:** Choose the user's preferred connected AI service and default model.
+
+**Frames:** 14 AI defaults and 15 save/recovery states.
+
+**Authoritative operations:**
+
+```text
+GET /api/user-config/llm
+PUT /api/user-config/llm
+```
+
+**Steps:**
+
+1. Settings opens AI defaults and loads the current real selection and
+ available connected-service/model catalogue through existing adapters.
+2. Changing Preferred service updates valid model choices without saving.
+3. Changing either value makes the page dirty and reveals the shell-fixed
+ Restore and Save changes dock without remounting the edited form control.
+4. Discard restores the last authoritative values.
+5. Save sends the selected intent and receives an accepted receipt.
+6. The UI says `Confirming saved values`, not Saved.
+7. Existing save-observation logic requeries settings until the selected
+ service/model is authoritative.
+8. Only observation clears dirty/confirming state and shows saved success.
+
+**Completion:** A fresh GET returns the chosen values.
+
+**Recovery:** Save failure preserves dirty values. Catalogue unavailable keeps
+the last real selection visible and disables invalid changes. Fallback is
+explained rather than silently saved. `localStorage` cannot be the settings
+authority.
+
+## UP-14: Inspect Account And Session
+
+**Intent:** Understand who is signed in and manage the existing session or
+service access.
+
+**Frame:** 16 Account.
+
+**Authoritative identity:**
+
+```text
+GET /api/auth/me
+```
+
+**Steps:**
+
+1. The user opens Account from local Settings navigation.
+2. The page shows returned identity, user ID, roles, groups, provider, scope,
+ and expiry only when present.
+3. Sign in, Sign out, and Manage service access reuse current application auth
+ actions.
+4. Any navigation/confirmation follows those existing auth contracts.
+
+**Completion:** The UI reflects the current real session state.
+
+**Recovery:** Expired or unavailable auth is shown explicitly. The page never
+uses the prototype's `signed-in` browser flag, invents claims, or renders bearer
+or credential material.
+
+## UP-15: Inspect Advanced Runtime Values
+
+**Intent:** Diagnose the effective runtime/request context without editing it.
+
+**Frame:** 17 Advanced and responsive.
+
+**Authoritative read:**
+
+```text
+GET /api/user-config/runtime
+```
+
+**Steps:**
+
+1. The user opens Advanced.
+2. One read-only representation shows the effective runtime mode and request
+ values returned by the API.
+3. Permitted non-secret values may be copied.
+
+**Completion:** The user can inspect the active real configuration without a
+save action.
+
+**Recovery:** An unavailable runtime query shows retry. It does not substitute
+build-time defaults and label them as the user's effective server state.
+
+## UP-16: Complete Key Jobs On Tablet And Mobile
+
+**Intent:** Preserve the same product semantics on constrained screens.
+
+**Frames:** 13 responsive Workflows/Activity and 17 responsive Settings, plus
+the mobile editor references in the baseline directory.
+
+**Required mobile paths:**
+
+- Workflows: search, New workflow, Open, and eligible Run remain reachable.
+- Creation: all four methods remain usable; YAML and validation errors remain
+ associated with their inputs.
+- Editor: Run, Add node, Edit YAML, Save, and Publish availability remain
+ visible. Node configuration uses an accessible bottom sheet.
+- Activity: status, Workflow, short Run ID, time, filters, and detail access
+ remain available without desktop-only hover.
+- Run detail: failure, output, step trace, Retry, and Run again eligibility
+ remain accessible.
+- Settings: section navigation, shell-fixed dirty save dock, save observation, Account
+ actions, and Advanced values remain operable.
+- Login and callback: the existing NyxID flow remains usable at mobile width,
+ and the existing language switch remains reachable without desktop chrome.
+
+**Completion:** Mobile and desktop reach the same authoritative end states.
+Responsive layout changes presentation, never API source, identity, recovery,
+or completion evidence.
+
+## Cross-Path Truth Checkpoints
+
+| Checkpoint | The UI may say | Required evidence | The UI must not say |
+| --- | --- | --- | --- |
+| Workflow generated | Generated preview ready | Real generator response | Saved |
+| Workflow create accepted | Accepted/Observing | Real `202` receipt with `workflowId` and `projection_pending` readiness | Draft readable or editor ready |
+| Workflow created | Draft created | Materialized create response or exact scoped draft GET succeeds | Created from a local ID or receipt alone |
+| Workflow saved | Saved | Real draft update success and clean document | Saved after timer-only feedback |
+| Run submitted | Submitting | User action in progress | Running |
+| Run accepted | Accepted/Running | Real SSE event or accepted receipt | Observed in Activity |
+| Activity observed | Observed | Observatory summary with `runId` and `stateVersion` | Observed from local insertion |
+| Retry accepted | New Run accepted | Real fork receipt | Linked history persisted |
+| New Run observed | New Run available | Observatory returns authoritative `runId` | Detail route from guessed actor ID |
+| Settings accepted | Confirming saved values | Real PUT receipt | Saved |
+| Settings saved | Saved | Real GET observes selected values | Saved from `localStorage` |
+| Empty collection | No records | Successful API response with zero records | Empty after request failure |
+
+## Failure And Recovery Paths
+
+| Failure | Preserve | Show | Recovery |
+| --- | --- | --- | --- |
+| Workflow catalogue request fails | Successful source rows and active search | Failed source or page error | Retry failed query |
+| Generator fails | User description | Generator error | Retry generation |
+| YAML parse fails | YAML input | Exact parse feedback | Edit and parse again |
+| Draft create fails | Creation inputs/preview | Create error | Retry create |
+| Draft projection is delayed | Accepted receipt, returned ID, and creation inputs | Accepted/Observing or delayed readiness | Retry the exact draft GET; do not repeat create |
+| Draft save fails | Dirty document | Save error | Retry or continue editing |
+| Run submission fails | Input and document | Submission error | Retry explicit Run |
+| Run stream disconnects | Last real receipt/event | Disconnected or unknown status | Reconnect or open Activity |
+| Activity observation is delayed | Accepted receipt | Delayed projection state | Bounded retry/open Activity |
+| Activity list fails | Current filters | Request error, not empty | Retry |
+| Run graph fails | Run detail | Graph-only error | Retry graph |
+| Retry/Run again fails | Immutable source and overrides | Fork error | Retry command |
+| Settings catalogue fails | Last real selection | Catalogue unavailable | Retry catalogue |
+| Settings save fails | Dirty selection | Save error | Retry or discard |
+| Auth expires | Non-secret local editing state when safe | Session expired | Existing sign-in flow |
+| Login/callback fails | Sanitized return target and safe error reason | Existing callback recovery | Retry existing NyxID flow/back to Login |
+| Locale catalogue/key is missing | Current locale selection | Explicit development/test failure | Fix both catalogues; never use vNext-local fallback copy |
+
+## Excalidraw Coverage
+
+Every frame has a corresponding user path:
+
+| Frame | Covered by |
+| --- | --- |
+| 01 Workflows catalogue | UP-00, UP-01 |
+| 02 New workflow direct creation | UP-02, UP-03, UP-04, UP-05 |
+| 03 Describe generated draft | UP-02, UP-06 |
+| 04 Blank draft | UP-03, UP-06 |
+| 05 Import YAML draft | UP-04, UP-06 |
+| 06 Template draft | UP-05, UP-06 |
+| 07 Unified Run dialog | UP-07 |
+| 08 Running draft and Run console | UP-07, UP-08 |
+| 09 Activity filtered by Workflow | UP-09 |
+| 10 All retained Runs | UP-08 |
+| 11 Immutable Run detail | UP-10, UP-12 |
+| 12 Failed Run recovery | UP-11, UP-12 |
+| 13 Workflows and Activity states | UP-00, UP-01, UP-08, UP-16 |
+| 14 Settings AI defaults | UP-13 |
+| 15 Settings save/recovery | UP-13 |
+| 16 Settings Account | UP-14 |
+| 17 Settings Advanced/responsive | UP-15, UP-16 |
+
+## User-Path Acceptance Criteria
+
+- Every `UP-*` path can be traced from its start route to its completion
+ evidence without leaving the new namespace, except existing authentication
+ and service-access actions.
+- UP-00 uses the existing protected-route guard, Login, NyxID callback,
+ sanitized `returnTo`, session restoration, and locale behavior; only their
+ presentation may change.
+- Login/callback styling remains compatible with all existing protected routes,
+ not only the vNext namespace.
+- Every vNext message ID exists in both `en-US` and `zh-CN`, and language
+ switching uses the existing `ConsoleLanguageSwitch`/Umi locale state.
+- All four creation methods converge on a real persisted draft and the same
+ connected-node editor.
+- A real returned `workflowId` and a successful materialized response or exact
+ scoped draft GET are required before editor navigation; a `202` receipt alone
+ is not readability.
+- A create-observation timeout retries the same draft ID and never silently
+ resubmits draft creation.
+- Opening a committed-only Workflow preserves `draftExists=false`; first Save
+ creates and observes a new draft, then adopts its returned ID.
+- Draft Run reaches Accepted/Running before it can reach Activity observation.
+- Activity observation requires a real observatory `runId` and `stateVersion`.
+- Workflow-specific Activity preserves the catalogue row's exact `workflowId`
+ in the URL and uses only the exact returned `definitionActorId` for the Runs
+ request; invalid resolution never falls back to global Activity.
+- Run detail remains immutable in success, failure, partial graph, Retry, and
+ Run again paths.
+- Retry and Run again require explicit step identity and create a new accepted
+ execution.
+- Settings Save remains confirming until authoritative readback observes the
+ selected values.
+- API pending, empty, delayed, unavailable, decoding failure, and request
+ failure remain distinct in every path.
+- No production path imports mock fixtures, inserts demonstration rows, reads
+ prototype Activity/settings from browser storage, or uses timers to simulate
+ authoritative API progress.
+- Desktop, tablet, and mobile use the same identities, APIs, and completion
+ evidence.
+- Home aliases resolve through `/workflows`; explicit safe deep links and scoped
+ Team/member routes retain their destination. Backend files and contracts
+ remain unchanged.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-05-workflow-list-filter-semantics-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-05-workflow-list-filter-semantics-design.md
new file mode 100644
index 0000000000..80b46a64cc
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-05-workflow-list-filter-semantics-design.md
@@ -0,0 +1,135 @@
+# Workflow List Filter Semantics Design
+
+## Status
+
+Approved in conversation on 2026-08-05. This document records the product
+semantics for aligning the Workflow and Activity list toolbars before the
+frontend implementation changes.
+
+This design supplements, but does not modify, the Workflow Activity vNext
+design baseline. Real backend responses remain authoritative over prototype
+labels and demonstration data.
+
+## Mismatch
+
+The UI currently makes the two list pages appear inconsistent: Activity has
+categorical filters while Workflows exposes only search and refresh. Copying
+Activity's filters into Workflows would still be wrong because the pages own
+different resources and facts.
+
+- Activity lists Runs. Its status and source are authoritative Run API fields.
+- Workflows merges the scoped Workflow list with the scoped draft list.
+ `draft` and `committed` describe frontend merge inputs, not two mutually
+ exclusive product lifecycle states.
+- A Workflow can have both a committed source and a draft. The frontend must
+ not present `Committed` as the opposite of `Draft`.
+- The current backend does not provide the published identity, last-Run
+ outcome, or other facts required to implement the prototype's `Published`
+ and `Failing` Workflow filters honestly.
+
+The semantic owner of Workflow filtering is therefore the Workflow catalogue,
+using only facts returned by its existing scoped Workflow and draft APIs. Run
+status and source remain owned by Activity.
+
+## Decision
+
+Both pages use the same toolbar grammar:
+
+- search on the left;
+- resource-specific categorical filters on the right;
+- refresh in the page header rather than mixed into the filter group;
+- active search and filter values represented in the URL;
+- an honest filtered-empty state with a clear-filters action.
+
+Activity keeps its existing Run status and Run source filters.
+
+Workflows adds one select with two options:
+
+| Option | Product meaning | Evidence |
+| --- | --- | --- |
+| `All workflows` | Every row produced by the existing merge of scoped Workflows and scoped drafts | Successful results from the two existing list APIs |
+| `Drafts` | Workflows whose exact `workflowId` is present in `studioApi.listWorkflowDrafts(scopeId)` | A real draft summary returned by that API |
+
+The Workflows filter must not include `Committed`, `Published`, `Failing`,
+`Ready`, or similar values until a real contract supplies an unambiguous,
+user-facing fact for that option.
+
+## Data And URL Behavior
+
+The existing Workflow merge remains authoritative and unchanged:
+
+1. Load the scoped committed Workflow summaries.
+2. Load the scoped draft summaries.
+3. Merge by exact `workflowId` without inferring identity from names, prefixes,
+ route position, member IDs, or service IDs.
+4. When `view=drafts`, retain only merged rows whose exact `workflowId` was
+ returned by the draft API.
+5. Apply the text search to the selected view and keep the existing updated-at
+ ordering.
+
+The Workflows URL uses:
+
+- `q=` for non-empty search text;
+- `view=drafts` for the Drafts view;
+- no `view` parameter for All workflows.
+
+Activity adds its non-empty search text as `q=` while preserving its
+existing `status`, `origin`, `definition`, and `workflowFilter` parameters.
+Empty values are omitted. Reloading, browser navigation, or returning to a
+copied URL restores the same visible search and filters.
+
+## Loading, Failure, And Empty States
+
+- While either Workflow source is loading, retain the existing loading state.
+- If the draft source fails, the Drafts option is disabled because the
+ frontend cannot know which Workflows have drafts.
+- A draft-source failure does not hide successfully returned scoped Workflow
+ rows in All workflows.
+- If the user was already viewing Drafts when the draft source fails, show an
+ unavailable state with Retry rather than an empty result.
+- A successful filtered result with zero rows shows `No matching workflows`
+ and a `Clear filters` action.
+- Clearing filters removes `q` and `view` while preserving the scoped route.
+- No mock rows, localStorage state, timer results, or successful fallback may
+ stand in for either API.
+
+## Presentation And Accessibility
+
+- Workflows and Activity use the same toolbar spacing, search width, select
+ height, responsive wrapping, and mobile full-width controls.
+- Selects retain visible accessible names through existing localized labels.
+- The Drafts option is a normal menu option, not a status badge or lifecycle
+ claim.
+- Keyboard, touch, and screen-reader behavior continue through Ant Design's
+ existing Input and Select controls.
+- All new copy is added to both the `en-US` and `zh-CN` catalogues.
+
+## Test Contract
+
+Focused tests must prove that:
+
+- Workflows filters Drafts by exact draft API membership while All workflows
+ still contains committed-only and draft-backed rows.
+- Workflows never renders unsupported `Committed`, `Published`, or `Failing`
+ filter options.
+- Workflows search and view values restore from and write to the URL.
+- Activity search joins its existing URL-backed filters without dropping
+ `status`, `origin`, `definition`, or `workflowFilter`.
+- A failed draft source disables or makes the Drafts view unavailable instead
+ of presenting a false empty state.
+- Clearing filters restores the unfiltered list and scoped URL.
+- Both locale catalogues contain every new message key.
+
+Browser verification uses the running frontend with the real remote backend at
+desktop, tablet, and mobile widths. If a remote endpoint is unavailable, the
+unavailable state is recorded as a verification gap; test fixtures are not
+used to fabricate browser evidence.
+
+## Non-Goals
+
+- No backend endpoint, DTO, projection, identity, or persistence change.
+- No change to existing Workflow, Run, Settings, Studio, Team, member, login,
+ callback, redirect, session, menu, or locale route behavior.
+- No attempt to implement prototype-only Published or Failing filters.
+- No identity conversion among `workflowId`, `memberId`,
+ `definitionActorId`, or `publishedServiceId`.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-add-node-sequential-link-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-add-node-sequential-link-design.md
new file mode 100644
index 0000000000..17d54b7d33
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-add-node-sequential-link-design.md
@@ -0,0 +1,136 @@
+# Add Node Sequential Link Design
+
+## Status
+
+Approved for implementation on 2026-08-06.
+
+Implementation branch: `fix/2026-08-06_add-node-sequential-link`.
+
+Base branch: `feat/2026-08-04_workflow-activity-vnext` at
+`121f0132f1191206aaffdaf71d189840c41d7c5e`.
+
+## Problem
+
+Workflow Activity vNext appends a selected node type by calling
+`insertStepByType` without an insertion source. The helper therefore adds the
+new step to the document array without updating a predecessor's `next` field.
+The canvas renders only explicit `next` and `branches` edges, while the runtime
+still permits adjacent steps to execute through implicit sequential ordering.
+
+After insertion, the serialize endpoint validates the complete document and
+returns `implicit_next` warnings for every non-terminal step that still relies
+on array order. This makes the Add node action appear to have introduced an
+error and leaves the newly added node visually disconnected.
+
+## Product Contract
+
+`Add node` extends the current executable flow; it does not create an
+unconnected step by default.
+
+- When a step is selected, insert the new step immediately after it.
+- When no step is selected, append the new step after the final document step.
+- If the selected step already has an explicit linear successor, preserve that
+ successor as the inserted step's `next` target.
+- Before insertion, materialize existing implicit sequential transitions that
+ are already defined by adjacent document order: a non-final step with no
+ `next` and no branches points to the following step.
+- Do not synthesize a linear transition for a step that owns branches.
+- Preserve every existing explicit `next` and branch target.
+- Keep a truly empty workflow behavior unchanged: its first node has no
+ predecessor and remains the terminal step.
+- The inserted node becomes selected so its configuration surface opens as it
+ does today.
+
+## State Transition
+
+Given this implicit document:
+
+```text
+create_weekly_report, transform_step
+```
+
+adding `assign_step` without a selected node produces:
+
+```text
+create_weekly_report.next = transform_step
+transform_step.next = assign_step
+assign_step.next = null
+```
+
+Given an explicit chain and a selected middle step:
+
+```text
+draft.next = review
+review.next = publish
+```
+
+adding `assign_step` after `review` produces:
+
+```text
+draft.next = review
+review.next = assign_step
+assign_step.next = publish
+```
+
+## Implementation Boundary
+
+Keep the change frontend-only.
+
+1. Add a pure document helper that materializes only runtime-defined implicit
+ sequential transitions without altering explicit or branched transitions.
+2. Update the vNext editor insertion path to apply that helper and pass the
+ selected step ID, or the final step ID when no step is selected, to
+ `insertStepByType`.
+3. Keep serialization as the server-authoritative validation boundary. Do not
+ filter or suppress returned findings.
+4. Do not change backend validation, runtime semantics, graph rendering, or
+ the Team Member Workflow Studio behavior in this fix.
+
+## Failure And Concurrency
+
+The existing structural-mutation generation guard remains authoritative.
+Materialization and insertion happen in memory before the single serialize
+request. A failed or stale serialize response does not update the current
+document. Retry repeats the same deterministic insertion intent against the
+still-current document.
+
+## Verification
+
+Add focused coverage for:
+
+- materializing adjacent implicit transitions while preserving explicit and
+ branched transitions;
+- adding a node with no selected step after the final step and sending a fully
+ explicit linear chain to serialization;
+- inserting after a selected middle step while preserving its former
+ successor;
+- keeping first-node insertion valid for an empty document.
+
+Run only the changed test files and changed-file static checks. The complete
+frontend suite, typecheck, and production build remain delegated to GitHub CI
+under the personal incremental frontend policy.
+
+## Design Baseline
+
+```text
+Design baseline:
+ apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/
+Primary design:
+ aevatar-workflow-activity-vnext.excalidraw
+Design SHA-256:
+ 30e74d7b410ae72c4c91432355436679033679c54c10b1702908435b001577de
+Contract specification:
+ apps/aevatar-console-web/docs/superpowers/specs/
+ 2026-08-04-workflow-activity-vnext-design.md
+User paths:
+ apps/aevatar-console-web/docs/superpowers/specs/
+ 2026-08-04-workflow-activity-vnext-user-paths.md
+Authentication and localization:
+ Existing Aevatar login, callback, session, returnTo, and Umi locale logic;
+ presentation may change, behavior may not.
+Production data source:
+ Real APIs and API-acknowledged user actions only; no mock fallback.
+Baseline integrity:
+ python3 apps/aevatar-console-web/docs/design-baselines/
+ workflow-activity-vnext/verify-baseline.py
+```
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-new-workflow-creation-ux-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-new-workflow-creation-ux-design.md
new file mode 100644
index 0000000000..ad5c29e2f6
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-new-workflow-creation-ux-design.md
@@ -0,0 +1,273 @@
+# New Workflow Creation UX Design
+
+## Status
+
+Approved direction on 2026-08-06 for the Workflow Activity vNext frontend,
+amended on 2026-08-10 to merge blank creation into Describe.
+This document narrows the existing direct-creation contract without changing
+its backend APIs, identity rules, or materialization semantics.
+
+Implementation branch: `fix/2026-08-06_restore-describe-workflow-name`.
+
+## Problem
+
+The current creation route exposes backend requirements as if they were user
+decisions. `Save location` is always prominent even when the workspace offers
+only one directory, and `Automation goal` does not explain that the text is
+used to generate the workflow. The selected method is then rendered in a
+full-width bordered panel whose fields stretch across the complete work area.
+This loses the compact hierarchy defined by the Workflow Activity vNext
+baseline and makes a short creation task look like an empty settings form.
+
+Describe also requires two separate decisions after input: generate a YAML
+preview and then create it. The raw generated YAML is implementation detail at
+this stage. Users asked for one action that generates, validates, saves, and
+opens the common editor.
+
+Describe and Start blank are also presented as peer creation methods even
+though blank creation is the same named draft flow with no generation prompt.
+Making users choose between them before seeing the form adds a decision that
+the entered data already resolves.
+
+## Product Decisions
+
+### Save Target
+
+The workspace directory remains part of the typed draft-create request because
+the backend requires `directoryId`. It is not shown when there is exactly one
+available directory. The route automatically selects that directory and uses
+it for every creation mode.
+
+If the workspace returns more than one directory, the selected method surface
+shows one compact `Save to` selector below the method-specific input. This is
+the only state in which save location is a real user decision. Loading, zero
+directory, authorization, and request-failure states retain user input and
+provide the existing recovery actions; they never invent a directory.
+
+The backend also requires a unique YAML path inside the chosen directory even
+though duplicate Workflow display names are allowed. The frontend derives a
+file name from the resolved Workflow name and the authoritative draft list,
+using the first available suffix (`weekly-review.yaml`, then
+`weekly-review-2.yaml`, and so on). This filename is not a creation-page field.
+A server conflict caused by a concurrent create remains a visible retryable
+error and never overwrites the existing draft.
+
+### Describe
+
+Describe is a focused form with one required input, one optional input, and one
+final action:
+
+- heading: `Describe your workflow`;
+- field label: `Workflow name`;
+- optional field label: `What should this workflow do?`;
+- supporting copy explains that the generated steps can be reviewed in the
+ editor;
+- one primary action: `Generate and open`.
+
+The action label remains `Generate and open`; it does not change while the user
+types. Submission trims the description and chooses one of two existing paths:
+
+- a non-empty description calls the real generator, parses and validates the
+ returned YAML, then persists it;
+- an empty or whitespace-only description creates and persists the existing
+ minimal blank Workflow YAML without calling the generator.
+
+Both paths use the user-provided Workflow name for the draft display name and
+filename, wait for the exact draft to become readable when creation is
+accepted, and open the editor. The generated YAML document name remains an
+independent document field. The page does not expose generated YAML or require
+a second create confirmation. Generator, parse, validation, create, and
+materialization failures preserve both inputs and identify the failed stage in
+product terms.
+
+### Other Creation Methods
+
+All methods follow the same rule: show only the input the user can meaningfully
+provide, then use one final action.
+
+| Method | Visible input | Name source | Primary action |
+| --- | --- | --- | --- |
+| Describe | Workflow name and optional natural-language workflow outcome | User input | Generate and open |
+| Import YAML | Workflow YAML | Parsed YAML document | Import and open |
+| Use template | Bundled template selection and concise preview | Template name, made independent for the new draft | Use template and open |
+
+The editor remains the only surface for reviewing generated/imported YAML,
+renaming the created workflow, editing nodes, and resolving document-level
+details. Creation does not add a second editor.
+
+## Approaches Considered
+
+### Focused Prompt
+
+Show only task-specific inputs and one final action. Describe asks for the
+user-controlled display name and accepts an optional generation prompt; the
+prompt's presence selects generated or blank creation. Import and Template
+derive their names from their respective sources. This is the selected
+approach because it removes a redundant method choice while keeping the blank
+authoring path available.
+
+### Separate Describe And Blank Methods
+
+Keep the two existing chooser cards and their separate actions. This is
+rejected because the second method is already represented by submitting the
+Describe form without a description, so the chooser asks users to predict a
+distinction the form can determine directly.
+
+### Derived Name For Describe
+
+Derive the draft display name from generated YAML and ask only for the outcome.
+This is rejected because the generated document name is not a substitute for
+the user's intended Workflow display name. Describe therefore requires an
+explicit name before generation.
+
+### Guided Wizard
+
+Add examples and a visible two-step progress indicator. This can help first-
+time users, but it adds visual weight and falsely suggests that creation and
+editor review are one wizard-owned transaction. It is rejected for the normal
+flow.
+
+## Layout And Visual Direction
+
+The selected-method surface is a centered, unframed creation column instead
+of a full-width bordered panel. Desktop width is bounded for comfortable form
+reading; mobile uses the available width. The title, short supporting line,
+input, inline validation, optional multi-directory selector, and action row
+form one continuous vertical hierarchy.
+
+The page continues the Operational Automation Ledger direction:
+
+- existing dark navigation rail and white work surface;
+- AlibabaSans product typography;
+- neutral dividers, restrained blue primary action, and 4-6 px radii;
+- no nested cards, gradient, decorative shadow, or oversized form controls;
+- stable button dimensions and visible keyboard focus;
+- long validation and technical details wrap without widening the viewport.
+
+The method chooser retains three clearly distinct starting methods. Selecting
+one changes only the main creation column. `Change method` is a low-emphasis
+back action. User input survives method switches where the value remains
+meaningful, while stale failure and validation output from another method is
+cleared.
+
+## Data Flow
+
+```mermaid
+%%{init: {"maxTextSize": 100000, "flowchart": {"useMaxWidth": false, "nodeSpacing": 10, "rankSpacing": 50}, "themeVariables": {"fontSize": "10px"}}}%%
+flowchart LR
+ A["Choose creation method"] --> B["Enter method-specific input"]
+ B --> C["Resolve the real workspace directory"]
+ C --> K{"Describe entered?"}
+ K -->|"Yes"| D["Generate, parse, and validate"]
+ K -->|"No"| L["Build minimal blank YAML"]
+ K -->|"Import or template"| D
+ D --> E["Resolve a non-conflicting YAML file name"]
+ L --> E
+ E --> J["Create the scoped workflow draft"]
+ J --> F{"Draft readable?"}
+ F -->|"Yes"| G["Open the common workflow editor"]
+ F -->|"Accepted"| H["Observe the exact returned workflow ID"]
+ H --> G
+ H -->|"Delayed"| I["Preserve input and retry observation only"]
+```
+
+Describe conditionally executes either generation plus parse/validation or
+minimal blank-YAML creation behind one user action, followed by the same draft
+creation and opening flow. These remain distinct technical stages so recovery
+can be truthful. The action never treats generated content as saved, never
+routes from a locally invented workflow ID, and never repeats the create POST
+when only materialization is delayed.
+
+## State And Recovery Contract
+
+- While workspace settings load, creation inputs remain editable and the final
+ action remains unavailable until a real directory is resolved.
+- With zero directories, show the existing access/retry path and preserve all
+ entered input.
+- Generator failure keeps the description and enables retry of generation;
+ blank creation never calls the generator.
+- Parse or validation failure keeps the input and shows actionable findings;
+ no draft is created.
+- Draft-create failure keeps the method input and retries only creation after
+ another explicit click.
+- Accepted creation shows `Creating workflow...`; delayed materialization
+ preserves the receipt and retries only the exact scoped GET.
+- Duplicate submission is blocked across the complete one-click operation.
+- Changing methods clears stale errors and findings but does not erase the
+ previously typed Describe name or prompt.
+
+## Accessibility And Responsive Behavior
+
+- Labels are programmatically associated with their controls.
+- Pending status is announced without repeatedly announcing observation
+ polling.
+- The primary action exposes a stable loading state and cannot be triggered
+ twice by mouse or keyboard.
+- `Change method` and `Back to workflows` remain separate navigation actions.
+- At widths below 768 px, inputs and actions stack, the action remains visible
+ after its associated validation content, and no sticky element covers a
+ focused textarea.
+- The three method options collapse to a single column or compact multi-column
+ arrangement without truncating labels or descriptions.
+
+## Focused Test Contract
+
+The owning route integration test will protect these observable risks:
+
+1. A single returned directory is not rendered, while Describe uses it in the
+ create request.
+2. Multiple returned directories render `Save to` and the selected value is
+ used in the create request.
+3. Describe requires `Workflow name`, keeps `What should this workflow do?`
+ optional, and exposes one fixed `Generate and open` action. With a
+ description it calls generator, parser, draft create, and opens the
+ materialized workflow using the user-provided display name.
+4. The same Describe action with an empty or whitespace-only description skips
+ generator and parser calls, persists the minimal blank Workflow YAML, and
+ opens the materialized workflow.
+5. File-name resolution chooses the first available suffix within the selected
+ directory without changing the Workflow display name.
+6. Import derives the name from parsed YAML and creates without a separate
+ workflow-name field.
+7. Generator or create failure preserves the Workflow name and prompt;
+ switching methods clears stale failure output without discarding shared
+ name input.
+8. Accepted draft creation preserves the existing observe-and-retry contract
+ and never resubmits create during readiness retry.
+
+Tests assert accessible rendered behavior and typed boundary requests. They do
+not assert CSS class names or implementation callback order beyond the real
+external operation sequence that distinguishes generation from persistence.
+
+## Scope
+
+The change is frontend-only and restricted to Workflow Activity vNext creation
+page code, its focused tests, creation-mode type, and this specification.
+It does not change backend endpoints, shared workflow identity semantics,
+legacy Workflow/Studio routes, the common editor, global navigation, or the
+authoritative 17-frame baseline assets.
+
+## Design Baseline
+
+```text
+Design baseline:
+ apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/
+Primary design:
+ aevatar-workflow-activity-vnext.excalidraw
+Design SHA-256:
+ 30e74d7b410ae72c4c91432355436679033679c54c10b1702908435b001577de
+Contract specification:
+ apps/aevatar-console-web/docs/superpowers/specs/
+ 2026-08-04-workflow-activity-vnext-design.md
+User paths:
+ apps/aevatar-console-web/docs/superpowers/specs/
+ 2026-08-04-workflow-activity-vnext-user-paths.md
+Authentication and localization:
+ Existing Aevatar login, callback, session, returnTo, and Umi locale logic;
+ presentation may change, behavior may not.
+Production data source:
+ Real APIs and API-acknowledged user actions only; no mock fallback.
+Baseline integrity:
+ python3 apps/aevatar-console-web/docs/design-baselines/
+ workflow-activity-vnext/verify-baseline.py
+```
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-settings-save-action-dock-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-settings-save-action-dock-design.md
new file mode 100644
index 0000000000..ee8dd4dfb7
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-settings-save-action-dock-design.md
@@ -0,0 +1,289 @@
+# Settings Save Action Dock Design
+
+## Status
+
+Approved and implemented on 2026-08-06.
+
+This specification covers only the Workflow Activity vNext Settings save
+action layout. It is based on branch
+`feat/2026-08-04_workflow-activity-vnext` at
+`3416817a147df0d362bcf770b9cf59f65b624ab8`.
+
+The approved direction is a shell-owned bottom action area. It supersedes the
+earlier use of the word `sticky` for this save bar in the Workflow Activity
+vNext design specification and user paths. The save contract, authoritative
+data source, colors, navigation, and Settings information architecture remain
+unchanged.
+
+## Sources And Precedence
+
+This specification must be read with:
+
+1. `docs/canon/frontend-design.md`, including the Workflow Activity vNext
+ NyxID visual-alignment rules;
+2. `docs/design-baselines/workflow-activity-vnext/README.md` and its primary
+ Excalidraw artifact;
+3. `2026-08-04-workflow-activity-vnext-design.md`;
+4. `2026-08-04-workflow-activity-vnext-user-paths.md`.
+
+This document has precedence only for the location, scroll ownership,
+responsive layout, and accessibility semantics of the dirty Settings save
+actions. All other contracts retain their existing precedence.
+
+## Problem
+
+The current dirty action bar is rendered inside the AI defaults form and uses
+`position: sticky`. Its containing page owns the vertical scroll. As a result:
+
+- the action bar moves with the form before reaching its sticky threshold;
+- its location is coupled to the form card rather than the Settings work area;
+- below 600 px, CSS changes it to `position: static`, so it is no longer
+ persistent at all;
+- adding content above or below the form can change when and where it appears;
+- a viewport-fixed replacement would need fragile sidebar and gutter offsets
+ and could cover the last form field.
+
+The user needs the pending save decision to remain continuously reachable
+after editing without covering editable content or moving with that content.
+
+## Goals
+
+- Keep dirty Settings actions stationary at the bottom of the Settings main
+ work area on desktop, tablet, and mobile.
+- Let the header, local Settings navigation, form, alerts, and technical
+ details scroll in a separate region above the actions.
+- Keep the action area outside the 200 px desktop navigation rail.
+- Preserve the current Aevatar colors and Workflow Activity vNext visual
+ language while following the NyxID-derived spacing, density, and responsive
+ rules.
+- Preserve the existing accepted-to-observed save lifecycle, dirty navigation
+ guard, and authoritative API behavior.
+- Keep every form field, validation message, and recovery message reachable
+ without being covered by the action area.
+
+## Non-Goals
+
+- No backend, API, query, cache, authentication, routing, or persistence
+ change.
+- No redesign of Preferred service, Default model, Account, or Advanced.
+- No new global footer, global navigation behavior, or legacy Settings change.
+- No color, typography-family, or brand change.
+- No viewport-fixed positioning and no sidebar-width calculation.
+- No change to success, failure, delayed-observation, or leave-confirmation
+ semantics.
+
+## Chosen Layout
+
+### Shell Footer Slot
+
+`WorkflowActivityVNextShell` receives an optional footer slot. The shell uses
+its current layout when the slot is absent. When the slot is present, the main
+column becomes two rows:
+
+```text
+Workflow Activity vNext shell
+ top bar
+ navigation rail | main column
+ scroll region
+ page header
+ route content
+ footer region
+ route-provided action dock
+```
+
+The main column, not the viewport, establishes the boundary. The scroll region
+uses `minmax(0, 1fr)` and owns vertical overflow. The footer occupies its
+natural height and never scrolls with the content. This keeps it aligned with
+the shell when the desktop rail is visible and when the rail becomes a mobile
+drawer.
+
+Routes that do not provide a footer retain the existing main-column scrolling
+behavior. The change must not alter Workflows, Activity, Run detail, creation,
+or editor layout.
+
+### Settings Ownership
+
+`SettingsPage` always provides the shell footer slot so changing dirty state
+does not remount the scrolling form or move focus away from the edited control.
+The slot renders no visible content while the draft is clean and renders the
+action dock only while it is dirty. The save action component is removed from
+the AI defaults form body. It remains owned by `SettingsPage`, so it uses the
+existing `draft`, `baseline`, `savePhase`, `discard`, and `save` state and
+handlers without introducing a second state container.
+
+The footer wrapper follows the same responsive horizontal gutters as the
+Settings content. The dark action surface is centered at the existing Settings
+maximum width of 1120 px. It retains the current Aevatar background, border,
+text, button colors, radius, and restrained shadow.
+
+## Interaction And State Contract
+
+| State | Dock | Controls | Existing feedback |
+| --- | --- | --- | --- |
+| Clean | Hidden | None | Quiet current values |
+| Dirty | Fixed in shell footer | Restore and Save enabled according to capabilities | Unsaved title and description |
+| Saving | Fixed in shell footer | Restore disabled; Save loading and duplicate submission blocked | Existing saving state remains visible |
+| Accepted, awaiting observation | Fixed in shell footer | Restore and Save disabled | Existing confirming state remains visible |
+| Observed | Removed after baseline matches submitted values | None | Existing success toast |
+| Delayed | Fixed because the draft remains dirty | Retry Save or Restore according to current behavior | Existing durable delayed alert |
+| Failed | Fixed because the draft remains dirty | Save can be retried; Restore remains available | Existing durable error details |
+
+The dock appears and disappears from actual dirty state only. It does not use
+a timer, animation completion, request acceptance, or browser storage as its
+authority.
+
+Leaving through local navigation or the browser continues to use the existing
+dirty-navigation confirmation and `beforeunload` protection. Moving the
+actions must not bypass or duplicate those paths.
+
+## Responsive Contract
+
+### Desktop, At Least 1200 px
+
+- The 200 px navigation rail and 52 px top bar remain unchanged.
+- The footer is confined to the flexible main column.
+- Its content uses 40 px horizontal gutters and the Settings 1120 px maximum
+ width.
+- The status copy is on the left and the two actions are on the right.
+- The footer consumes layout space; it never overlays the scroll region.
+
+### Tablet, 768-1199 px
+
+- The footer uses the shell's medium horizontal gutter.
+- Copy and controls may wrap within the action surface without changing scroll
+ ownership.
+- Both commands remain visible without horizontal page overflow.
+
+### Mobile, Below 768 px
+
+- The hidden rail and drawer behavior remain unchanged.
+- The footer uses 16 px horizontal gutters and
+ `env(safe-area-inset-bottom)`.
+- Status copy and actions stack when required by available width.
+- Actions use stable responsive tracks; at the narrowest supported widths they
+ become one column rather than truncating or overlapping.
+- The scroll region shrinks to the remaining height, so focused fields,
+ validation messages, alerts, and technical details can scroll fully above
+ the dock.
+- The page must have no horizontal overflow at the 390 x 844 acceptance
+ viewport.
+
+## Accessibility
+
+- The footer is an explicitly named region, using localized copy equivalent
+ to `Unsaved settings actions`.
+- Live save status is scoped to status text rather than placing interactive
+ buttons inside a `role="status"` container.
+- Restore and Save retain native button semantics, visible focus, disabled and
+ loading behavior, and their current accessible names.
+- DOM order places the footer after the scroll region, matching its visual
+ order and keyboard sequence.
+- The fixed layout does not trap focus. When the dock appears, focus remains
+ on the field the user edited; no automatic focus jump occurs.
+- Reduced-motion preferences remain respected. The footer requires no
+ entrance or exit animation.
+
+## Error Handling
+
+This redesign does not create a new error surface. Existing save lifecycle
+feedback remains authoritative:
+
+- request failure preserves the dirty draft and technical error details;
+- accepted-but-unobserved state remains distinct from saved success;
+- delayed observation retains recovery actions;
+- only authoritative readback produces the saved success toast and clears the
+ dock.
+
+The layout must keep durable alerts inside the scroll region reachable while
+the dock remains present. The footer cannot cover or replace those alerts.
+
+## Test Strategy
+
+The highest-value evidence is the existing Workflow Activity vNext route
+integration test because the regression involves rendered Settings state,
+shell composition, and user actions together.
+
+Add focused coverage that proves:
+
+1. editing AI defaults renders a named save-action region with Restore and
+ Save while the clean state does not render that region;
+2. the shell exposes the footer as a sibling of its main scroll region, so the
+ actions are not children of the form panel;
+3. Restore clears dirty state and removes the footer without calling the save
+ API;
+4. existing Save loading, accepted-to-observed, failure, and dirty-navigation
+ tests continue to pass.
+
+CSS is verified through browser QA at 1440 x 900, 834 x 1112, and 390 x 844.
+The checks confirm a stationary dock during content scrolling, no covered last
+field or alert, no page-level horizontal overflow, reachable buttons, and
+correct mobile safe-area spacing.
+
+Local authenticated browser QA was attempted on port 5187 with the real API
+proxy and `MOCK=none`. The temporary origin had no authenticated session, and
+the configured NyxID client returned to the registered port 5173, which was
+already owned by another process. The implementation did not inject a session,
+copy browser storage, or substitute mock Settings data. Final authenticated
+screenshots therefore remain preview-environment verification; focused route
+tests, locale parity, changed-file static checks, and CI protect the local
+implementation contract.
+
+Local automated verification remains focused on changed and dependency-related
+files. Full frontend tests, typecheck, and production build are delegated to
+GitHub CI by the personal frontend workflow policy.
+
+## Affected Files
+
+The expected implementation surface is:
+
+- `src/pages/workflow-activity-vnext/WorkflowActivityVNextShell.tsx` for the
+ optional footer slot and scroll-region composition;
+- `src/pages/workflow-activity-vnext/settings/SettingsPage.tsx` for providing
+ the dirty action dock outside the form;
+- `src/pages/workflow-activity-vnext/styles.ts` for main-column, footer,
+ responsive, and safe-area layout;
+- `src/pages/workflow-activity-vnext/index.test.tsx` for route-level behavior;
+- both Workflow Activity vNext locale catalogues for the region label;
+- the existing vNext design and user-path documents to replace the superseded
+ sticky wording with the shell-fixed action contract.
+
+## Acceptance Criteria
+
+- Dirty save actions remain stationary at the bottom of the Settings main
+ column while Settings content scrolls.
+- The actions never enter or cover the desktop navigation rail.
+- The footer occupies layout space and never covers a field, alert, technical
+ detail, or focused control.
+- Clean, dirty, saving, accepted, observed, delayed, failed, restore, and
+ leave-confirmation behavior preserves the existing authoritative contract.
+- Other vNext routes retain their current layout and scroll behavior.
+- Desktop, tablet, and mobile have no action overlap or page-level horizontal
+ overflow, and mobile accounts for the bottom safe area.
+- The action region and controls are keyboard reachable and correctly named.
+- Existing Aevatar colors and the NyxID-derived non-color visual rules remain
+ intact.
+
+## Required Pull Request Declaration
+
+```text
+Design baseline:
+ apps/aevatar-console-web/docs/design-baselines/workflow-activity-vnext/
+Primary design:
+ aevatar-workflow-activity-vnext.excalidraw
+Design SHA-256:
+ 30e74d7b410ae72c4c91432355436679033679c54c10b1702908435b001577de
+Contract specification:
+ apps/aevatar-console-web/docs/superpowers/specs/
+ 2026-08-04-workflow-activity-vnext-design.md
+User paths:
+ apps/aevatar-console-web/docs/superpowers/specs/
+ 2026-08-04-workflow-activity-vnext-user-paths.md
+Authentication and localization:
+ Existing Aevatar login, callback, session, returnTo, and Umi locale logic;
+ presentation may change, behavior may not.
+Production data source:
+ Real APIs and API-acknowledged user actions only; no mock fallback.
+Baseline integrity:
+ python3 apps/aevatar-console-web/docs/design-baselines/
+ workflow-activity-vnext/verify-baseline.py
+```
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-workflow-archive-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-workflow-archive-design.md
new file mode 100644
index 0000000000..943df83f96
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-workflow-archive-design.md
@@ -0,0 +1,131 @@
+# Workflow Archive Design
+
+**Date:** 2026-08-06
+**Status:** Approved for implementation
+
+## Problem
+
+The Workflow catalogue overflow menu exposes only draft maintenance actions and
+`Copy workflow reference`. A published Workflow without an editable draft
+therefore has no lifecycle action at all. Users expect to be able to archive a
+published Workflow without deleting its authoring source, published revisions,
+or Activity history.
+
+`Delete draft` cannot satisfy that expectation. It removes only the editable
+workspace document and deliberately leaves the published Workflow runnable.
+
+## Product Semantics
+
+Archive is the product-level presentation of deactivating the Workflow's active
+published deployment:
+
+- archiving stops new runs through the published Workflow;
+- the editable draft, published revisions, and Activity history remain;
+- publishing the Workflow again restores an active published deployment;
+- draft-only Workflows continue to use `Delete draft` and do not expose
+ `Archive`;
+- an already archived Workflow does not expose a second Archive action.
+
+This is a boundary mapping, not a new global Workflow lifecycle. The backend's
+deployment state remains authoritative. The frontend must never persist a local
+archived flag or report success from command acceptance alone.
+
+## Catalogue Views And Status
+
+The existing default `All workflows` label becomes `Active workflows`, because
+archived Workflows are intentionally excluded from that view. The selector has
+three values:
+
+- **Active workflows:** draft-only Workflows and published Workflows whose
+ deployment is not deactivated;
+- **Drafts:** every Workflow with an editable draft, including a draft whose
+ previously published deployment is archived;
+- **Archived:** committed Workflows whose authoritative deployment status is
+ `Deactivated`.
+
+The URL omits the default active view, keeps `view=drafts`, and adds
+`view=archived`. Unknown view values fall back to active.
+
+Status presentation follows runtime truth. `Deactivated` maps to `Archived`
+before the existing Published/Draft rules, so an archived published Workflow
+with a remaining draft is still visibly archived.
+
+## Archive Action
+
+`Archive` appears in the overflow menu only when all of these facts are true:
+
+- the row has a committed source;
+- it has an active revision;
+- it has a non-empty deployment ID;
+- its normalized deployment status is `Active`.
+
+The menu action uses an archive icon, is visually separated from ordinary
+actions, and opens a confirmation dialog. The dialog states that new runs stop
+while drafts, published revisions, and Activity history remain. The confirming
+button is explicit and visually cautionary.
+
+Draft rows retain `Rename` and `Delete draft`. All rows retain
+`Copy workflow reference`.
+
+## Data Flow
+
+1. The user confirms Archive.
+2. The frontend calls the existing service deployment deactivation endpoint
+ with `serviceId = workflowId`, the row's exact `deploymentId`,
+ `tenantId = scopeId`, and the canonical scope service app/namespace.
+3. A `202 Accepted` receipt means only that the command entered dispatch. The
+ UI enters an observing state; it does not close the dialog or show success.
+4. A bounded observer rereads the scope Workflow list and matches the exact
+ `workflowId`. Success requires its normalized `deploymentStatus` to become
+ `Deactivated`.
+5. Once observed, the authoritative committed query is refreshed, the dialog
+ closes, and the UI reports `Workflow archived`.
+
+The observer follows the existing Workflow publication/materialization pattern:
+deterministic delay injection for tests, a small bounded delay schedule in the
+browser, and no query-time mutation or locally invented state.
+
+## Failure Handling
+
+- If the deactivation request fails before acceptance, keep the dialog open,
+ report that the archive request failed, and let `Try again` resubmit it.
+- If the request is accepted but the read model is not observed within the
+ bounded window, keep the dialog open and report that archival is still being
+ confirmed. `Check again` repeats only observation and must not submit a second
+ deactivation command.
+- Unauthorized and forbidden responses use the same non-technical Workflow
+ error surface as the existing catalogue actions.
+- Refresh or partial-list failures do not optimistically remove the row.
+
+## Implementation Boundaries
+
+- Add a focused archival helper beside the vNext Workflow surface for status
+ normalization, action eligibility, and accepted-command observation.
+- Reuse `servicesApi.deactivateDeployment`; do not add a duplicate HTTP client
+ or a frontend-only archive store.
+- Extend `WorkflowRow` only with the committed deployment facts required by the
+ action and status.
+- Keep all changes within Workflow Activity vNext, its locale catalogues, and
+ focused tests. No backend contract or protobuf change is needed.
+
+## Verification
+
+Focused tests must prove:
+
+- an active published row exposes Archive while draft-only and archived rows do
+ not;
+- confirmation calls deactivation with distinct Workflow, deployment, and
+ scope identities;
+- command acceptance alone does not show success;
+- observed `Deactivated` state closes the dialog and moves the row from Active
+ to Archived;
+- delayed observation can be retried without resubmitting the command;
+- draft deletion semantics remain unchanged;
+- `view=archived` is preserved in navigation and stale/unknown views fall back
+ to Active.
+
+Browser verification must cover the overflow menu, confirmation dialog, active
+and archived filters, and the no-overlap behavior of the table at desktop and
+mobile widths. No destructive archive request will be submitted against the
+user's real remote data during browser verification without separate explicit
+authorization.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-workflow-list-metadata-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-workflow-list-metadata-design.md
new file mode 100644
index 0000000000..7cff175b40
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-workflow-list-metadata-design.md
@@ -0,0 +1,71 @@
+# Workflow List Metadata Design
+
+## Product Mismatch
+
+The Workflow list currently gives draft descriptions, publication revision IDs, ownership labels, and update context the same visual priority as the workflow name. This makes internal scope and revision details look like primary product information, while users need to scan workflow identity, lifecycle status, and recency first.
+
+This is primarily a placement and prominence mismatch. The existing runtime and API semantics are sufficient for the requested UI change:
+
+- `description` is user-authored workflow purpose text.
+- `activeRevisionId` determines whether a workflow is published, but the identifier itself is diagnostic detail.
+- `directoryLabel` can resolve to a technical scope identifier and is not useful in the primary list.
+- `updatedAtUtc` is the authoritative last-update time.
+- No authoritative creation time exists in the current list contract.
+
+## Chosen Design
+
+Use a four-column catalogue:
+
+```text
+Workflow | Status | Last updated | Actions
+```
+
+The Workflow column shows only the workflow name. When a workflow has a non-empty description, its name becomes a keyboard-focusable description trigger. Hovering or focusing that trigger opens a fixed-width popover containing the full description. Workflows without descriptions remain plain text and do not expose an empty trigger or popover.
+
+The description popover uses a 320 px content width with a viewport-safe maximum width and normal wrapping. It is contextual supporting information, not part of the row's default reading order.
+
+The Status column contains one badge:
+
+- `Draft` when there is no active revision.
+- `Published` when an active revision exists.
+
+The active revision identifier is not rendered in the primary list. The list does not visibly render ownership or scope labels. A draft's user-facing directory label may remain an internal accessibility discriminator for same-name row actions, but it is not row metadata and must not create another visible label. Workflow and service identifiers remain available only through the existing explicit copy-reference action and navigation contracts.
+
+The Last updated column renders the existing authoritative `updatedAtUtc` value with the established localized formatter. The UI does not add a Created column and does not derive a creation time from update time, file metadata, identifiers, or ordering.
+
+## Responsive And Accessible Behavior
+
+The existing horizontally scrollable table remains the layout owner. Column widths prioritize the workflow name, reserve stable space for status and last update, and keep Actions wide enough for the existing commands. On narrow screens, horizontal scrolling preserves the column relationships instead of collapsing unrelated facts into the Workflow cell.
+
+The description trigger supports both pointer hover and keyboard focus through the Ant Design popover behavior. Its focus treatment is visible, and the popover content wraps long descriptions without resizing the table row. No empty description surface is rendered.
+
+## Data Flow
+
+No API, model, or backend change is required.
+
+1. Draft and committed workflow sources continue to merge by `workflowId`.
+2. Draft descriptions continue to populate the merged row when available.
+3. `activeRevisionId` is converted to the user-facing status label only.
+4. `updatedAtUtc` continues to supply the localized last-update value.
+5. `directoryLabel` is not rendered as visible row metadata; when available, it may disambiguate the accessible names of same-name row actions.
+
+Search continues to match workflow name, description, and workflow ID even though descriptions and IDs are not permanently rendered. Sorting continues to use `updatedAtUtc` descending.
+
+## Error And Empty States
+
+Loading, partial-source failure, total failure, retry, empty, and filtered-empty states are unchanged. Missing or invalid update values continue to use the existing `Unavailable` behavior. A missing description simply disables the contextual description popover for that row.
+
+## Verification
+
+Focused page tests will assert that:
+
+- the table exposes Workflow, Status, Last updated, and Actions headers;
+- descriptions are absent from the default row content and appear through hover and keyboard focus;
+- the description popover has a fixed 320 px width and is not created for empty descriptions;
+- Published never exposes the active revision identifier;
+- ownership labels and the scope ID are absent from the workflow list;
+- the authoritative update value is rendered in the Last updated column;
+- no Created column or inferred creation value appears;
+- existing row actions, search behavior, and identity routing remain intact.
+
+Local verification remains limited to the Workflow Activity test file and changed-file static checks. The full frontend suite, typecheck, and production build are delegated to GitHub CI by the personal frontend workflow policy.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-workflow-row-action-hierarchy-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-workflow-row-action-hierarchy-design.md
new file mode 100644
index 0000000000..17c7c118da
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-06-workflow-row-action-hierarchy-design.md
@@ -0,0 +1,207 @@
+# Workflow Row Action Hierarchy Design
+
+## Status
+
+Visual direction and the written contract were approved by the user on
+2026-08-06. Implementation is authorized.
+
+This specification applies only to the Workflow Activity vNext catalogue row
+actions on `feat/2026-08-04_workflow-activity-vnext`. It does not change any
+Workflow identity, API, mutation, Activity filtering, or confirmation
+semantics.
+
+## Problem
+
+The catalogue currently presents five row capabilities through four adjacent
+controls:
+
+- bordered `Open` and `Activity` buttons;
+- a standalone red Delete icon for draft rows;
+- a neutral overflow trigger containing Rename and Copy workflow reference.
+
+The UI therefore implies that draft deletion deserves more persistent visual
+prominence than rename or copy, even though deletion is a low-frequency,
+destructive management action. The row also mixes button types without an
+explicit action hierarchy and adds capabilities without preserving the
+catalogue's original `one clear Open action` contract.
+
+The mismatch is one of placement and prominence: the user expects one obvious
+way to enter the Workflow, a secondary way to inspect its Activity, and one
+consistent place for lower-frequency management actions.
+
+## Existing Authorities
+
+The implementation must continue to follow:
+
+1. `docs/canon/frontend-design.md` and its component-interaction requirements;
+2. `docs/design/2026-04-23-component-interaction-standard.md`;
+3. `2026-08-04-workflow-activity-vnext-design.md`, especially the catalogue,
+ information-priority, accessibility, and interaction contracts;
+4. the existing authoritative Workflow draft and scope APIs.
+
+The global interaction standard governs complete default, hover, active,
+disabled, loading, focus, duplicate-submission, and error states. This design
+adds the missing catalogue-specific prominence rule; it does not replace the
+global standard.
+
+## Chosen Action Model
+
+Each row exposes exactly three persistent controls in this order:
+
+```text
+[ Open ] [ View activity ] [ More actions ]
+```
+
+### Open
+
+- The only primary action in the row.
+- Uses the existing blue primary treatment.
+- Includes the edit icon shown in the approved visual and the visible label
+ `Open`.
+- Is a real link to the row's canonical Workflow editor URL, so modified click,
+ middle click, copy link, and browser link semantics continue to work.
+- Uses a row-specific accessible name that includes the Workflow name and
+ ownership label.
+
+### View Activity
+
+- A neutral secondary action with the same height, radius, typography, and
+ icon alignment as Open.
+- Uses the visible label `View activity`, not the less specific `Activity`.
+- Is a real link containing the row's exact authoritative `workflowId` query.
+- Uses a row-specific accessible name that includes the Workflow name and
+ ownership label.
+
+### More Actions
+
+- A neutral icon-only overflow trigger with stable dimensions.
+- Uses the existing ellipsis icon and a localized row-specific accessible
+ name.
+- The familiar ellipsis does not require persistent visible copy. It retains a
+ visible focus state and the Dropdown's keyboard behavior.
+- The trigger remains present even when a published-only row currently has
+ only one available low-frequency action. Stable placement is more important
+ than changing the row shape by capability count.
+
+## Overflow Menu
+
+Draft-capable rows use this exact order:
+
+```text
+Rename
+Copy workflow reference
+-----------------------
+Delete draft
+```
+
+Published-only rows show only `Copy workflow reference` until another honest
+row capability exists.
+
+- Rename retains the current draft read, update, refresh, duplicate-name
+ warning, loading lock, success toast, and error toast behavior.
+- Copy retains the exact `workflowId` payload and current success/error toasts.
+- Delete moves from the persistent row surface into the menu. It is separated
+ from ordinary items and uses the menu's danger presentation.
+- Selecting Delete opens the existing confirmation Modal. No deletion occurs
+ from the menu click itself.
+- The confirmation Modal retains its exact target explanation, pending lock,
+ retry behavior, and danger confirmation button.
+
+The menu item hierarchy is semantic, not decorative. Danger red is reserved
+for Delete; Rename and Copy remain neutral.
+
+## Visual Contract
+
+- All three persistent controls share one height, radius, icon size, gap, font,
+ hover transition, active response, and visible focus treatment.
+- Open alone uses primary blue. View activity and More actions are neutral.
+- The action cell does not show a standalone red icon.
+- The action group has a stable no-wrap desktop layout and cannot resize on
+ hover, focus, loading, or menu open.
+- Menu items use one row height and icon/text alignment. The divider and danger
+ color communicate destructive separation without adding another button
+ style.
+- Existing Aevatar and Ant Design tokens remain authoritative. No new palette,
+ shadow system, radius token, or reusable global component is introduced.
+- Persistent buttons reuse `AEVATAR_INTERACTIVE_BUTTON_CLASS` in addition to
+ the existing vNext focus and dimension contract.
+
+## Responsive Contract
+
+- Desktop keeps the three-control action group aligned to the end of the row.
+- The local table scroll region remains the only horizontal overflow owner.
+- At touch-oriented narrow widths, persistent action targets meet the existing
+ 44 px minimum while preserving the compact desktop target size.
+- The overflow menu remains within the viewport and opens from the row's end
+ edge.
+- No action is discoverable only through hover.
+
+## Accessibility And Navigation
+
+- Open and View activity are links because they navigate; Rename, Copy,
+ Delete, and the overflow trigger remain actions.
+- Icons accompanying visible labels are decorative. The icon-only overflow
+ trigger has a localized accessible name and `aria-expanded`/menu semantics
+ through Ant Design Dropdown.
+- Duplicate Workflow names remain distinguishable in action names by including
+ the authoritative user-facing ownership label.
+- The Delete menu item uses both text and danger styling; color is not its only
+ signal.
+- Keyboard users can reach Open, View activity, More actions, every menu item,
+ and the confirmation Modal in DOM order. Closing the menu restores normal
+ row navigation without triggering Open.
+
+## Testing Contract
+
+Focused route tests must prove:
+
+1. a row has one primary Open link, one View activity link, and one More actions
+ button;
+2. no standalone Delete button is present before the menu opens;
+3. a draft row menu orders Rename, Copy workflow reference, divider, and Delete
+ draft, with Delete marked dangerous;
+4. selecting Delete opens the existing confirmation without calling the API;
+5. confirming the Modal preserves the existing deletion, refresh, retry, and
+ error behavior;
+6. Open and View activity expose real canonical `href` values and preserve the
+ exact `workflowId` identity boundary;
+7. same-name rows receive distinguishable accessible action names;
+8. rename and copy behavior continue to pass their existing tests.
+
+Changed source and test files receive focused Jest and changed-file Biome
+validation only. Full frontend tests, typecheck, and production build remain
+delegated to GitHub CI by the personal frontend workflow policy.
+
+## Non-Goals
+
+- No capability is removed.
+- No new Workflow action, bulk action, row selection, Run action, or context
+ drawer is added.
+- No change to duplicate-name allowance or discriminator content.
+- No backend, API, query key, route identity, Activity contract, or toast
+ mapping change.
+- No redesign of the catalogue table, status badges, search, filters, header,
+ navigation, or empty states.
+
+## Expected Implementation Surface
+
+- `workflows/WorkflowsPage.tsx`: link semantics, visible action hierarchy, and
+ overflow menu composition.
+- `styles.ts`: narrowly scoped stable action layout and touch target rules.
+- `index.test.tsx`: observable hierarchy, navigation, menu, and preserved
+ mutation behavior.
+- Workflow Activity vNext locale catalogues: `View activity` and row-specific
+ accessible names.
+- The original vNext design: one concise catalogue row-action rule so future
+ capabilities cannot return to the persistent row surface by default.
+
+## Acceptance Criteria
+
+- The row visibly presents only Open, View activity, and More actions.
+- Open is the only primary button; View activity and More actions are neutral.
+- Rename, Copy workflow reference, and Delete draft use one overflow menu.
+- Delete draft is last, separated, dangerous, and still requires confirmation.
+- All five existing capabilities retain their authoritative behavior.
+- Navigation uses real links and remains correct for distinct Workflow IDs.
+- The action group is keyboard operable, screen-reader distinguishable, stable
+ at desktop density, and usable at touch widths.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-07-workflow-node-reselection-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-07-workflow-node-reselection-design.md
new file mode 100644
index 0000000000..2f88af13ae
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-07-workflow-node-reselection-design.md
@@ -0,0 +1,54 @@
+# Workflow Node Reselection Design
+
+## Problem
+
+The vNext workflow editor currently treats every node click as a request to
+leave the active node configuration context. When the inspector contains
+unapplied changes, clicking the already-selected node therefore opens the
+discard confirmation even though no editing context would change.
+
+This is a runtime and mental-model mismatch: users expect selecting the active
+node to be idempotent, while the current event path treats it like a node
+switch.
+
+## Decision
+
+Node reselection is a no-op at the vNext editor page boundary. When the clicked
+node ID equals the editor's selected node ID, the page keeps the current
+inspector and its unapplied configuration draft without opening a dialog.
+
+The existing discard confirmation remains authoritative for real context
+changes:
+
+- selecting a different node;
+- selecting the canvas;
+- closing the inspector;
+- switching to YAML mode;
+- inserting another node;
+- navigating away from the editor.
+
+The selection boundary owns this decision because it has both the clicked node
+ID and the current selected node ID. The shared graph canvas and the inspector
+remain independent of route-specific selection policy.
+
+## Scope
+
+- Add an identity check to the vNext editor's node-selection handler.
+- Add rendered interaction coverage for same-node reselection and different-node
+ switching while node configuration is dirty.
+- Preserve all existing save, run, publish, close, canvas, YAML, insertion, and
+ navigation guards.
+- Do not change shared canvas behavior, localization, API contracts, or backend
+ code.
+
+## Verification
+
+The focused route integration test must prove both sides of the boundary:
+
+1. Re-clicking the dirty active node does not open the discard dialog and does
+ not change the inspector draft.
+2. Clicking another node while dirty still opens the discard dialog, preserves
+ the active draft on cancel, and switches only after explicit discard.
+
+The exact test file is run directly. Shared locale files are not passed to
+`--findRelatedTests`.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-11-vnext-list-return-refresh-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-11-vnext-list-return-refresh-design.md
new file mode 100644
index 0000000000..2fbeee57eb
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-11-vnext-list-return-refresh-design.md
@@ -0,0 +1,56 @@
+# vNext List Return Refresh Design
+
+## Status
+
+Approved for implementation on 2026-08-11 under the user's standing direction
+to proceed with the recommended approach without another approval round.
+
+Implementation branch: `fix/2026-08-11_refresh-vnext-lists-on-return`.
+
+Base branch: `feat/2026-08-04_workflow-activity-vnext` at
+`4ea36e70f904680b05f90a27b66cf7b85cb14bbd`.
+
+## Problem
+
+The shared React Query client keeps successful query data fresh for 30 seconds.
+Workflow Activity vNext renders list and detail surfaces exclusively, so returning
+from a detail surface unmounts the detail and remounts the list. During the
+freshness window, React Query serves the cached list without issuing a request.
+
+This leaves the Workflows catalogue stale after returning from the workflow
+editor. The Activity runs list has the same lifecycle when returning from run
+detail and can show an outdated run status for the same reason.
+
+## Product Contract
+
+- Entering the Workflows list refreshes its catalogue from the server.
+- Entering the Activity list refreshes its runs from the server.
+- The rule covers editor, new-workflow, run-detail, browser-back, and sidebar
+ return paths without requiring each source surface to know the list query key.
+- Existing filters and pagination query keys remain unchanged.
+- Existing loading and error presentation remains unchanged.
+- Other cached queries keep the shared 30-second freshness policy.
+
+## Design
+
+Set `refetchOnMount: 'always'` on the Workflows catalogue query and the Activity
+runs query. This makes list ownership explicit: a list surface decides that its
+server-backed collection must be revalidated whenever the surface is entered.
+
+The alternative of setting `staleTime: 0` would depend on default remount
+behavior and obscure the product intent. Invalidating from editor/detail
+mutations would couple independent surfaces, miss no-mutation return paths, and
+require every navigation path to cooperate.
+
+## Verification
+
+Use production-like test clients with `staleTime: 30_000` so the tests reproduce
+the bug rather than passing because test data is immediately stale. Cover:
+
+- Workflows list -> workflow editor -> Workflows list, asserting a second
+ catalogue request.
+- Activity list -> run detail -> Activity list, asserting a second runs request.
+
+Run only the changed test files and affected frontend checks. The complete
+frontend suite, typecheck, and production build remain delegated to GitHub CI
+under the personal incremental frontend policy.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-11-workflow-activity-authoritative-contracts-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-11-workflow-activity-authoritative-contracts-design.md
new file mode 100644
index 0000000000..8c46082e2a
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-11-workflow-activity-authoritative-contracts-design.md
@@ -0,0 +1,114 @@
+---
+title: Workflow Activity authoritative contracts
+status: approved
+owner: Aevatar frontend
+---
+
+# Workflow Activity Authoritative Contracts Design
+
+## Context
+
+Issue #3245 coordinates backend contracts needed by the Workflow Activity vNext frontend. The completed backend work on `feature/integrate` covers three capabilities:
+
+- #3250: a typed, cursor-paginated Activity feed;
+- #3251: authoritative recovery capabilities and a fork receipt with a routable `newRunId`;
+- #3252: durable retry/fork and sub-workflow lineage.
+
+This design applies those contracts to the frontend on top of `feat/2026-08-04_workflow-activity-vnext`. It supersedes the older client-side assumptions in the original vNext design where the new backend facts now exist.
+
+## Goals
+
+- Make Activity useful for identifying and investigating exact runs without exposing technical actor identities.
+- Scale Activity history through the backend cursor contract.
+- Make recovery eligibility, alternatives, reuse, revision, and cost warnings backend-owned.
+- Route fork receipts and lineage with public `runId` values only.
+- Keep retry/fork lineage distinct from sub-workflow lineage.
+
+## Non-Goals
+
+- Do not implement #3253 version-aligned Run Detail surfaces.
+- Do not implement #3254 usage provenance, currency, or the broader five-surface Run Detail redesign.
+- Do not infer workflow, run, actor, member, service, or definition identities.
+- Do not merge `feature/integrate` into the frontend branch; it is the contract source, not this PR's base.
+- Do not add production mocks, optimistic lineage, or fabricated recovery facts.
+
+## Contract Boundary
+
+The shared workflow Activity model will add explicit types for:
+
+- `WorkflowActivityRunFeedFilter` and `WorkflowActivityRunFeedPage`;
+- authoritative Activity row facts, including initiator, redacted input summary, current step, first failure, waiting state, timestamps, duration, recovery capability, and lineage;
+- recovery eligibility, unavailable reason codes, recommended actions, and action semantics;
+- retry/fork lineage and sub-workflow lineage as separate structures;
+- fork receipts containing both public `newRunId` and technical `newRunActorId`.
+
+The API adapter will add `listActivityRuns(scopeId, filter)` for `GET /api/workflow/observatory/activity-runs`. Existing `listRuns`, detail, and graph methods remain because the backend introduced an additive feed rather than replacing every observatory endpoint.
+
+Decoders remain strict. Malformed required identities, cursor envelopes, recovery fields, or lineage fields fail at the transport boundary instead of leaking partially trusted objects into pages.
+
+## Activity Page
+
+The Activity page will use TanStack Query cursor pagination. Its server query key includes scope, status, origin, definition, workflow, and time-range filters. Changing any server filter creates a new query and discards the prior cursor chain.
+
+The page sends `workflowId` directly. It removes the extra Workflow-detail lookup and no longer converts `workflowId` into `definitionActorId`.
+
+The default table groups backend facts into a compact operational view:
+
+- Workflow: workflow name and a secondary short run reference;
+- Status: status plus the first available failure, waiting, or current-step context;
+- Started: localized start time and backend duration or a live elapsed value;
+- Initiator: user-readable initiator plus run source;
+- Input: the backend-redacted input summary;
+- Action: open the exact run.
+
+Actor ids, command ids, projection fields, and raw payloads are excluded from the default table. Mobile retains workflow, status, time, and the open action through the existing responsive table treatment.
+
+The initial request asks for `includeTotalCount=true`. The page displays loaded and total counts when the backend supplies the total. `Load more` uses the opaque `nextCursor`; the client does not inspect or synthesize it. A next-page failure preserves loaded rows and provides a local retry. A `malformed_cursor` response offers a refresh from the first page.
+
+The existing `q` filter remains client-only and is labeled as filtering loaded runs so it does not imply a full-history server search.
+
+## Recovery
+
+Run Detail reads top-level `recoveryCapability`. It removes `resolveRunRecovery(steps, graph)` and never derives eligibility from failed-step count or graph root shape.
+
+Retry and Run again are enabled only when their respective capability has `eligibility=eligible` and a non-blank `startingStepId`. An unavailable action remains keyboard-focusable with `aria-disabled` and a visible backend reason. Recommended alternatives are presented in user language; only routes backed by existing stable identities are navigable.
+
+Before dispatch, the confirmation modal shows:
+
+- the source input;
+- definition revision id and version;
+- starting step;
+- whether prior step outputs are reused;
+- that a separate run will be created and the source run remains immutable;
+- a model/tool cost warning only when the capability says cost may recur.
+
+The fork request continues using the source run id, authoritative starting step, and source input required by the current endpoint. HTTP 202 is rendered as accepted, not completed. Its primary action opens the new Run Detail route using `newRunId`. `newRunActorId`, command id, correlation id, and status URL remain in Technical details.
+
+## Lineage
+
+Run Detail adds a Related runs section with two independent groups:
+
+- Retry history: source, original, attempt, starting step, and child runs;
+- Sub-workflows: parent, root, depth, parent step, and child runs.
+
+Every non-blank public run id links through `buildWorkflowActivityRunHref(scopeId, runId)`. Actor ids are never routing inputs and appear only in Technical details. Unavailable or legacy lineage renders an honest unavailable message without inventing relationships.
+
+## Error Handling
+
+- Initial Activity failure uses the existing full-page error state.
+- Next-page failure is local to the pagination footer and preserves prior pages.
+- Strict decoder failures surface through the existing Activity API error path.
+- Fork failure leaves the confirmation available for retry and shows the existing toast treatment.
+- Missing or unavailable capability disables dispatch with the backend reason.
+- Lineage unavailability does not fail the whole Run Detail page.
+
+## Testing
+
+Focused Jest tests will cover:
+
+- feed query encoding, pagination envelope decoding, authoritative workflow id filtering, recovery/lineage decoding, and `newRunId` receipt decoding;
+- Activity initial loading/error/empty states, rich authoritative row facts, URL-backed filters, cursor append/reset behavior, next-page failure preservation, and exact run navigation;
+- Run Detail capability-driven action states, focusable unavailable reasons, confirmation disclosures, immutable fork request, accepted receipt navigation via `newRunId`, and separate lineage groups.
+
+Local verification follows the personal incremental frontend policy: run the scope analyzer, every changed test file, dependency-related focused tests, and changed-file static checks only. Full frontend suite, full typecheck when no affected target exists, and production build are delegated to GitHub CI.
+
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-11-workflow-schedule-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-11-workflow-schedule-design.md
new file mode 100644
index 0000000000..554c95b3cd
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-11-workflow-schedule-design.md
@@ -0,0 +1,328 @@
+# Workflow Schedule vNext Design Specification
+
+**Status:** Design baseline for frontend implementation
+
+**Backend authority:** [Issue #3446](https://github.com/aevatarAI/aevatar/issues/3446)
+and the workflow-scoped Schedule facade merged by PR #3451.
+
+**Existing-Schedule management extension:**
+[Workflow Schedule Management and History Design Specification](./2026-08-21-workflow-schedule-history-design.md).
+The extension is normative for Overview, History, edit navigation, action
+hierarchy, and the handoff to filtered Activity.
+
+## Product Decision
+
+The previous design implied that a Workflow Schedule was a Team member
+automation requiring a separate authorization review. The backend and the pure
+Workflow product model define something else: a recurring Schedule is a child
+resource of one Workflow, addressed by exact `scopeId + workflowId`.
+
+The corrected model is:
+
+```mermaid
+flowchart LR
+ W["Published Workflow"] --> S["Workflow Schedule"]
+ S --> F["Scheduled fires"]
+ F --> R["Workflow execution"]
+```
+
+- Workflow owns Schedule configuration, list, detail, and mutations.
+- Schedule is not a Workflow graph node, Run-dialog mode, global setting, or
+ Team member automation.
+- Team Automation remains a separate Team/Member product resource.
+- Activity remains the separate owner of actual Run history and Run detail.
+ Schedule History owns only bounded recent attempts and provides a secondary
+ `View related runs in Activity` handoff with exact Workflow and Schedule
+ filters. A recent attempt may also open its exact Activity Run when the
+ backend fire record contains a non-empty `runActorId`. Any attempt without
+ that authoritative identity remains non-interactive; the row must not reuse
+ the Schedule-wide Activity handoff or guess a Run destination.
+
+## Identity Boundary
+
+The frontend Schedule resource needs only:
+
+```text
+scopeId
+workflowId
+scheduleId
+```
+
+`scopeId + workflowId` select the exact Schedule collection.
+`scheduleId` selects one resource inside that collection.
+
+The frontend must not require or infer `teamId`, `memberId`, or
+`publishedServiceId` for this flow. The backend resolves the published
+service, service endpoint, active revision, and authenticated NyxID owner
+binding behind the Workflow facade.
+
+At the API boundary, the frontend normalizes the Workflow facade's
+`runActorId` and the scheduled-dispatch transport's `targetActorId` into one
+product-semantic `runActorId`. Both fields carry the same authoritative target
+Run actor identity in this response boundary. UI components must not branch on
+the transport field name or expose `targetActorId` as a second identity.
+
+Draft, unpublished, or not-ready Workflows show an unavailable validation
+state. They never fall back to Team Automation.
+
+## Information Architecture
+
+### Workflows Catalogue
+
+Each published Workflow row exposes `Schedules` beside `Run` and `Open`.
+The action opens a management modal over the current list. It is the durable
+place to see every Schedule owned by that Workflow and provides `New schedule`
+for creation without navigating into the editor.
+
+The collection container has one resource-owned title:
+`Schedules for {workflowName}`. Its content toolbar contains only Refresh and
+`New schedule`; it does not repeat `Schedules` or add a generic subtitle that
+paraphrases the same Workflow ownership. A nested title remains appropriate
+only for a distinct state such as `No schedules yet` or a different task such
+as `Edit schedule`.
+
+Draft and unavailable Workflows keep the action disabled and explain that the
+Workflow must be saved and published.
+
+### Workflow Editor
+
+`Schedule` appears beside `Run` in the editor header. It opens the existing
+right-side surface while preserving the canvas. The panel contains:
+
+- a list of Schedules returned for this Workflow;
+- the same new-Schedule flow used by the catalogue modal;
+- Schedule detail and editing;
+- enable or disable, run-now, and delete actions.
+
+The modal and panel are two containers for one state machine, not two resource
+models.
+
+## Creation Flow
+
+The shared creation flow is:
+
+```text
+configure -> previewing -> review -> create -> toast -> refresh list/detail
+```
+
+### Configure
+
+The primary form order is:
+
+| UI field | API field | Behavior |
+| --- | --- | --- |
+| Schedule name | `displayName` | Editable and required |
+| Repeat + time | `cronExpression` | Common recurring rules use the human builder |
+| Raw cron | `cronExpression` | Opens only through `write it as cron instead` |
+| Time zone | `timezone` | Browser IANA zone with `UTC` fallback |
+| Prompt | `prompt` | Optional; empty means no fixed prompt |
+| Enabled after creation | `enabled` | Explicit create request state |
+
+The configure surface keeps only user-actionable labels. The workflow identity
+strip shows the exact workflow name and publication state without a generic
+category banner. Run input is labeled directly, and the enabled switch sits
+beside it without a separate explanatory section. Backend implementation notes
+such as where preview times are calculated are not shown in the product UI.
+
+`headers` is supported by the API but is not a primary first-release field.
+The UI can add it later under an advanced surface without changing the core
+flow.
+
+The first release supports recurring five-field cron only. It does not expose
+one-shot fields even if lower layers contain them.
+
+The human repeat builder covers the recurring presets users commonly need:
+
+- Every hour (at minute `0` of each hour)
+- Every day (with a time)
+- Weekdays (with a time)
+- Every week (with a weekday and time)
+- Every month (with a day of month and time)
+
+Weekly and monthly selections are still serialized as ordinary five-field cron
+expressions. More advanced expressions remain available through the explicit
+`write it as cron instead` disclosure. The two input modes are mutually
+exclusive: raw Cron mode replaces the Repeat, weekday/day-of-month, and Time
+controls with Cron expression while keeping Time zone editable. Returning to
+the repeat builder restores the human-readable controls from the Cron value.
+
+### Preview
+
+`Review schedule` validates the local form and calls:
+
+```text
+POST /api/scopes/{scopeId}/workflows/{workflowId}/schedules/preview
+```
+
+The request contains `cronExpression`, `timezone`, and `count: 5`. The
+browser never calculates future fire times. A preview error returns the user
+to the form with the invalid field still editable. Preview, create, edit, and
+lifecycle failure Toasts use the shared user-facing error boundary; they never
+display raw proxy text, endpoint paths, local addresses, or backend exception
+messages.
+
+### Review
+
+Review is a normal creation confirmation, not an authorization screen. It
+shows:
+
+- Workflow name;
+- Schedule name;
+- recurrence and timezone;
+- optional prompt;
+- enabled-after-creation state;
+- five fire times returned by preview.
+
+No Team/Member identity, service identity, node grant, LLM selection,
+credential, permission digest, or policy version appears.
+
+`Create schedule` calls:
+
+```text
+POST /api/scopes/{scopeId}/workflows/{workflowId}/schedules
+```
+
+with:
+
+```json
+{
+ "displayName": "Weekly feedback report recurring work",
+ "cronExpression": "0 9 * * 1-5",
+ "timezone": "Asia/Shanghai",
+ "enabled": true,
+ "prompt": ""
+}
+```
+
+### Create acknowledgement
+
+Create returns `202 Accepted` and a mutation receipt. The UI must not claim
+Active, next fire, or final state from the command response. The catalogue
+modal closes immediately and shows a non-blocking Toast saying that the
+request was accepted and will appear in the list shortly. The editor panel
+returns to the Schedule list and shows the same Toast.
+
+The Workflow-scoped list continues refreshing while the panel remains open;
+the next list mount also refetches, so the read model remains the authority for
+the created Schedule. No second accepted-state modal is needed.
+
+## Existing Schedule Management
+
+The list and detail surfaces use:
+
+```text
+GET /api/scopes/{scopeId}/workflows/{workflowId}/schedules
+GET /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}
+```
+
+The UI may present only typed response facts, including:
+
+- display name, cron expression, timezone, enabled state, and prompt;
+- next and last fire;
+- last error and error code;
+- fire and failure counts;
+- recent fires;
+- a per-attempt exact Run destination only when `runActorId` is non-empty;
+- a separate Workflow + Schedule filtered Activity handoff for the Schedule as
+ a whole;
+- service revision only when useful as secondary diagnostics.
+
+Low-level service, actor, command, correlation, credential, or Team fields do
+not belong in the primary product UI.
+
+Collection and selected-Schedule request failures keep their owning modal or
+panel shell, name what could not be loaded, and provide Retry. Their primary
+description uses stable product language rather than the backend exception
+string. Raw transport diagnostics may appear only inside a collapsed
+`Technical details` disclosure. Long diagnostic text wraps or scrolls inside
+that disclosure; it must never widen the Alert, modal, drawer, or viewport.
+
+Selecting an existing Schedule opens its read-only Overview before editing.
+Overview and History are sibling tabs in one stable management surface.
+Overview owns configuration and observed status; History renders the returned
+`recentFires` as bounded recent attempts in newest-first order. Loading, empty,
+request-failure, and failed-attempt states remain distinct. The focused
+management and History specification linked above defines the exact layout,
+copy, action hierarchy, diagnostics disclosure, and Activity handoff.
+
+### Edit
+
+`Change schedule` enters the editable configuration from the observed detail.
+Cancel returns to that Schedule detail rather than closing the management
+surface or skipping back to the collection. Edit hydrates the observed
+Schedule values and sends a full replacement:
+
+```text
+PUT /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}
+```
+
+The request must preserve the observed `enabled` value. Editing cadence or
+prompt on a paused Schedule must not enable it accidentally.
+
+The update response is `202 Accepted`; the detail remains pending until a
+refresh observes the new state.
+
+### Lifecycle Actions
+
+| Action | Existing endpoint | UI rule |
+| --- | --- | --- |
+| Enable | `POST .../{scheduleId}:enable` | Show pending, then refresh |
+| Disable | `POST .../{scheduleId}:disable` | Show pending, then refresh |
+| Run now | `POST .../{scheduleId}:run-now` | Confirm external effects, then refresh |
+| Delete | `DELETE .../{scheduleId}` | Confirm, then remove only after refresh |
+
+All four return accepted receipts. The UI does not optimistically mutate
+authoritative Schedule state.
+
+There is no `Review and reauthorize` action because the Workflow Schedule API
+does not expose or require an authorization-preflight lifecycle.
+
+## API Compatibility
+
+The existing backend covers every required first-release interaction:
+
+| Screen or behavior | Backend support |
+| --- | --- |
+| Workflow Schedule list | Workflow-scoped `GET` collection |
+| Five-fire preview | Workflow-scoped `POST .../preview` |
+| Create | Workflow-scoped `POST`, `202 Accepted` |
+| Accepted observation | Collection/detail `GET` |
+| Detail | Workflow-scoped detail `GET` |
+| Edit | Workflow-scoped `PUT`, `202 Accepted` |
+| Enable/disable | Colon action routes |
+| Run now | Colon action route |
+| Delete | Workflow-scoped `DELETE`, `202 Accepted` |
+
+No backend contract gap remains for this design, so issue #3446 does not need
+an additional comment.
+
+## Standalone Review Artifacts
+
+The deterministic Schedule source contains exactly seven independently useful
+`1440x900` scenes:
+
+1. `schedule-workflows-list-modal.png`
+2. `schedule-workflow-editor-panel.png`
+3. `schedule-review.png`
+4. `schedule-creation-pending.png`
+5. `schedule-detail.png` (Overview)
+6. `schedule-history.png`
+7. `schedule-edit.png`
+
+There is no contact sheet, combined overview, Schedule-specific Activity
+frame, or authorization review image.
+
+## Verification Contract
+
+- The baseline verifier rejects Team/Member, generic Schedule API,
+ authorization-preflight, credential, grant, policy, and reauthorization
+ terms in the Schedule area.
+- The verifier requires all Workflow-scoped collection, detail, mutation, and
+ action routes.
+- The prototype must key fixtures by exact `scopeId + workflowId`.
+- The prototype must preserve observed `enabled` on update and refresh after
+ every accepted mutation.
+- The generator, Excalidraw, renderer, and seven PNGs must be deterministic and
+ source-linked.
+- Focused frontend checks are local; the full suite and build remain delegated
+ to GitHub CI.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-21-workflow-schedule-history-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-21-workflow-schedule-history-design.md
new file mode 100644
index 0000000000..c10e645b8e
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-21-workflow-schedule-history-design.md
@@ -0,0 +1,440 @@
+# Workflow Schedule Management and History Design Specification
+
+**Status:** Approved for implementation
+
+**Extends:**
+[Workflow Schedule vNext Design Specification](./2026-08-11-workflow-schedule-design.md)
+
+**Backend authority:** [Issue #3446](https://github.com/aevatarAI/aevatar/issues/3446),
+the Workflow-scoped Schedule facade, and the Schedule detail `recentFires`
+contract on `feature/integrate`.
+
+## Product Decision
+
+An existing Schedule has one management surface with two sibling views:
+
+```text
+Schedule management
+ Overview configuration and current observed state
+ History bounded recent Schedule attempts
+```
+
+This surface does not replace Activity. The product owns three related but
+different records:
+
+| Surface | Fact it owns | Source |
+| --- | --- | --- |
+| Schedule Overview | Recurrence configuration and current observed state | Workflow Schedule detail |
+| Schedule History | Bounded recent trigger attempts | Schedule detail `recentFires` |
+| Activity | Actual observed Workflow Runs and Run detail | Activity read model |
+
+A Schedule attempt can fail before a Workflow Run exists. For that reason,
+the UI must call `recentFires` **attempts**, must not promise complete Run
+history, and must not derive or guess a Run URL from command, correlation, or
+idempotency identifiers. The backend fire record's non-empty `runActorId` is
+the only authoritative per-attempt Run destination.
+
+## User Jobs
+
+The management surface supports four explicit jobs without mixing them into
+one long form:
+
+1. Understand whether the Schedule is active and when it will try next.
+2. Inspect or change its recurrence and optional Run input.
+3. Inspect recent Schedule attempts, including failures before Run creation.
+4. Continue to Activity when the user needs the resulting Workflow Runs.
+
+Raw backend diagnostics, accepted-command implementation notes, and resource
+identifiers are not primary user jobs.
+
+## Information Architecture
+
+### Entry points
+
+The same state model is used in both existing containers:
+
+- Workflows catalogue: `Schedules` opens the Workflow's Schedule management
+ modal.
+- Workflow editor: `Schedules` opens the right-side Schedule management panel
+ without hiding the canvas.
+
+Both containers start on the Schedule collection. Selecting a Schedule opens
+its Overview. Neither container navigates through Activity to manage a
+Schedule.
+
+### Schedule collection
+
+The modal and panel list state use one title, `Schedules for {workflowName}`,
+to identify both the collection and its owning Workflow. The list body does not
+repeat that collection title or add helper copy that only restates recurrence
+or ownership; its toolbar is reserved for collection actions.
+
+Each row shows only the facts needed to choose a Schedule:
+
+- Schedule name;
+- Enabled or Paused status;
+- human-readable recurrence and timezone;
+- next scheduled time, or `No upcoming attempt` when paused or unavailable.
+
+The row is the single selection target. Lifecycle and edit buttons do not
+repeat on every collection row. `New schedule` remains the primary collection
+action.
+
+### Selected Schedule header
+
+The back/close structure and resource context are stable across Overview,
+History, and Edit. Every selected-Schedule state uses the same one-line resource
+identity:
+
+```text
+[Back] Morning digest · Weekly Feedback Report [Close]
+```
+
+- Back returns to the Schedule collection inside the current container.
+- Close exits the entire Schedule management modal or panel.
+- The selected Schedule and owning Workflow stay on one line. Long resource
+ names truncate rather than increasing the header height, while accessible
+ labels and title text retain the full context.
+- Switching Overview and History changes only the content below the tab list;
+ it never replaces or rewrites the selected-Schedule header.
+- The active tab is the sole view label. `Schedule history` is not repeated in
+ the resource title.
+- There is no footer-level `Back to schedules` action.
+
+Back and Close are separate controls because they produce different state
+transitions. Their accessible names are `Back to schedules` and
+`Close schedules`.
+
+### Tabs
+
+Overview and History are a two-item tab list immediately below the header.
+Overview is selected every time the user newly selects a Schedule. Tab state
+may persist while that Schedule stays selected, but must not leak to a
+different Schedule.
+
+The tab list supports normal keyboard tab semantics:
+
+- `Tab` focuses the active tab;
+- Left/Right move between tabs;
+- `Home` and `End` select the first and last tab;
+- each tab owns one labelled tab panel.
+
+Edit is not a third tab. It is a temporary mutation mode entered from
+Overview and returns to Overview after Cancel or an observed successful
+update.
+
+## Overview
+
+### Primary content
+
+Overview is read-only and follows this order:
+
+1. Status and recurrence summary.
+2. Next scheduled time.
+3. Timezone.
+4. Last attempt.
+5. Total attempts and failed attempts.
+6. Run input, only when non-empty.
+7. Collapsed Advanced details containing the raw cron expression.
+
+The human-readable recurrence is primary, for example:
+
+```text
+Every weekday at 09:00
+```
+
+The raw value `0 9 * * 1-5` is diagnostic configuration and stays under
+`Advanced details`. Overview does not repeat a large blue Workflow context
+strip or an empty `No prompt` field.
+
+Expanding Advanced details must not require the user to interpret cron syntax.
+It presents a compact readable-to-technical hierarchy:
+
+| Detail | Presentation |
+| --- | --- |
+| Runs | Reuse the same localized human recurrence shown by Overview, such as `Every hour` or `Every weekday at 09:00` |
+| Technical format | Show the exact cron expression in monospace for support and debugging |
+
+For a valid expression outside the human builder's supported shapes, `Runs`
+honestly says `Custom schedule` and `Technical format` retains the exact value.
+Do not add a five-field cron tutorial, duplicate Timezone, or introduce a
+second cron interpretation path just for this disclosure.
+
+Dates and times use the current application locale and the Schedule timezone.
+They must not use an independent browser-locale formatter that can create a
+mixed-language surface.
+
+### Actions
+
+Overview exposes this hierarchy:
+
+```text
+[Run now] [Edit schedule] [More]
+ Pause / Enable
+ Delete schedule
+```
+
+- `Run now` is a direct command. Its accepted response is acknowledged by
+ Toast; observed attempt state still comes from Schedule detail.
+- `Edit schedule` enters Edit mode with the observed values.
+- `Pause` or `Enable` is inside More and reflects the observed current state.
+- `Delete schedule` is inside More, visually destructive, and requires a
+ confirmation dialog naming the Schedule.
+
+Pause/Enable and Delete must not have equal visual weight with Run now or Edit
+schedule. Accepted mutations must not optimistically rewrite authoritative
+Schedule state.
+
+### Pending state
+
+When a mutation returns `202 Accepted`, keep the selected Schedule visible,
+disable only conflicting mutations, and show a compact inline pending message
+or Toast. Refresh the exact Workflow-scoped list/detail until the observed
+state changes. Do not show backend implementation narration such as
+`Observed Schedule state only` or `Waiting for the Workflow list to confirm`.
+
+## History
+
+### Meaning
+
+The tab title is `History`; its content heading is `Recent attempts`. It shows
+the bounded `recentFires` returned by the exact Schedule detail endpoint in
+newest-first order.
+
+It is not a lifetime audit log and is not labelled `Runs`, `Run history`, or
+`All history`.
+
+### Row model
+
+History uses one compact table or table-like list. Each attempt exposes:
+
+| UI column | Backend source | Presentation |
+| --- | --- | --- |
+| Scheduled time | `scheduledFireAt` | Application locale in Schedule timezone |
+| Source | `manual` | `Manual` when true; otherwise `Scheduled` |
+| Schedule outcome | `error` + `runActorId` | `Failed to start`, `Run created`, or `Accepted` according to the mapping below |
+| Completed time | `completedAt` | Application locale in Schedule timezone |
+
+The table allocates most horizontal space to the two localized timestamp
+columns. `Source` remains compact, and `Schedule outcome` has enough room to
+name the observed dispatch state without implying a terminal Run result. The
+reference desktop proportions are `28 / 14 / 28 / 30` percent in the column
+order above.
+
+Outcome mapping is exact and does not infer execution state:
+
+| Condition | Label | Meaning |
+| --- | --- | --- |
+| `error` is non-empty | `Failed to start` | The Schedule attempt failed before it could reliably create a Run. |
+| `error` is empty and `runActorId` is non-empty | `Run created` | The backend returned an authoritative Run identity; Activity owns its current and terminal state. |
+| `error` is empty and `runActorId` is empty | `Accepted` | The attempt has no immediate Schedule error, but this record cannot identify an exact Run. |
+
+A failed row adds one concise message below its main row:
+
+```text
+The scheduled attempt could not start the Workflow.
+```
+
+For a manual attempt, substitute `manual` for `scheduled`. The raw backend
+error is available only through an expandable `Technical details` disclosure
+inside that row.
+
+Technical details may show the returned error text for support and debugging.
+The primary row must not expose:
+
+- service IDs or endpoint IDs;
+- actor IDs;
+- command or correlation IDs;
+- idempotency keys;
+- a guessed Run link when `runActorId` is empty;
+- a frontend-invented error category based on raw string matching.
+
+`Completed time` always renders only its localized timestamp. When `runActorId`
+is non-empty, one native anchor covers the full data row. The row receives a
+single hover and focus treatment, opens the existing Activity Run detail route
+in a new tab, and preserves `workflowId + schedule` in the query so Back returns
+to the same filtered Activity context. The anchor has a full accessible name
+that identifies the attempt and destination, supports keyboard focus and the
+browser link context menu, and does not rely on a scripted row click.
+
+Technical details remains an independently interactive disclosure above the
+row-link hit area. Any attempt with an empty `runActorId`, whether accepted or
+failed, remains non-interactive. It must never guess an identity or fall back to
+the Workflow + Schedule filtered Activity list.
+
+### Activity handoff
+
+The History tab provides one secondary link:
+
+```text
+View related runs in Activity
+```
+
+The link is deliberately about related Runs, not all attempts. It is a native
+link that opens a new tab and leaves the current Schedule container and History
+state intact. It opens:
+
+```text
+/scopes/:scopeId/workflow-activity-vnext/activity
+ ?workflowId=:workflowId
+ &schedule=:scheduleId
+```
+
+Activity owns the visible filter context and Run detail. The target view must
+make the Workflow and Schedule filters understandable to the user rather than
+silently filtering the table. Schedule is an attribution dimension, not a Run
+origin, so the handoff must not add `origin=schedule` or show Schedule in the
+Activity Source filter. No backend change is required because the Activity
+contract already accepts `workflowId` and `scheduleIds` filters.
+
+The filtered Activity request is an idempotent read. It retries one transient
+network, timeout, throttling, or server failure before showing the existing
+Activity error state. Authentication, authorization, bad-request, and response
+contract errors are not retried. The retry does not remove or weaken the
+Workflow and Schedule filters.
+
+## Edit
+
+Edit uses the existing human recurrence builder and preserves the selected
+Schedule's observed values:
+
+- name;
+- repeat preset plus time, or raw cron mode;
+- timezone;
+- optional Run input;
+- observed enabled state in the full update request.
+
+Raw cron mode and the repeat builder are mutually exclusive. When raw cron is
+selected, Repeat and Time are hidden and `Cron expression` is shown; timezone
+remains visible. Returning to the builder restores the human controls from a
+representable cron value.
+
+`Cancel` discards the draft and returns to Overview. `Save changes` sends the
+full replacement, shows accepted feedback, and returns to Overview while the
+observed detail refreshes. Edit contains no History table or lifecycle action.
+
+## State Model
+
+```mermaid
+stateDiagram-v2
+ [*] --> ScheduleList
+ ScheduleList --> Overview: Select Schedule
+ Overview --> History: Select History tab
+ History --> Overview: Select Overview tab
+ Overview --> Edit: Edit schedule
+ Edit --> Overview: Cancel
+ Edit --> Overview: Save accepted
+ History --> Activity: Open related runs in new tab
+ History --> RunDetail: Open attempt with runActorId in new tab
+ Overview --> ScheduleList: Back
+ History --> ScheduleList: Back
+ Edit --> ScheduleList: Back
+ ScheduleList --> [*]: Close
+ Overview --> [*]: Close
+ History --> [*]: Close
+ Edit --> [*]: Close
+```
+
+Using Back from Edit abandons the draft and returns to the collection. If the
+form is dirty, the container uses the product's normal unsaved-change
+confirmation before discarding it.
+
+## Loading, Empty, and Error States
+
+Schedule-detail loading and History loading are explicit states. They do not
+render stale collection summaries as if they were detail data.
+
+| State | Required presentation |
+| --- | --- |
+| Detail loading | Stable skeleton inside the selected-Schedule shell |
+| Detail request error | `Schedule couldn't be loaded` with Retry |
+| History loading | Stable row skeleton under `Recent attempts` |
+| Successful empty History | `No attempts yet` |
+| History request error | `History couldn't be loaded` with Retry |
+| Failed attempt | Normal History row with `Failed to start` outcome and Technical details |
+
+A failed attempt is business data, not a History-request error. Refresh and
+Retry preserve the selected Schedule and active tab.
+
+Request-error Alerts use one concise product description and keep Retry in the
+owning data region. The raw error is not the description: proxy text, endpoint
+paths, local addresses, and backend exception details stay behind a collapsed
+`Technical details` disclosure. Alert content must shrink within the modal or
+drawer; long diagnostics wrap or scroll locally, and the Retry action moves
+below the message at narrow widths instead of escaping or overlapping the
+container.
+
+## Backend Compatibility
+
+The design uses the existing Workflow-facing contract without additions:
+
+```text
+GET /api/scopes/{scopeId}/workflows/{workflowId}/schedules
+GET /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}
+PUT /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}
+POST /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}:enable
+POST /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}:disable
+POST /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}:run-now
+DELETE /api/scopes/{scopeId}/workflows/{workflowId}/schedules/{scheduleId}
+```
+
+The Schedule detail provides configuration, counters, current timestamps, and
+bounded `recentFires`, including an authoritative target Run actor identity when
+an attempt created a Run. The frontend adapter accepts the Workflow facade name
+`runActorId` and the scheduled-dispatch transport name `targetActorId`, then
+normalizes both to the UI's single `runActorId` field. Activity already supplies
+the filtered actual-Run surface and Run detail. There is no backend issue
+comment to add for this design.
+
+## Review Artifacts
+
+The deterministic Schedule source contains seven standalone `1440x900`
+scenes:
+
+1. `schedule-workflows-list-modal.png`
+2. `schedule-workflow-editor-panel.png`
+3. `schedule-review.png`
+4. `schedule-creation-pending.png`
+5. `schedule-detail.png` as the Overview state
+6. `schedule-history.png` as the History state
+7. `schedule-edit.png`
+
+There is no contact sheet and no Schedule-specific Activity frame. Overview
+and History are separate PNGs so each state can be reviewed at readable size.
+
+## Acceptance Criteria
+
+- Selecting a Schedule opens Overview, not an editable form.
+- Overview and History are sibling tabs under one stable selected-Schedule
+ header.
+- Back returns to the collection; Close exits the container.
+- Overview uses a human recurrence and hides raw cron under Advanced details.
+- Expanded Advanced details shows the human recurrence before the raw cron;
+ raw syntax is never the user's only local explanation of when it runs.
+- Empty optional Run input is omitted.
+- Run now and Edit schedule are direct actions; Pause/Enable and Delete live
+ under More.
+- History renders bounded attempts in a compact list and keeps raw failures
+ under Technical details.
+- An attempt with a non-empty backend `runActorId` opens that exact Activity
+ Run. Every attempt without one remains non-interactive; the row never changes
+ scope to the Schedule-wide Activity list or guesses an identity.
+- `View related runs in Activity` uses exact Workflow and Schedule filters.
+- Activity and Run links open in a new tab without closing or navigating the
+ current Schedule surface.
+- The same `Schedule · Workflow` title stays on one visual line across Overview
+ and History.
+- Schedule is absent from Activity Source filters and no handoff sends
+ `origin=schedule`.
+- A single transient Activity handoff request failure is retried once; 4xx
+ contract errors and response decoding errors are not retried.
+- History column widths prioritize localized timestamps and do not let the
+ Schedule outcome tag dominate common rows.
+- History has no Action column. A row with an authoritative `runActorId` is one
+ native whole-row link, while a row without one remains non-interactive.
+- Schedule outcome labels describe only the observed Schedule dispatch state;
+ Workflow Run success or failure remains visible in Activity Run detail.
+- Loading, successful empty, request error, and failed-attempt states remain
+ distinct.
+- All timestamps follow the application locale and Schedule timezone.
+- The implementation requires no backend contract change.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-08-25-workflow-create-result-unknown-design.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-25-workflow-create-result-unknown-design.md
new file mode 100644
index 0000000000..0f7c6c90e4
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-08-25-workflow-create-result-unknown-design.md
@@ -0,0 +1,79 @@
+# Workflow Create Result Unknown Design
+
+## Status
+
+Approved for the Workflow Activity vNext frontend on 2026-08-25. This design
+clarifies how the creation page reports an interrupted draft-create response;
+it does not change backend contracts or creation identity semantics.
+
+## Problem
+
+The draft-create command can be persisted even when its HTTP response times
+out. The current page maps every thrown error to `Workflow couldn't be
+created`, so it can tell the user to retry after the Workflow already exists.
+That feedback is stronger than the evidence available to the frontend and can
+lead to duplicate Workflows.
+
+## Product Contract
+
+- HTTP `408`, HTTP `504`, and transport interruption from
+ `createWorkflowDraft` mean the creation result is **unconfirmed**.
+- An unconfirmed result shows a warning telling the user to check Workflows
+ before trying again.
+- The page preserves every entered field and does not automatically resubmit
+ the create request.
+- The page refreshes the authoritative drafts query so the Workflow list can
+ reflect a create that completed despite the interrupted response.
+- The page never searches the refreshed list to infer a generated
+ `workflowId`, and never navigates to a Workflow selected by name, YAML,
+ filename, order, or timestamp.
+- Generation, YAML parsing, validation, and definite create business failures
+ continue to use the existing failure feedback.
+
+## Request-Stage Boundary
+
+Classification is scoped to the `createWorkflowDraft` call. A `504` from
+`authorWorkflow` or `parseYaml` is a definite pre-create failure because no
+draft-create request has been sent. Keeping the catch at the create boundary
+prevents status-code-only handling from changing those earlier stages.
+
+## User Flow
+
+```mermaid
+flowchart TD
+ A["User submits valid Workflow input"] --> B["Create scoped Workflow draft"]
+ B -->|"Materialized or accepted"| C["Open or observe the returned Workflow ID"]
+ B -->|"Definite business failure"| D["Show creation failed and keep input"]
+ B -->|"408, 504, or transport interruption"| E["Show result unconfirmed warning"]
+ E --> F["Refresh authoritative drafts query"]
+ F --> G["Keep input and let the user check Workflows"]
+```
+
+## Copy
+
+English: `Workflow creation couldn't be confirmed. Check Workflows before
+trying again.`
+
+Chinese: `无法确认工作流是否已创建,请先返回工作流列表检查,再决定是否重试。`
+
+The warning does not claim that creation succeeded, failed, or remains in
+progress.
+
+## Verification Contract
+
+A focused component test must prove that a draft-create timeout:
+
+1. emits the unconfirmed warning instead of the failure toast;
+2. preserves the submitted input;
+3. sends the create request exactly once;
+4. refreshes the drafts query;
+5. does not navigate to an inferred Workflow.
+
+The same test suite must retain coverage that failures before draft creation
+use the definite failure message.
+
+## Scope
+
+Frontend-only: Workflow Activity vNext creation component, focused tests,
+localized copy, and this specification. No backend code, API contract, proxy,
+or timeout value changes are included.
diff --git a/apps/aevatar-console-web/docs/superpowers/specs/2026-09-17-channel-bot-adoption.md b/apps/aevatar-console-web/docs/superpowers/specs/2026-09-17-channel-bot-adoption.md
new file mode 100644
index 0000000000..fd7ad9f450
--- /dev/null
+++ b/apps/aevatar-console-web/docs/superpowers/specs/2026-09-17-channel-bot-adoption.md
@@ -0,0 +1,129 @@
+# Channel Bot Adoption
+
+## Approved Design
+
+The approved Channels design is the
+[Channels - Add and bind Figma page](https://www.figma.com/design/FaVJx5IZeQX9jHcUi55ndB/?node-id=124-642).
+The existing Workflow Activity vNext shell, authentication, localization and
+real-API-only rules still apply. This supplement supersedes the previous
+Telegram token creation flow.
+
+- The top section is a static illustrated guide: add a bot in NyxID, return
+ and refresh/bind, then view the bound bot in the table. All three steps have
+ equal visual emphasis in every state. It is never a progress indicator.
+- The primary external action opens https://nyx.chrono-ai.fun/channel-bots.
+- Inventory contains both bound and unbound bots. Only an available unbound
+ bot exposes Bind; only a bound registration exposes Manage.
+- Bind uses the existing Skill and Services form controls. It contains no
+ platform credentials or editable channel name. Edit retains its existing
+ NyxID Label action and shares the Skill and Services controls.
+- Bind and Edit preselect and lock services whose exact slugs are `ornn-api`
+ and `chrono-llm-public`. Individual and bulk deselection retain both; other
+ services remain optional. Resolve their real UserService IDs from the current
+ authorized, active NyxID inventory, never from labels or slug-as-ID fallbacks.
+ Missing required access blocks submission and offers retry after correcting
+ NyxID access. Save uses an explicit allowlist containing both required IDs and
+ the optional selections. Editing an older NyxID-default configuration explains
+ that saving replaces defaults with these selected services; the UI does not
+ infer which services were included in those defaults.
+- Skill creation opens https://ornn.chrono-ai.fun/skills/new/generate in a
+ new tab. Its tooltip explains the return-and-refresh action. The refresh icon
+ sits at the right of the popup's Available in Ornn heading. Refresh preserves
+ the selected skill and keeps the dropdown open.
+- Match Figma's DM Sans typography, continuous white surface, restrained blue
+ actions, 60px guide icons and neutral connecting arrows. At a 1440px viewport,
+ the inventory content is 1080px wide; the table has a 46px header and 88px
+ rows, horizontal separators and no enclosing card border.
+- The binding form identifies the bot by label and platform and shows Not
+ bound. The Skill popup contains a search input, skill names and descriptions,
+ and the external creation footer. Keyboard users can move from search into
+ the options with Arrow Down and dismiss the popup with Escape.
+
+## API Contract
+
+[Backend issue #3652](https://github.com/aevatarAI/aevatar/issues/3652)
+defines the required deployment contract:
+
+| Action | Endpoint | Public inputs |
+| --- | --- | --- |
+| Inventory | GET /api/channels/registrations | None; caller-owned inventory |
+| Bind | POST /api/channels/registrations | nyx_channel_bot_id, skill_name, authorization_mode, service_ids |
+| Detail | GET /api/channels/registrations/{registrationId} | Opaque registration identity |
+| Edit | POST /api/channels/registrations/{registrationId} | skill_name, authorization_mode, service_ids |
+
+The inventory key is the NyxID bot ID. A local registration ID may be null for
+unbound bots. Neither API input nor response decoding requires a scope key;
+ownership is server resolved. Query caches remain isolated by the current
+route scope. A missing or malformed inventory is an error, never an empty list.
+Detail and edit take configuration and version evidence from the exact detail,
+then match its registration ID, bot ID and platform to inventory for the current
+NyxID Label and inbound status. The detail's placeholder label is never used as
+the editable bot name.
+
+A successful write receipt is admission only. Bind automatically observes the
+inventory for the receipt's registration ID and the original bot ID with a
+bound state and matching configuration. Once confirmed, it opens the existing
+Manage/detail page for that observed registration ID, showing the bound bot
+and its configuration. Acceptance alone does not navigate, and the NyxID bot
+ID is never substituted for the registration ID. Edit observes the exact detail
+until a newer authoritative state version contains the requested configuration.
+Observation is bounded to 30 seconds. Delay or read failure preserves the
+receipt and offers a return to Channels without repeating the write. An
+uncertain transport result also prevents a duplicate write from this form.
+
+Skill search uses the current NyxID session through
+`GET /api/v1/proxy/s/ornn-api/api/v1/skill-search` on the configured NyxID
+authority, with `scope=private&mode=keyword&q=...&limit=50`.
+Private scope includes owned and shared skills. The selector supports server
+search and opaque cursor pagination. No frontend API key or separate token
+store is introduced. Ornn's route schema confirms that `cursor` and `limit`
+are supported.
+
+## Deployment And Verification
+
+The #3652 contract was merged into `feature/integrate` by
+[backend PR #3653](https://github.com/aevatarAI/aevatar/pull/3653).
+During live verification on 2026-09-17, the remote deployment initially returned
+HTTP 200 with the old inventory shape, which the new decoder rejected. The UI
+now distinguishes an unavailable binding contract from a generic load failure.
+It never infers a binding state from legacy fields or treats failed decoding as
+an empty inventory.
+
+The remote deployment subsequently exposed the new exact-detail GET/POST
+routes and the new inventory shape. Verification against that real deployment
+displayed nine bots (eight bound and one unbound), loaded NyxID services and
+Ornn skills, and confirmed server search, keyboard selection and retention
+after refreshing skills. Desktop (1440px) and mobile (375px) screenshots were
+inspected in the existing Chrome session. No production bot or service grant
+was mutated during this verification; write confirmation and uncertain-write
+behavior are covered by focused tests.
+
+A subsequent user-triggered Bind returned `400 insecure_webhook_base_url`.
+The captured request contained exactly the documented bot ID, skill name,
+authorization mode and service IDs. In the referenced backend, registration
+checks the resolved callback address before calling the adoption facade.
+The UI must attribute this rejection to server configuration and preserve the
+user's choices instead of suggesting that the skill or grants are incorrect.
+
+The deployed host must explicitly configure its public HTTPS callback origin
+when TLS terminates before the application, for example:
+
+```text
+Aevatar__NyxIdRelay__WebhookBaseUrl=https://
+```
+
+`MainnetHostBuilderExtensions` reads `NyxIdRelay` first and overlays
+`Aevatar:NyxIdRelay`; operators must check that effective configuration.
+The host appends `/api/webhooks/nyxid-relay` to the base. Do not send this value
+in adoption JSON or substitute the browser's localhost origin. Correcting the
+UI error alone does not resolve the deployment configuration or establish a
+successful live binding. The backend deployment blocker is tracked in
+[issue #3656](https://github.com/aevatarAI/aevatar/issues/3656), assigned to
+`louis4li`.
+
+Focused tests cover inventory/null identities, request shapes and safe
+decoding, private Ornn proxy authorization/pagination, static guidance,
+binding/availability actions, refresh retention, exact detail/removal,
+admission observation, newer edit state and duplicate-write prevention.
+The removed token and runtime-config tests describe retired contracts.
+Full frontend tests, typecheck and production build belong to GitHub CI.
diff --git a/apps/aevatar-console-web/docs/testing-policy.md b/apps/aevatar-console-web/docs/testing-policy.md
index 4855adb0c5..be1bb24bf7 100644
--- a/apps/aevatar-console-web/docs/testing-policy.md
+++ b/apps/aevatar-console-web/docs/testing-policy.md
@@ -364,6 +364,14 @@ distinct user-facing risk justifies it.
## Coverage and Reporting
+GitHub CI runs the complete frontend test inventory in four Jest shards on
+separate runners. Each shard keeps serial execution within its own process.
+The existing `console-web` check requires every shard to succeed before running
+the full typecheck, production build, and workflow canvas benchmark; a failed,
+cancelled or skipped shard fails that check. Each test shard has a 35-minute
+budget, while `console-web` retains its 45-minute budget. Local development
+continues to use the focused commands above.
+
- Coverage is diagnostic evidence, not a reason to create low-value tests or
exercise generated code. Do not organize test files as generic coverage
buckets.
diff --git a/apps/aevatar-console-web/package.json b/apps/aevatar-console-web/package.json
index 153e698c77..eb46f4bbea 100644
--- a/apps/aevatar-console-web/package.json
+++ b/apps/aevatar-console-web/package.json
@@ -6,6 +6,8 @@
"description": "Aevatar console shell built on Ant Design Pro",
"scripts": {
"analyze": "cross-env ANALYZE=1 max build",
+ "benchmark:workflow-canvas": "playwright test --config=playwright.performance.config.ts",
+ "benchmark:workflow-canvas:serve": "cross-env UMI_ENV=production AEVATAR_WORKFLOW_CANVAS_BENCHMARK=1 max preview --port 4174",
"build": "max build",
"build:web": "npm run build",
"dev": "npm run start:dev",
@@ -29,12 +31,14 @@
"@aevatar-react-sdk/types": "0.5.0",
"@ant-design/icons": "^6.1.0",
"@ant-design/pro-components": "3.1.2-0",
+ "@fontsource-variable/dm-sans": "5.3.0",
"@monaco-editor/react": "^4.7.0",
"@tanstack/react-query": "^5.90.21",
"@xyflow/react": "^12.10.1",
"antd": "^6.2.2",
"antd-style": "^4.1.0",
"clsx": "^2.1.1",
+ "core-js-pure": "3.49.0",
"dayjs": "^1.11.19",
"monaco-editor": "^0.55.1",
"react": "^19.2.4",
@@ -47,6 +51,7 @@
"@biomejs/biome": "^2.1.1",
"@commitlint/cli": "^20.1.0",
"@commitlint/config-conventional": "^20.0.0",
+ "@playwright/test": "1.60.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
@@ -66,6 +71,7 @@
"jest-environment-jsdom": "^29.7.0",
"lint-staged": "^16.1.2",
"mockjs": "^1.1.0",
+ "react-intl": "3.12.1",
"ts-node": "^10.9.2",
"typescript": "^5.6.3",
"umi-presets-pro": "^2.0.3",
diff --git a/apps/aevatar-console-web/performance/workflowCanvas.performance.spec.ts b/apps/aevatar-console-web/performance/workflowCanvas.performance.spec.ts
new file mode 100644
index 0000000000..329693068a
--- /dev/null
+++ b/apps/aevatar-console-web/performance/workflowCanvas.performance.spec.ts
@@ -0,0 +1,783 @@
+import { mkdir, writeFile } from 'node:fs/promises';
+import os from 'node:os';
+import path from 'node:path';
+import { expect, type Page, test } from '@playwright/test';
+import {
+ assertWorkflowCanvasBenchmarkResult,
+ createWorkflowCanvasBenchmarkProgress,
+ WORKFLOW_CANVAS_BENCHMARK_GRAPH_SIZES,
+ WORKFLOW_CANVAS_BENCHMARK_SCENARIOS,
+ type WorkflowCanvasBenchmarkMeasurement,
+ type WorkflowCanvasBenchmarkPolicy,
+ type WorkflowCanvasBenchmarkReactCommit,
+ type WorkflowCanvasBenchmarkResult,
+ type WorkflowCanvasBenchmarkScenario,
+} from '../src/pages/workflow-canvas-benchmark/benchmarkGraph';
+
+type PolicyCase = {
+ readonly id: string;
+ readonly policy: WorkflowCanvasBenchmarkPolicy;
+};
+
+type ReactProfileArtifact = {
+ readonly commits: readonly WorkflowCanvasBenchmarkReactCommit[];
+ readonly graphNodes: number;
+ readonly policy: WorkflowCanvasBenchmarkPolicy;
+ readonly scenario: WorkflowCanvasBenchmarkScenario;
+};
+
+type InteractableNodeTarget = {
+ readonly id: string;
+ readonly point: {
+ readonly x: number;
+ readonly y: number;
+ };
+};
+
+type InteractableNodePair = readonly [
+ InteractableNodeTarget,
+ InteractableNodeTarget,
+];
+
+const BENCHMARK_POLICIES: readonly PolicyCase[] = [
+ {
+ id: 'full-graph-with-minimap',
+ policy: { minimap: true, visibleElementsOnly: false },
+ },
+ {
+ id: 'full-graph-without-minimap',
+ policy: { minimap: false, visibleElementsOnly: false },
+ },
+ {
+ id: 'visible-elements-with-minimap',
+ policy: { minimap: true, visibleElementsOnly: true },
+ },
+ {
+ id: 'visible-elements-without-minimap',
+ policy: { minimap: false, visibleElementsOnly: true },
+ },
+];
+const EXPECTED_RESULT_COUNT =
+ WORKFLOW_CANVAS_BENCHMARK_GRAPH_SIZES.length *
+ BENCHMARK_POLICIES.length *
+ WORKFLOW_CANVAS_BENCHMARK_SCENARIOS.length;
+const ARTIFACT_DIRECTORY = path.resolve(
+ __dirname,
+ '../artifacts/workflow-canvas-benchmark',
+);
+const COMPACT_ZOOM_THRESHOLD = 0.48;
+const CONTROL_ZOOM_FACTOR = 1.2;
+const POSITION_CHANGE_TOLERANCE = 1;
+const ZOOM_CHANGE_TOLERANCE = 0.0001;
+const EXPECTED_CHANGED_NODE_REFERENCES: Readonly<
+ Record number>
+> = {
+ drag: () => 1,
+ 'initial-load': (graphNodes) => graphNodes,
+ pan: () => 0,
+ selection: () => 2,
+ 'status-update': () => 1,
+ 'topology-add': () => 2,
+ 'zoom-same-band': () => 0,
+ 'zoom-threshold': () => 0,
+};
+
+async function waitForAnimationFrames(page: Page, frameCount = 2) {
+ await page.evaluate(async (frames) => {
+ for (let index = 0; index < frames; index += 1) {
+ await new Promise((resolve) =>
+ requestAnimationFrame(() => resolve()),
+ );
+ }
+ }, frameCount);
+}
+
+async function readViewport(page: Page) {
+ return page.locator('.react-flow__viewport').evaluate((element) => {
+ const matrix = new DOMMatrix(getComputedStyle(element).transform);
+ return { x: matrix.e, y: matrix.f, zoom: matrix.a };
+ });
+}
+
+async function waitForInitialFit(page: Page) {
+ await page.locator('[data-benchmark-ready="true"]').waitFor();
+ await page.waitForFunction(async () => {
+ if (
+ !window.__AEVATAR_WORKFLOW_CANVAS_BENCHMARK__ ||
+ !document.querySelector('.react-flow__node')
+ ) {
+ return false;
+ }
+
+ const transforms: string[] = [];
+ for (let index = 0; index < 4; index += 1) {
+ await new Promise((resolve) =>
+ requestAnimationFrame(() => resolve()),
+ );
+ const viewport = document.querySelector(
+ '.react-flow__viewport',
+ );
+ if (!viewport) {
+ return false;
+ }
+ transforms.push(getComputedStyle(viewport).transform);
+ }
+
+ const matrix = new DOMMatrix(transforms.at(-1));
+ return (
+ Math.abs(matrix.a - 1) > 0.001 &&
+ transforms.slice(-3).every((transform) => transform === transforms.at(-1))
+ );
+ });
+}
+
+async function findPanePanGesture(page: Page): Promise<{
+ readonly end: { readonly x: number; readonly y: number };
+ readonly minimumHorizontalDistance: number;
+ readonly start: { readonly x: number; readonly y: number };
+}> {
+ return page.evaluate(() => {
+ const pane = document.querySelector('.react-flow__pane');
+ if (!pane) {
+ throw new Error('React Flow pane is unavailable');
+ }
+ const bounds = pane.getBoundingClientRect();
+ const fractions = [0.12, 0.22, 0.32, 0.42, 0.58, 0.68, 0.78, 0.88];
+ const candidates: Array<{ x: number; y: number }> = [];
+ for (const vertical of fractions) {
+ for (const horizontal of fractions) {
+ const point = {
+ x: bounds.left + bounds.width * horizontal,
+ y: bounds.top + bounds.height * vertical,
+ };
+ if (
+ document
+ .elementFromPoint(point.x, point.y)
+ ?.classList.contains('react-flow__pane')
+ ) {
+ candidates.push(point);
+ }
+ }
+ }
+
+ let gesture:
+ | {
+ end: { x: number; y: number };
+ horizontalDistance: number;
+ start: { x: number; y: number };
+ }
+ | undefined;
+ for (let startIndex = 0; startIndex < candidates.length; startIndex += 1) {
+ const start = candidates[startIndex];
+ for (
+ let endIndex = startIndex + 1;
+ endIndex < candidates.length;
+ endIndex += 1
+ ) {
+ const end = candidates[endIndex];
+ const horizontalDistance = Math.abs(end.x - start.x);
+ if (!gesture || horizontalDistance > gesture.horizontalDistance) {
+ gesture = { end, horizontalDistance, start };
+ }
+ }
+ }
+
+ const minimumHorizontalDistance = bounds.width * 0.45;
+ if (!gesture || gesture.horizontalDistance < minimumHorizontalDistance) {
+ throw new Error(
+ `No sufficiently long unobstructed horizontal pane gesture is available; found ${gesture?.horizontalDistance ?? 0}px`,
+ );
+ }
+ return {
+ end: gesture.end,
+ minimumHorizontalDistance,
+ start: gesture.start,
+ };
+ });
+}
+
+async function readMountedNodeIds(page: Page): Promise {
+ return page.evaluate(() => {
+ const ids = Array.from(
+ document.querySelectorAll('.react-flow__node'),
+ (node) => node.dataset.id,
+ );
+ if (ids.some((id) => !id)) {
+ throw new Error('A mounted workflow node is missing its data-id');
+ }
+ if (new Set(ids).size !== ids.length) {
+ throw new Error('Mounted workflow node IDs are not unique');
+ }
+ return (ids as string[]).sort();
+ });
+}
+
+async function findInteractableNodePair(
+ page: Page,
+): Promise {
+ return page.evaluate(() => {
+ const pane = document.querySelector('.react-flow__pane');
+ if (!pane) {
+ throw new Error('React Flow pane is unavailable');
+ }
+
+ const paneBounds = pane.getBoundingClientRect();
+ // Keep gestures away from clipped nodes and React Flow's edge auto-pan zone.
+ const gestureMargin = 80;
+ const seenIds = new Set();
+ const targets: InteractableNodeTarget[] = [];
+ const nodes = document.querySelectorAll('.react-flow__node');
+ for (const node of nodes) {
+ const id = node.dataset.id;
+ if (!id || seenIds.has(id) || !pane.contains(node)) {
+ continue;
+ }
+
+ const nodeBounds = node.getBoundingClientRect();
+ const intersection = {
+ bottom: Math.min(nodeBounds.bottom, paneBounds.bottom),
+ left: Math.max(nodeBounds.left, paneBounds.left),
+ right: Math.min(nodeBounds.right, paneBounds.right),
+ top: Math.max(nodeBounds.top, paneBounds.top),
+ };
+ if (
+ intersection.right <= intersection.left ||
+ intersection.bottom <= intersection.top ||
+ nodeBounds.left < paneBounds.left + gestureMargin ||
+ nodeBounds.right > paneBounds.right - gestureMargin ||
+ nodeBounds.top < paneBounds.top + gestureMargin ||
+ nodeBounds.bottom > paneBounds.bottom - gestureMargin
+ ) {
+ continue;
+ }
+
+ const point = {
+ x: (intersection.left + intersection.right) / 2,
+ y: (intersection.top + intersection.bottom) / 2,
+ };
+ const hitTarget = document.elementFromPoint(point.x, point.y);
+ if (
+ !hitTarget ||
+ !node.contains(hitTarget) ||
+ hitTarget.closest('.nodrag, .react-flow__handle')
+ ) {
+ continue;
+ }
+
+ seenIds.add(id);
+ targets.push({ id, point });
+ if (targets.length === 2) {
+ return [targets[0], targets[1]] as InteractableNodePair;
+ }
+ }
+
+ throw new Error(
+ `Expected two distinct interactable workflow nodes, found ${targets.length}`,
+ );
+ });
+}
+
+async function readNodePosition(page: Page, nodeId: string) {
+ return page.evaluate((expectedNodeId) => {
+ const node = Array.from(
+ document.querySelectorAll('.react-flow__node'),
+ ).find((candidate) => candidate.dataset.id === expectedNodeId);
+ if (!node) {
+ throw new Error(`Workflow node ${expectedNodeId} is unavailable`);
+ }
+ const transform = new DOMMatrix(getComputedStyle(node).transform);
+ return { x: transform.e, y: transform.f };
+ }, nodeId);
+}
+
+async function readNodeSelected(page: Page, nodeId: string) {
+ return page.evaluate((expectedNodeId) => {
+ const node = Array.from(
+ document.querySelectorAll('.react-flow__node'),
+ ).find((candidate) => candidate.dataset.id === expectedNodeId);
+ if (!node) {
+ throw new Error(`Workflow node ${expectedNodeId} is unavailable`);
+ }
+ return (
+ node.classList.contains('selected') ||
+ node.getAttribute('aria-selected') === 'true'
+ );
+ }, nodeId);
+}
+
+async function waitForSelectionState(
+ page: Page,
+ selectedNodeId: string,
+ clearedNodeIds: readonly string[] = [],
+) {
+ await page.waitForFunction(
+ ({ clearedIds, selectedId }) => {
+ const nodes = Array.from(
+ document.querySelectorAll('.react-flow__node'),
+ );
+ const isSelected = (nodeId: string) => {
+ const node = nodes.find((candidate) => candidate.dataset.id === nodeId);
+ return Boolean(
+ node &&
+ (node.classList.contains('selected') ||
+ node.getAttribute('aria-selected') === 'true'),
+ );
+ };
+ return (
+ isSelected(selectedId) && clearedIds.every((id) => !isSelected(id))
+ );
+ },
+ { clearedIds: clearedNodeIds, selectedId: selectedNodeId },
+ );
+ expect(await readNodeSelected(page, selectedNodeId)).toBe(true);
+ for (const clearedNodeId of clearedNodeIds) {
+ expect(await readNodeSelected(page, clearedNodeId)).toBe(false);
+ }
+}
+
+async function prepareSelectionScenario(
+ page: Page,
+): Promise {
+ const targets = await findInteractableNodePair(page);
+ const [initialTarget] = targets;
+ await page.mouse.click(initialTarget.point.x, initialTarget.point.y);
+ await waitForSelectionState(page, initialTarget.id);
+ return targets;
+}
+
+async function runSelection(page: Page, targets: InteractableNodePair) {
+ const [initialTarget, nextTarget] = targets;
+ await page.mouse.click(nextTarget.point.x, nextTarget.point.y);
+ await waitForSelectionState(page, nextTarget.id, [initialTarget.id]);
+}
+
+async function runDrag(page: Page) {
+ const [target] = await findInteractableNodePair(page);
+ const before = await readNodePosition(page, target.id);
+ await page.mouse.move(target.point.x, target.point.y);
+ await page.mouse.down();
+ await page.mouse.move(target.point.x + 48, target.point.y + 32, { steps: 8 });
+ await page.mouse.up();
+ await page.waitForFunction(
+ ({ nodeId, position, tolerance }) => {
+ const node = Array.from(
+ document.querySelectorAll('.react-flow__node'),
+ ).find((candidate) => candidate.dataset.id === nodeId);
+ if (!node) {
+ return false;
+ }
+ const transform = new DOMMatrix(getComputedStyle(node).transform);
+ return (
+ Math.abs(transform.e - position.x) > tolerance ||
+ Math.abs(transform.f - position.y) > tolerance
+ );
+ },
+ {
+ nodeId: target.id,
+ position: before,
+ tolerance: POSITION_CHANGE_TOLERANCE,
+ },
+ );
+ const after = await readNodePosition(page, target.id);
+ expect(
+ Math.max(Math.abs(after.x - before.x), Math.abs(after.y - before.y)),
+ ).toBeGreaterThan(POSITION_CHANGE_TOLERANCE);
+}
+
+async function runPan(page: Page, visibleElementsOnly: boolean) {
+ const before = await readViewport(page);
+ const beforeMountedNodeIds = await readMountedNodeIds(page);
+ const gesture = await findPanePanGesture(page);
+ await page.mouse.move(gesture.start.x, gesture.start.y);
+ await page.mouse.down();
+ await page.mouse.move(gesture.end.x, gesture.end.y, { steps: 16 });
+ await page.mouse.up();
+ await waitForAnimationFrames(page);
+ if (visibleElementsOnly) {
+ await page.waitForFunction((previousIds) => {
+ const currentIds = Array.from(
+ document.querySelectorAll('.react-flow__node'),
+ (node) => node.dataset.id,
+ ).filter((id): id is string => Boolean(id));
+ const currentIdSet = new Set(currentIds);
+ const previousIdSet = new Set(previousIds);
+ return (
+ previousIds.some((id) => !currentIdSet.has(id)) &&
+ currentIds.some((id) => !previousIdSet.has(id))
+ );
+ }, beforeMountedNodeIds);
+ }
+ const after = await readViewport(page);
+ const afterMountedNodeIds = await readMountedNodeIds(page);
+ expect(Math.abs(after.x - before.x)).toBeGreaterThan(
+ gesture.minimumHorizontalDistance * 0.8,
+ );
+ expect(Math.abs(after.zoom - before.zoom)).toBeLessThanOrEqual(
+ ZOOM_CHANGE_TOLERANCE,
+ );
+ if (visibleElementsOnly) {
+ const beforeMountedNodeIdSet = new Set(beforeMountedNodeIds);
+ const afterMountedNodeIdSet = new Set(afterMountedNodeIds);
+ expect(
+ beforeMountedNodeIds.filter((id) => !afterMountedNodeIdSet.has(id))
+ .length,
+ ).toBeGreaterThan(0);
+ expect(
+ afterMountedNodeIds.filter((id) => !beforeMountedNodeIdSet.has(id))
+ .length,
+ ).toBeGreaterThan(0);
+ }
+}
+
+async function clickZoomControl(page: Page, direction: 'in' | 'out') {
+ const before = await readViewport(page);
+ await page.locator(`.react-flow__controls-zoom${direction}`).click();
+ await page.waitForFunction(
+ ({ direction: expectedDirection, zoom }) => {
+ const viewport = document.querySelector(
+ '.react-flow__viewport',
+ );
+ if (!viewport) {
+ return false;
+ }
+ const currentZoom = new DOMMatrix(getComputedStyle(viewport).transform).a;
+ return expectedDirection === 'in'
+ ? currentZoom > zoom + 0.0001
+ : currentZoom < zoom - 0.0001;
+ },
+ { direction, zoom: before.zoom },
+ );
+ await waitForAnimationFrames(page);
+}
+
+async function runZoomSameBand(page: Page) {
+ const before = await readViewport(page);
+ const candidates = [
+ { direction: 'in', projectedZoom: before.zoom * CONTROL_ZOOM_FACTOR },
+ { direction: 'out', projectedZoom: before.zoom / CONTROL_ZOOM_FACTOR },
+ ] as const;
+ const candidate = candidates.find(
+ ({ projectedZoom }) =>
+ projectedZoom > before.zoom + ZOOM_CHANGE_TOLERANCE &&
+ projectedZoom < COMPACT_ZOOM_THRESHOLD ===
+ before.zoom < COMPACT_ZOOM_THRESHOLD,
+ );
+ if (!candidate) {
+ throw new Error(
+ `No non-expanding control zoom remains in the current compact band from ${before.zoom}`,
+ );
+ }
+ await clickZoomControl(page, candidate.direction);
+ const after = await readViewport(page);
+ expect(after.zoom).toBeGreaterThan(before.zoom + ZOOM_CHANGE_TOLERANCE);
+ expect(after.zoom < COMPACT_ZOOM_THRESHOLD).toBe(
+ before.zoom < COMPACT_ZOOM_THRESHOLD,
+ );
+}
+
+async function runZoomThreshold(page: Page) {
+ const before = await readViewport(page);
+ const direction = before.zoom < COMPACT_ZOOM_THRESHOLD ? 'in' : 'out';
+ for (let attempt = 0; attempt < 12; attempt += 1) {
+ const current = await readViewport(page);
+ if (
+ current.zoom < COMPACT_ZOOM_THRESHOLD !==
+ before.zoom < COMPACT_ZOOM_THRESHOLD
+ ) {
+ expect(current.zoom < COMPACT_ZOOM_THRESHOLD).not.toBe(
+ before.zoom < COMPACT_ZOOM_THRESHOLD,
+ );
+ return;
+ }
+ await clickZoomControl(page, direction);
+ }
+ throw new Error('Zoom did not cross the Studio compact threshold');
+}
+
+async function runScenarioAction(
+ page: Page,
+ scenario: Exclude,
+ visibleElementsOnly: boolean,
+ selectionTargets?: InteractableNodePair,
+) {
+ switch (scenario) {
+ case 'selection': {
+ if (!selectionTargets) {
+ throw new Error('Selection scenario targets are unavailable');
+ }
+ await runSelection(page, selectionTargets);
+ break;
+ }
+ case 'drag':
+ await runDrag(page);
+ break;
+ case 'pan':
+ await runPan(page, visibleElementsOnly);
+ break;
+ case 'zoom-same-band':
+ await runZoomSameBand(page);
+ break;
+ case 'zoom-threshold':
+ await runZoomThreshold(page);
+ break;
+ case 'status-update':
+ case 'topology-add':
+ await page.evaluate(
+ (stateScenario) =>
+ window.__AEVATAR_WORKFLOW_CANVAS_BENCHMARK__?.runStateScenario(
+ stateScenario,
+ ),
+ scenario,
+ );
+ break;
+ }
+ await waitForAnimationFrames(page);
+}
+
+function assertSemanticResult(
+ result: WorkflowCanvasBenchmarkResult,
+ measurement: WorkflowCanvasBenchmarkMeasurement,
+) {
+ // ResizeObserver changes measured bounds on compact/full transitions and
+ // when virtualized nodes enter view. Keep reporting every changed reference,
+ // while checking the interaction contract independently of measurement-only updates.
+ expect(
+ measurement.changedNodeReferencesExcludingMeasurements,
+ `${result.scenario}: ${result.graph.nodes} nodes, ${JSON.stringify(result.policy)}`,
+ ).toBe(EXPECTED_CHANGED_NODE_REFERENCES[result.scenario](result.graph.nodes));
+ expect(result.changedNodeReferences).toBeGreaterThanOrEqual(
+ measurement.changedNodeReferencesExcludingMeasurements,
+ );
+ if (result.scenario === 'initial-load') {
+ expect(result.renderedNodeCount).toBeGreaterThan(0);
+ if (!result.policy.visibleElementsOnly) {
+ expect(result.renderedNodeCount).toBeGreaterThanOrEqual(
+ result.graph.nodes,
+ );
+ }
+ }
+ if (result.scenario === 'zoom-same-band') {
+ expect(result.renderedNodeCount).toBe(0);
+ }
+ if (result.scenario === 'zoom-threshold') {
+ expect(result.renderedNodeCount).toBeGreaterThan(0);
+ }
+}
+
+async function appendResult(
+ page: Page,
+ scenario: WorkflowCanvasBenchmarkScenario,
+ startedAt: number,
+): Promise<{
+ readonly profile?: ReactProfileArtifact;
+ readonly result: WorkflowCanvasBenchmarkResult;
+}> {
+ const captured = await page.evaluate(() => {
+ const api = window.__AEVATAR_WORKFLOW_CANVAS_BENCHMARK__;
+ if (!api) {
+ throw new Error('Workflow canvas benchmark API is unavailable');
+ }
+ return {
+ config: api.config,
+ endedAt: performance.now(),
+ measurement: api.captureMeasurement(),
+ };
+ });
+ const result = assertWorkflowCanvasBenchmarkResult({
+ browser: captured.config.browser,
+ buildMode: 'production',
+ changedNodeReferences: captured.measurement.changedNodeReferences,
+ durationMs: captured.endedAt - startedAt,
+ graph: captured.config.graph,
+ longTasks: captured.measurement.longTasks,
+ policy: captured.config.policy,
+ renderedNodeCount: captured.measurement.renderedNodeCount,
+ scenario,
+ usedHeapBytes: captured.measurement.usedHeapBytes,
+ });
+ assertSemanticResult(result, captured.measurement);
+ const storedResult = await page.evaluate((value) => {
+ const api = window.__AEVATAR_WORKFLOW_CANVAS_BENCHMARK__;
+ if (!api) {
+ throw new Error('Workflow canvas benchmark API is unavailable');
+ }
+ return api.appendResult(value);
+ }, result);
+ expect(storedResult).toEqual(result);
+
+ return {
+ profile: captured.measurement.reactCommits?.length
+ ? {
+ commits: captured.measurement.reactCommits,
+ graphNodes: result.graph.nodes,
+ policy: result.policy,
+ scenario,
+ }
+ : undefined,
+ result,
+ };
+}
+
+function formatHeap(value: number | null): string {
+ return value === null ? 'unavailable' : String(value);
+}
+
+function policyId(policy: WorkflowCanvasBenchmarkPolicy): string {
+ return (
+ BENCHMARK_POLICIES.find(
+ (candidate) =>
+ candidate.policy.minimap === policy.minimap &&
+ candidate.policy.visibleElementsOnly === policy.visibleElementsOnly,
+ )?.id ?? 'unknown'
+ );
+}
+
+async function writeArtifacts({
+ browserVersion,
+ profiles,
+ results,
+ userAgent,
+}: {
+ readonly browserVersion: string;
+ readonly profiles: readonly ReactProfileArtifact[];
+ readonly results: readonly WorkflowCanvasBenchmarkResult[];
+ readonly userAgent: string | null;
+}) {
+ const cpus = os.cpus();
+ const progress = createWorkflowCanvasBenchmarkProgress(
+ results.length,
+ EXPECTED_RESULT_COUNT,
+ );
+ const envelope = {
+ build: {
+ commit: process.env.GITHUB_SHA || null,
+ mode: 'production',
+ },
+ browser: {
+ channel: 'chrome',
+ userAgent,
+ version: browserVersion,
+ },
+ complete: progress.complete,
+ generatedAt: new Date().toISOString(),
+ profiles: profiles.length ? profiles : undefined,
+ results,
+ runner: {
+ architecture: os.arch(),
+ ci: process.env.CI === 'true',
+ cpuModel: cpus[0]?.model ?? null,
+ logicalCpuCount: cpus.length,
+ operatingSystem: `${os.platform()} ${os.release()}`,
+ totalMemoryBytes: os.totalmem(),
+ },
+ schemaVersion: 1,
+ };
+ const profileSummary = profiles.length
+ ? `${profiles.reduce((sum, profile) => sum + profile.commits.length, 0)} React commits were exposed by the runtime.`
+ : 'React commit profiling was unavailable in this production runtime.';
+ const markdown = [
+ '# Workflow Canvas Benchmark',
+ '',
+ `- Build: production${envelope.build.commit ? ` (${envelope.build.commit})` : ''}`,
+ ...progress.markdownLines,
+ `- Browser: system Chrome ${browserVersion}`,
+ `- User agent: ${userAgent ?? 'unavailable'}`,
+ `- Runner: ${envelope.runner.operatingSystem}, ${envelope.runner.architecture}, ${envelope.runner.logicalCpuCount} logical CPUs, ${envelope.runner.totalMemoryBytes} bytes memory`,
+ `- React profiling: ${profileSummary}`,
+ '',
+ '| Nodes | Policy | Scenario | Duration (ms) | Long tasks | Rendered nodes | Changed refs | Heap (bytes) |',
+ '| ---: | --- | --- | ---: | ---: | ---: | ---: | ---: |',
+ ...results.map(
+ (result) =>
+ `| ${result.graph.nodes} | ${policyId(result.policy)} | ${result.scenario} | ${result.durationMs.toFixed(2)} | ${result.longTasks} | ${result.renderedNodeCount} | ${result.changedNodeReferences} | ${formatHeap(result.usedHeapBytes)} |`,
+ ),
+ '',
+ 'Timing and heap values are observational. CI gates only the schema and semantic assertions.',
+ '',
+ ].join('\n');
+
+ await mkdir(ARTIFACT_DIRECTORY, { recursive: true });
+ await Promise.all([
+ writeFile(
+ path.join(ARTIFACT_DIRECTORY, 'results.json'),
+ `${JSON.stringify(envelope, null, 2)}\n`,
+ 'utf8',
+ ),
+ writeFile(path.join(ARTIFACT_DIRECTORY, 'summary.md'), markdown, 'utf8'),
+ ]);
+}
+
+test('records every workflow canvas scale scenario and policy', async ({
+ browser,
+ page,
+}) => {
+ const results: WorkflowCanvasBenchmarkResult[] = [];
+ const profiles: ReactProfileArtifact[] = [];
+ let userAgent: string | null = null;
+
+ try {
+ for (const graphNodes of WORKFLOW_CANVAS_BENCHMARK_GRAPH_SIZES) {
+ for (const policyCase of BENCHMARK_POLICIES) {
+ const query = new URLSearchParams({
+ minimap: policyCase.policy.minimap ? '1' : '0',
+ nodes: String(graphNodes),
+ visible: policyCase.policy.visibleElementsOnly ? '1' : '0',
+ });
+ await page.goto(`/workflow-canvas-benchmark?${query.toString()}`, {
+ waitUntil: 'domcontentloaded',
+ });
+ await waitForInitialFit(page);
+
+ const initial = await appendResult(page, 'initial-load', 0);
+ results.push(initial.result);
+ if (initial.profile) profiles.push(initial.profile);
+ userAgent ??= initial.result.browser;
+
+ for (const scenario of WORKFLOW_CANVAS_BENCHMARK_SCENARIOS) {
+ if (scenario === 'initial-load') {
+ continue;
+ }
+ const selectionTargets =
+ scenario === 'selection'
+ ? await prepareSelectionScenario(page)
+ : undefined;
+ if (selectionTargets) {
+ await waitForAnimationFrames(page);
+ }
+ const startedAt = await page.evaluate((nextScenario) => {
+ const api = window.__AEVATAR_WORKFLOW_CANVAS_BENCHMARK__;
+ if (!api) {
+ throw new Error('Workflow canvas benchmark API is unavailable');
+ }
+ api.beginScenario(nextScenario);
+ return performance.now();
+ }, scenario);
+ await runScenarioAction(
+ page,
+ scenario,
+ policyCase.policy.visibleElementsOnly,
+ selectionTargets,
+ );
+ const captured = await appendResult(page, scenario, startedAt);
+ results.push(captured.result);
+ if (captured.profile) profiles.push(captured.profile);
+ }
+
+ const pageResults = await page.evaluate(
+ () =>
+ window.__AEVATAR_WORKFLOW_CANVAS_BENCHMARK__?.getResults() ?? [],
+ );
+ expect(pageResults).toEqual(results.slice(-8));
+ }
+ }
+
+ expect(results).toHaveLength(EXPECTED_RESULT_COUNT);
+ } finally {
+ await writeArtifacts({
+ browserVersion: browser.version(),
+ profiles,
+ results,
+ userAgent,
+ });
+ }
+});
diff --git a/apps/aevatar-console-web/playwright.performance.config.ts b/apps/aevatar-console-web/playwright.performance.config.ts
new file mode 100644
index 0000000000..24ea18b592
--- /dev/null
+++ b/apps/aevatar-console-web/playwright.performance.config.ts
@@ -0,0 +1,35 @@
+import { defineConfig } from '@playwright/test';
+
+const benchmarkBaseUrl = 'http://127.0.0.1:4174';
+
+export default defineConfig({
+ expect: {
+ timeout: 10_000,
+ },
+ fullyParallel: false,
+ outputDir: 'artifacts/workflow-canvas-benchmark/test-results',
+ reporter: [['line']],
+ testDir: './performance',
+ testMatch: 'workflowCanvas.performance.spec.ts',
+ timeout: 15 * 60_000,
+ use: {
+ baseURL: benchmarkBaseUrl,
+ browserName: 'chromium',
+ channel: 'chrome',
+ headless: true,
+ launchOptions: {
+ args: ['--enable-precise-memory-info'],
+ },
+ viewport: {
+ height: 900,
+ width: 1440,
+ },
+ },
+ webServer: {
+ command: 'pnpm benchmark:workflow-canvas:serve',
+ reuseExistingServer: false,
+ timeout: 120_000,
+ url: `${benchmarkBaseUrl}/workflow-canvas-benchmark?nodes=100&minimap=1&visible=1`,
+ },
+ workers: 1,
+});
diff --git a/apps/aevatar-console-web/pnpm-lock.yaml b/apps/aevatar-console-web/pnpm-lock.yaml
index f6730db584..1ce67a82bf 100644
--- a/apps/aevatar-console-web/pnpm-lock.yaml
+++ b/apps/aevatar-console-web/pnpm-lock.yaml
@@ -20,6 +20,9 @@ importers:
'@ant-design/pro-components':
specifier: 3.1.2-0
version: 3.1.2-0(antd@6.3.3(date-fns@2.30.0)(moment@2.30.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@fontsource-variable/dm-sans':
+ specifier: 5.3.0
+ version: 5.3.0
'@monaco-editor/react':
specifier: ^4.7.0
version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -38,6 +41,9 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
+ core-js-pure:
+ specifier: 3.49.0
+ version: 3.49.0
dayjs:
specifier: ^1.11.19
version: 1.11.20
@@ -69,6 +75,9 @@ importers:
'@commitlint/config-conventional':
specifier: ^20.0.0
version: 20.5.0
+ '@playwright/test':
+ specifier: 1.60.0
+ version: 1.60.0
'@testing-library/dom':
specifier: ^10.4.0
version: 10.4.1
@@ -126,6 +135,9 @@ importers:
mockjs:
specifier: ^1.1.0
version: 1.1.0
+ react-intl:
+ specifier: 3.12.1
+ version: 3.12.1(@types/react@19.2.14)(react@19.2.4)
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@25.5.0)(typescript@5.9.3)
@@ -1832,6 +1844,9 @@ packages:
react: '>=16.8.0'
react-dom: '>=16.8.0'
+ '@fontsource-variable/dm-sans@5.3.0':
+ resolution: {integrity: sha512-BpUG5bqePiDFMBM4ZtLSlPdAIOM990uB1dlXzIf4aw21yQR7BmykedLXXXMqGWsR18aMLOsbWccwJNh3ztTQaA==}
+
'@formatjs/ecma402-abstract@1.11.4':
resolution: {integrity: sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==}
@@ -2332,6 +2347,11 @@ packages:
resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ '@playwright/test@1.60.0':
+ resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==}
+ engines: {node: '>=18'}
+ hasBin: true
+
'@radix-ui/popper@0.0.10':
resolution: {integrity: sha512-YFKuPqQPKscreQid7NuB4it3PMzSwGg03vgrud6sVliHkI43QNAOHyrHyMNo015jg6QK5GVDn+7J2W5uygqSGA==}
@@ -5907,6 +5927,11 @@ packages:
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+ fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -8397,6 +8422,16 @@ packages:
resolution: {integrity: sha512-fjAPuiws93rm7mPUu21RdBnkeZNrbfCFCwfAhPWY+rR3zG0ubpe5cEReHOw5fIbfmsxEV/g2kSxGTATY3Bpnwg==}
engines: {node: '>=4'}
+ playwright-core@1.60.0:
+ resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ playwright@1.60.0:
+ resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
plur@4.0.0:
resolution: {integrity: sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==}
engines: {node: '>=10'}
@@ -13970,6 +14005,8 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
+ '@fontsource-variable/dm-sans@5.3.0': {}
+
'@formatjs/ecma402-abstract@1.11.4':
dependencies:
'@formatjs/intl-localematcher': 0.2.25
@@ -14700,6 +14737,10 @@ snapshots:
picocolors: 1.1.1
tslib: 2.8.1
+ '@playwright/test@1.60.0':
+ dependencies:
+ playwright: 1.60.0
+
'@radix-ui/popper@0.0.10':
dependencies:
'@babel/runtime': 7.29.2
@@ -19794,6 +19835,9 @@ snapshots:
fs.realpath@1.0.0: {}
+ fsevents@2.3.2:
+ optional: true
+
fsevents@2.3.3:
optional: true
@@ -22778,6 +22822,14 @@ snapshots:
dependencies:
find-up: 2.1.0
+ playwright-core@1.60.0: {}
+
+ playwright@1.60.0:
+ dependencies:
+ playwright-core: 1.60.0
+ optionalDependencies:
+ fsevents: 2.3.2
+
plur@4.0.0:
dependencies:
irregular-plurals: 3.5.0
diff --git a/apps/aevatar-console-web/src/app.layout.test.ts b/apps/aevatar-console-web/src/app.layout.test.ts
index 8f1f79d89b..fe354809b3 100644
--- a/apps/aevatar-console-web/src/app.layout.test.ts
+++ b/apps/aevatar-console-web/src/app.layout.test.ts
@@ -1,16 +1,22 @@
-import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
-import { getLocale, setLocale } from "@umijs/max";
-import React from "react";
-import defaultSettings from "../config/defaultSettings";
-import { layout } from "./app";
-
-describe("layout menu collapse behavior", () => {
+import {
+ act,
+ fireEvent,
+ render,
+ screen,
+ waitFor,
+} from '@testing-library/react';
+import { getLocale, setLocale } from '@umijs/max';
+import React from 'react';
+import defaultSettings from '../config/defaultSettings';
+import { layout } from './app';
+
+describe('layout menu collapse behavior', () => {
beforeEach(() => {
- setLocale("en-US", false);
- window.history.replaceState({}, "", "/scopes");
+ setLocale('en-US', false);
+ window.history.replaceState({}, '', '/scopes');
});
- it("keeps grouped navigation titles hidden in collapsed mode", () => {
+ it('keeps grouped navigation titles hidden in collapsed mode', () => {
const runtimeLayout = layout({
initialState: {
auth: {} as never,
@@ -22,15 +28,15 @@ describe("layout menu collapse behavior", () => {
collapsedWidth: 40,
collapsedShowGroupTitle: false,
collapsedShowTitle: false,
- type: "group",
+ type: 'group',
});
});
- it("collapses the global menu for Studio create-member intent", () => {
+ it('collapses the global menu for Studio create-member intent', () => {
window.history.replaceState(
{},
- "",
- "/studio?tab=studio&intent=create-member",
+ '',
+ '/studio?tab=studio&intent=create-member',
);
const runtimeLayout = layout({
@@ -44,8 +50,8 @@ describe("layout menu collapse behavior", () => {
expect(runtimeLayout.collapsed).toBe(true);
});
- it("leaves the global menu uncontrolled for ordinary Studio entry", () => {
- window.history.replaceState({}, "", "/studio?tab=studio");
+ it('leaves the global menu uncontrolled for ordinary Studio entry', () => {
+ window.history.replaceState({}, '', '/studio?tab=studio');
const runtimeLayout = layout({
initialState: {
@@ -58,8 +64,38 @@ describe("layout menu collapse behavior", () => {
expect(runtimeLayout.collapsed).toBeUndefined();
});
- it("hides console chrome for the fullscreen Mission Wall route", () => {
- window.history.replaceState({}, "", "/runtime/mission-wall");
+ it('hides console chrome for the fullscreen Mission Wall route', () => {
+ window.history.replaceState({}, '', '/runtime/mission-wall');
+
+ const runtimeLayout = layout({
+ initialState: {
+ auth: {} as never,
+ settings: defaultSettings,
+ },
+ });
+ const menuRender = runtimeLayout.menuRender as
+ | ((props: unknown, defaultDom: unknown) => React.ReactNode)
+ | undefined;
+ const actionsRender = runtimeLayout.actionsRender as
+ | ((props: unknown, dom: unknown) => React.ReactNode[])
+ | undefined;
+
+ expect(runtimeLayout.headerRender).toBe(false);
+ expect(menuRender?.({}, React.createElement('nav'))).toBe(false);
+ expect(actionsRender?.({}, {})).toEqual([]);
+ expect(runtimeLayout.contentStyle).toMatchObject({
+ background: '#09110f',
+ height: '100vh',
+ overflow: 'hidden',
+ padding: 0,
+ });
+ });
+
+ it.each([
+ '/workflows',
+ '/scopes/scope-a/workflow-activity-vnext/workflows/wf-a',
+ ])('renders workflow route %s without the global console chrome', (pathname) => {
+ window.history.replaceState({}, '', pathname);
const runtimeLayout = layout({
initialState: {
@@ -75,18 +111,20 @@ describe("layout menu collapse behavior", () => {
| undefined;
expect(runtimeLayout.headerRender).toBe(false);
- expect(menuRender?.({}, React.createElement("nav"))).toBe(false);
+ expect(menuRender?.({}, React.createElement('nav'))).toBe(false);
expect(actionsRender?.({}, {})).toEqual([]);
expect(runtimeLayout.contentStyle).toMatchObject({
- background: "#09110f",
- height: "100vh",
- overflow: "hidden",
+ background: '#ffffff',
+ height: 'auto',
+ inset: 0,
+ overflow: 'hidden',
padding: 0,
+ position: 'fixed',
});
});
- it("updates the controlled global menu collapse state after SPA route changes", () => {
- window.history.replaceState({}, "", "/scopes/scope-a/teams");
+ it('updates the controlled global menu collapse state after SPA route changes', () => {
+ window.history.replaceState({}, '', '/scopes/scope-a/teams');
const teamsLayout = layout({
initialState: {
auth: {} as never,
@@ -94,7 +132,7 @@ describe("layout menu collapse behavior", () => {
},
});
- window.history.pushState({}, "", "/studio?tab=studio&intent=create-member");
+ window.history.pushState({}, '', '/studio?tab=studio&intent=create-member');
const studioLayout = layout({
initialState: {
auth: {} as never,
@@ -106,7 +144,7 @@ describe("layout menu collapse behavior", () => {
expect(studioLayout.collapsed).toBe(true);
});
- it("renders a global language switch in the layout actions", async () => {
+ it('renders a global language switch in the layout actions', async () => {
const runtimeLayout = layout({
initialState: {
auth: {} as never,
@@ -117,24 +155,18 @@ describe("layout menu collapse behavior", () => {
| ((props: unknown, dom: unknown) => React.ReactNode[])
| undefined;
- render(
- React.createElement(
- React.Fragment,
- null,
- actionsRender?.({}, {}),
- ),
- );
+ render(React.createElement(React.Fragment, null, actionsRender?.({}, {})));
- fireEvent.click(screen.getByRole("button", { name: "Switch language" }));
- fireEvent.click(await screen.findByText("中文"));
+ fireEvent.click(screen.getByRole('button', { name: 'Switch language' }));
+ fireEvent.click(await screen.findByText('中文'));
await waitFor(() => {
- expect(getLocale()).toBe("zh-CN");
+ expect(getLocale()).toBe('zh-CN');
});
});
- it("keeps page content in sync when the locale changes without a reload", async () => {
- window.history.replaceState({}, "", "/studio");
+ it('keeps page content in sync when the locale changes without a reload', async () => {
+ window.history.replaceState({}, '', '/studio');
const runtimeLayout = layout({
initialState: {
auth: {} as never,
@@ -149,34 +181,32 @@ describe("layout menu collapse behavior", () => {
React.createElement(
React.Fragment,
null,
- childrenRender?.(
- React.createElement(LocalizedRuntimeProbe),
- ),
+ childrenRender?.(React.createElement(LocalizedRuntimeProbe)),
),
);
- expect(screen.getByText("My AI teams")).toBeTruthy();
+ expect(screen.getByText('My AI teams')).toBeTruthy();
act(() => {
- setLocale("zh-CN", false);
+ setLocale('zh-CN', false);
});
await waitFor(() => {
- expect(screen.getByText("我的 AI 团队")).toBeTruthy();
+ expect(screen.getByText('我的 AI 团队')).toBeTruthy();
});
- expect(screen.queryByText("My AI teams")).toBeNull();
+ expect(screen.queryByText('My AI teams')).toBeNull();
});
});
const LocalizedRuntimeProbe: React.FC = () => {
- const { useIntl } = require("@umijs/max") as typeof import("@umijs/max");
+ const { useIntl } = require('@umijs/max') as typeof import('@umijs/max');
const intl = useIntl();
return React.createElement(
- "div",
+ 'div',
null,
intl.formatMessage({
- id: "teams.home.title",
- }),
+ id: 'teams.home.title',
+ }),
);
};
diff --git a/apps/aevatar-console-web/src/app.test.tsx b/apps/aevatar-console-web/src/app.test.tsx
index e9875d6d5a..4eb9638033 100644
--- a/apps/aevatar-console-web/src/app.test.tsx
+++ b/apps/aevatar-console-web/src/app.test.tsx
@@ -1,80 +1,86 @@
-import { render, waitFor } from "@testing-library/react";
-import React from "react";
-import { requiresGlobalAuthGate } from "./app";
-import { ProtectedRouteRedirectGate } from "./shared/auth/ProtectedRouteRedirectGate";
+import { render, waitFor } from '@testing-library/react';
+import React from 'react';
+import { ProtectedRouteRedirectGate } from './shared/auth/ProtectedRouteRedirectGate';
+import { requiresGlobalAuthGate } from './shared/auth/routeAccess';
const mockedHistoryReplace = jest.fn();
-jest.mock("./shared/navigation/history", () => ({
+jest.mock('./shared/navigation/history', () => ({
history: {
push: jest.fn(),
replace: (...args: unknown[]) => mockedHistoryReplace(...args),
},
}));
-describe("ProtectedRouteRedirectGate", () => {
+describe('ProtectedRouteRedirectGate', () => {
beforeEach(() => {
mockedHistoryReplace.mockReset();
- window.history.replaceState({}, "", "/scopes");
+ window.history.replaceState({}, '', '/scopes');
});
- it("redirects protected routes into the login flow after mount", async () => {
+ it('redirects protected routes into the login flow after mount', async () => {
render(
React.createElement(ProtectedRouteRedirectGate, {
- pathname: "/scopes",
+ pathname: '/scopes',
}),
);
await waitFor(() => {
- expect(mockedHistoryReplace).toHaveBeenCalledWith("/login?redirect=%2Fscopes");
+ expect(mockedHistoryReplace).toHaveBeenCalledWith(
+ '/login?redirect=%2Fscopes',
+ );
});
});
- it("keeps Mission Wall behind the login flow", async () => {
- window.history.replaceState({}, "", "/runtime/mission-wall?focusRunId=run-1");
+ it('keeps Mission Wall behind the login flow', async () => {
+ window.history.replaceState(
+ {},
+ '',
+ '/runtime/mission-wall?focusRunId=run-1',
+ );
render(
React.createElement(ProtectedRouteRedirectGate, {
- pathname: "/runtime/mission-wall",
+ pathname: '/runtime/mission-wall',
}),
);
await waitFor(() => {
expect(mockedHistoryReplace).toHaveBeenCalledWith(
- "/login?redirect=%2Fruntime%2Fmission-wall%3FfocusRunId%3Drun-1",
+ '/login?redirect=%2Fruntime%2Fmission-wall%3FfocusRunId%3Drun-1',
);
});
});
- it("preserves the delivered Team member workflow deep link through login", async () => {
+ it('preserves the delivered Team member workflow deep link through login', async () => {
window.history.replaceState(
{},
- "",
- "/scopes/s-customer/teams/t-hr/members/m-reminder/workflow?workflowId=wf-reminder#run",
+ '',
+ '/scopes/s-customer/teams/t-hr/members/m-reminder/workflow?workflowId=wf-reminder#run',
);
render(
React.createElement(ProtectedRouteRedirectGate, {
- pathname: "/scopes/s-customer/teams/t-hr/members/m-reminder/workflow",
+ pathname: '/scopes/s-customer/teams/t-hr/members/m-reminder/workflow',
}),
);
await waitFor(() => {
expect(mockedHistoryReplace).toHaveBeenCalledWith(
- "/login?redirect=%2Fscopes%2Fs-customer%2Fteams%2Ft-hr%2Fmembers%2Fm-reminder%2Fworkflow%3FworkflowId%3Dwf-reminder%23run",
+ '/login?redirect=%2Fscopes%2Fs-customer%2Fteams%2Ft-hr%2Fmembers%2Fm-reminder%2Fworkflow%3FworkflowId%3Dwf-reminder%23run',
);
});
});
});
-describe("global auth route classification", () => {
- it("protects canonical Team member workflow routes while legacy Studio keeps its own recovery", () => {
+describe('global auth route classification', () => {
+ it('protects canonical Team member workflow routes while legacy Studio keeps its own recovery', () => {
expect(
requiresGlobalAuthGate(
- "/scopes/s-customer/teams/t-hr/members/m-reminder/workflow",
+ '/scopes/s-customer/teams/t-hr/members/m-reminder/workflow',
),
).toBe(true);
- expect(requiresGlobalAuthGate("/studio")).toBe(false);
- expect(requiresGlobalAuthGate("/login")).toBe(false);
+ expect(requiresGlobalAuthGate('/studio')).toBe(false);
+ expect(requiresGlobalAuthGate('/login')).toBe(false);
});
});
diff --git a/apps/aevatar-console-web/src/app.tsx b/apps/aevatar-console-web/src/app.tsx
index c33f6b4309..e4d46c5c13 100644
--- a/apps/aevatar-console-web/src/app.tsx
+++ b/apps/aevatar-console-web/src/app.tsx
@@ -1,61 +1,76 @@
-import { ProConfigProvider } from "@ant-design/pro-components";
-import { QueryClientProvider } from "@tanstack/react-query";
-import { Badge, ConfigProvider } from "antd";
-import { getLocale, useIntl } from "@umijs/max";
-import React from "react";
-import MainLayout from "@/layouts/MainLayout";
-import { history } from "./shared/navigation/history";
-import { CONSOLE_HOME_ROUTE } from "@/shared/navigation/consoleHome";
-import BrandLogo from "@/components/BrandLogo";
-import defaultSettings from "../config/defaultSettings";
-import { errorConfig } from "./requestErrorConfig";
+import { ProConfigProvider } from '@ant-design/pro-components';
+import { QueryClientProvider } from '@tanstack/react-query';
+import { getLocale, useIntl } from '@umijs/max';
+import { Badge, ConfigProvider } from 'antd';
+import React from 'react';
+import BrandLogo from '@/components/BrandLogo';
+import MainLayout from '@/layouts/MainLayout';
+import { buildMissionSnapshotFromRuntime } from '@/pages/MissionControl/runtimeAdapter';
+import { readMissionControlRouteContext } from '@/pages/MissionControl/services/api';
+import { runtimeActorsApi } from '@/shared/api/runtimeActorsApi';
+import { runtimeRunsApi } from '@/shared/api/runtimeRunsApi';
+import {
+ normalizeConsoleLocale,
+ resolveAntdLocale,
+ resolveProIntl,
+} from '@/shared/i18n/localeProvider';
+import { CONSOLE_HOME_ROUTE } from '@/shared/navigation/consoleHome';
+import { loadRecentRuns } from '@/shared/runs/recentRuns';
+import { AevatarPageLoading } from '@/shared/ui/AevatarLoading';
+import { aevatarThemeConfig } from '@/shared/ui/aevatarWorkbench';
+import { ConsoleHeaderActions } from '@/shared/ui/ConsoleHeaderActions';
+import { ConsoleToastProvider } from '@/shared/ui/ConsoleToast';
+import defaultSettings from '../config/defaultSettings';
+import { errorConfig } from './requestErrorConfig';
import {
ensureActiveAuthSession,
hasRestorableAuthSession,
-} from "./shared/auth/client";
-import { getNyxIDRuntimeConfig } from "./shared/auth/config";
+} from './shared/auth/client';
+import { getNyxIDRuntimeConfig } from './shared/auth/config';
+import { ProtectedRouteRedirectGate } from './shared/auth/ProtectedRouteRedirectGate';
+import {
+ PUBLIC_ROUTES,
+ requiresGlobalAuthGate,
+} from './shared/auth/routeAccess';
import {
buildAuthInitialState,
loadStoredAuthSession,
sanitizeReturnTo,
-} from "./shared/auth/session";
-import { ProtectedRouteRedirectGate } from "./shared/auth/ProtectedRouteRedirectGate";
+} from './shared/auth/session';
+import { history } from './shared/navigation/history';
import {
getNavigationGroupOrder,
type NavigationGroup,
-} from "./shared/navigation/navigationGroups";
-import { getNavigationSelectedKeys } from "./shared/navigation/navigationMenuSelection";
+} from './shared/navigation/navigationGroups';
import {
groupNavigationMenuItems,
type NavigationMenuItem,
-} from "./shared/navigation/navigationMenuGrouping";
-import { runtimeActorsApi } from "@/shared/api/runtimeActorsApi";
-import { runtimeRunsApi } from "@/shared/api/runtimeRunsApi";
-import { buildMissionSnapshotFromRuntime } from "@/pages/MissionControl/runtimeAdapter";
-import { readMissionControlRouteContext } from "@/pages/MissionControl/services/api";
-import { loadRecentRuns } from "@/shared/runs/recentRuns";
-import { queryClient } from "./shared/query/queryClient";
-import { aevatarThemeConfig } from "@/shared/ui/aevatarWorkbench";
-import { ConsoleHeaderActions } from "@/shared/ui/ConsoleHeaderActions";
-import {
- normalizeConsoleLocale,
- resolveAntdLocale,
- resolveProIntl,
-} from "@/shared/i18n/localeProvider";
-import { AevatarPageLoading } from "@/shared/ui/AevatarLoading";
+} from './shared/navigation/navigationMenuGrouping';
+import { getNavigationSelectedKeys } from './shared/navigation/navigationMenuSelection';
+import { queryClient } from './shared/query/queryClient';
-const PUBLIC_ROUTES = new Set(["/login", "/auth/callback"]);
const DEFAULT_PROTECTED_ROUTE = CONSOLE_HOME_ROUTE;
-const FULLSCREEN_DISPLAY_ROUTES = new Set(["/runtime/mission-wall"]);
+const FULLSCREEN_DISPLAY_ROUTES = new Set(['/runtime/mission-wall']);
+const WORKFLOW_ACTIVITY_VNEXT_ROUTE =
+ /^\/scopes\/[^/]+\/workflow-activity-vnext(?:\/|$)/;
const STUDIO_HOST_ROUTES = new Set([
- "/studio",
- "/scopes/:scopeId/teams/:teamId/members/new/workflow",
- "/scopes/:scopeId/teams/:teamId/members/:memberId/workflow",
+ '/studio',
+ '/scopes/:scopeId/teams/:teamId/members/new/workflow',
+ '/scopes/:scopeId/teams/:teamId/members/:memberId/workflow',
]);
-const SELF_MANAGED_AUTH_ROUTES = new Set(["/studio"]);
function isFullscreenDisplayRoute(pathname: string): boolean {
- return FULLSCREEN_DISPLAY_ROUTES.has(pathname);
+ return (
+ FULLSCREEN_DISPLAY_ROUTES.has(pathname) ||
+ isWorkflowActivityVNextRoute(pathname)
+ );
+}
+
+function isWorkflowActivityVNextRoute(pathname: string): boolean {
+ return (
+ pathname === CONSOLE_HOME_ROUTE ||
+ WORKFLOW_ACTIVITY_VNEXT_ROUTE.test(pathname)
+ );
}
function isStudioHostRoute(pathname: string): boolean {
@@ -63,23 +78,20 @@ function isStudioHostRoute(pathname: string): boolean {
return true;
}
- return (
- /^\/scopes\/[^/]+\/teams\/[^/]+\/members\/(?:new|[^/]+)\/workflow$/.test(
- pathname,
- )
+ return /^\/scopes\/[^/]+\/teams\/[^/]+\/members\/(?:new|[^/]+)\/workflow$/.test(
+ pathname,
);
}
-export function requiresGlobalAuthGate(pathname: string): boolean {
- return !PUBLIC_ROUTES.has(pathname) && !SELF_MANAGED_AUTH_ROUTES.has(pathname);
-}
-
-function shouldDefaultCollapseLayout(pathname: string, search: string): boolean {
+function shouldDefaultCollapseLayout(
+ pathname: string,
+ search: string,
+): boolean {
if (!isStudioHostRoute(pathname)) {
return false;
}
- return new URLSearchParams(search).get("intent") === "create-member";
+ return new URLSearchParams(search).get('intent') === 'create-member';
}
function shouldCollapseLayout(pathname: string, search: string): boolean {
@@ -94,7 +106,7 @@ function buildLoginRoute(returnTo: string): string {
}
function getCurrentReturnTo(pathname: string): string {
- return pathname === "/"
+ return pathname === '/'
? DEFAULT_PROTECTED_ROUTE
: `${pathname}${window.location.search}${window.location.hash}`;
}
@@ -145,20 +157,21 @@ type ConsoleRuntimeProvidersProps = {
search: string;
};
-const LIVE_OPS_ATTENTION_BADGE_KEY = "live.attention";
+const LIVE_OPS_ATTENTION_BADGE_KEY = 'live.attention';
const LIVE_OPS_ATTENTION_MAX_CANDIDATES = 6;
const LIVE_OPS_ATTENTION_MAX_AGE_MS = 12 * 60 * 60 * 1000;
const LIVE_OPS_ATTENTION_REFRESH_MS = 30_000;
-const NAVIGATION_GROUP_ORDER: readonly NavigationGroup[] = getNavigationGroupOrder();
+const NAVIGATION_GROUP_ORDER: readonly NavigationGroup[] =
+ getNavigationGroupOrder();
const NAVIGATION_MENU_MESSAGE_IDS: Readonly> = {
- "/chat": "nav.items.chat",
- "/scopes": "nav.items.myTeams",
- "/runtime/runs": "nav.items.eventStream",
- "/services": "nav.items.services",
- "/governance": "nav.items.governance",
- "/deployments": "nav.items.deployments",
- "/runtime/explorer": "nav.items.topology",
- "/settings": "nav.items.settings",
+ '/chat': 'nav.items.chat',
+ '/scopes': 'nav.items.myTeams',
+ '/runtime/runs': 'nav.items.eventStream',
+ '/services': 'nav.items.services',
+ '/governance': 'nav.items.governance',
+ '/deployments': 'nav.items.deployments',
+ '/runtime/explorer': 'nav.items.topology',
+ '/settings': 'nav.items.settings',
};
const LIVE_OPS_DEFAULT_ATTENTION_SNAPSHOT: LiveOpsAttentionSnapshot = {
hasPendingAttention: false,
@@ -168,11 +181,11 @@ const liveOpsAttentionListeners = new Set<() => void>();
let liveOpsAttentionSnapshot = LIVE_OPS_DEFAULT_ATTENTION_SNAPSHOT;
const navigationGroupLabelStyle: React.CSSProperties = {
- color: "#667085",
- display: "inline-flex",
+ color: '#667085',
+ display: 'inline-flex',
fontSize: 14,
fontWeight: 700,
- lineHeight: "22px",
+ lineHeight: '22px',
};
const LocalizedNavigationText: React.FC<{
@@ -181,7 +194,7 @@ const LocalizedNavigationText: React.FC<{
}> = ({ defaultLabel, messageId }) => {
const intl = useIntl();
const defaultMessage =
- typeof defaultLabel === "string" ? defaultLabel : undefined;
+ typeof defaultLabel === 'string' ? defaultLabel : undefined;
return (
<>
@@ -235,7 +248,7 @@ function setLiveOpsAttentionSnapshot(next: LiveOpsAttentionSnapshot): void {
}
function buildLiveOpsAttentionCandidateKey(
- candidate: LiveOpsAttentionCandidate
+ candidate: LiveOpsAttentionCandidate,
): string {
const actorId = trimOptional(candidate.actorId);
if (actorId) {
@@ -243,16 +256,16 @@ function buildLiveOpsAttentionCandidateKey(
}
return [
- "run",
- trimOptional(candidate.scopeId) || "",
- trimOptional(candidate.serviceId) || "",
- trimOptional(candidate.runId) || "",
- ].join(":");
+ 'run',
+ trimOptional(candidate.scopeId) || '',
+ trimOptional(candidate.serviceId) || '',
+ trimOptional(candidate.runId) || '',
+ ].join(':');
}
function collectLiveOpsAttentionCandidates(
pathname: string,
- search: string
+ search: string,
): LiveOpsAttentionCandidate[] {
const nowMs = Date.now();
const deduped = new Map();
@@ -266,7 +279,7 @@ function collectLiveOpsAttentionCandidates(
continue;
}
- if (entry.status === "finished" || entry.status === "error") {
+ if (entry.status === 'finished' || entry.status === 'error') {
continue;
}
@@ -286,7 +299,7 @@ function collectLiveOpsAttentionCandidates(
}
}
- if (pathname === "/runtime/mission-control") {
+ if (pathname === '/runtime/mission-control') {
const context = readMissionControlRouteContext(search);
const candidate: LiveOpsAttentionCandidate = {
actorId: trimOptional(context.actorId),
@@ -303,11 +316,14 @@ function collectLiveOpsAttentionCandidates(
}
}
- return Array.from(deduped.values()).slice(0, LIVE_OPS_ATTENTION_MAX_CANDIDATES);
+ return Array.from(deduped.values()).slice(
+ 0,
+ LIVE_OPS_ATTENTION_MAX_CANDIDATES,
+ );
}
async function resolveLiveOpsAttentionActorId(
- candidate: LiveOpsAttentionCandidate
+ candidate: LiveOpsAttentionCandidate,
): Promise {
const actorId = trimOptional(candidate.actorId);
if (actorId) {
@@ -331,7 +347,7 @@ async function resolveLiveOpsAttentionActorId(
}
async function runNeedsLiveOpsAttention(
- candidate: LiveOpsAttentionCandidate
+ candidate: LiveOpsAttentionCandidate,
): Promise {
const actorId = await resolveLiveOpsAttentionActorId(candidate);
if (!actorId) {
@@ -343,7 +359,7 @@ async function runNeedsLiveOpsAttention(
const [graph, timeline] = await Promise.all([
runtimeActorsApi.getActorGraphEnriched(actorId, {
depth: 4,
- direction: "Both",
+ direction: 'Both',
take: 120,
}),
runtimeActorsApi.getActorTimeline(actorId, {
@@ -352,7 +368,7 @@ async function runNeedsLiveOpsAttention(
]);
const snapshot = buildMissionSnapshotFromRuntime({
- connectionStatus: "degraded",
+ connectionStatus: 'degraded',
nowMs: fetchedAtMs,
recentEvents: [],
routeContext: {
@@ -369,15 +385,15 @@ async function runNeedsLiveOpsAttention(
},
session: {
runId: trimOptional(candidate.runId),
- status: "running",
+ status: 'running',
},
},
});
return (
snapshot.intervention?.required === true &&
- (snapshot.intervention.kind === "human_approval" ||
- snapshot.intervention.kind === "human_input")
+ (snapshot.intervention.kind === 'human_approval' ||
+ snapshot.intervention.kind === 'human_input')
);
} catch {
return false;
@@ -386,7 +402,7 @@ async function runNeedsLiveOpsAttention(
async function loadLiveOpsAttentionSnapshot(
pathname: string,
- search: string
+ search: string,
): Promise {
const candidates = collectLiveOpsAttentionCandidates(pathname, search);
if (candidates.length === 0) {
@@ -394,10 +410,10 @@ async function loadLiveOpsAttentionSnapshot(
}
const results = await Promise.allSettled(
- candidates.map((candidate) => runNeedsLiveOpsAttention(candidate))
+ candidates.map((candidate) => runNeedsLiveOpsAttention(candidate)),
);
const pendingCount = results.reduce((count, result) => {
- if (result.status === "fulfilled" && result.value) {
+ if (result.status === 'fulfilled' && result.value) {
return count + 1;
}
@@ -418,7 +434,7 @@ const NavigationMenuLabel: React.FC<{
const snapshot = React.useSyncExternalStore(
subscribeLiveOpsAttention,
getLiveOpsAttentionSnapshot,
- getLiveOpsAttentionSnapshot
+ getLiveOpsAttentionSnapshot,
);
const showCountBadge =
badgeKey === LIVE_OPS_ATTENTION_BADGE_KEY && snapshot.pendingCount > 0;
@@ -426,18 +442,18 @@ const NavigationMenuLabel: React.FC<{
return (
{label}
@@ -456,10 +472,10 @@ const NavigationMenuLabel: React.FC<{
) : null}
@@ -481,7 +497,7 @@ const NavigationMenuLabel: React.FC<{
);
});
-NavigationMenuLabel.displayName = "NavigationMenuLabel";
+NavigationMenuLabel.displayName = 'NavigationMenuLabel';
const LiveOpsGroupIcon: React.FC<{
icon: React.ReactNode;
@@ -489,7 +505,7 @@ const LiveOpsGroupIcon: React.FC<{
const snapshot = React.useSyncExternalStore(
subscribeLiveOpsAttention,
getLiveOpsAttentionSnapshot,
- getLiveOpsAttentionSnapshot
+ getLiveOpsAttentionSnapshot,
);
if (!snapshot.hasPendingAttention || !React.isValidElement(icon)) {
@@ -503,35 +519,33 @@ const LiveOpsGroupIcon: React.FC<{
);
});
-LiveOpsGroupIcon.displayName = "LiveOpsGroupIcon";
+LiveOpsGroupIcon.displayName = 'LiveOpsGroupIcon';
function decorateNavigationMenuItems(
items: NavigationMenuItem[],
- groupItems = true
+ groupItems = true,
): NavigationMenuItem[] {
const sourceItems = groupItems
- ? groupNavigationMenuItems(
- items,
- NAVIGATION_GROUP_ORDER,
- (group) => React.createElement(NavigationGroupLabel, { group }),
+ ? groupNavigationMenuItems(items, NAVIGATION_GROUP_ORDER, (group) =>
+ React.createElement(NavigationGroupLabel, { group }),
)
: items;
return sourceItems.map((item) => {
- const path = typeof item.path === "string" ? item.path : undefined;
+ const path = typeof item.path === 'string' ? item.path : undefined;
const badgeKey =
- typeof item.menuBadgeKey === "string" ? item.menuBadgeKey : undefined;
+ typeof item.menuBadgeKey === 'string' ? item.menuBadgeKey : undefined;
const groupKey =
- typeof item.menuGroupKey === "string" ? item.menuGroupKey : undefined;
+ typeof item.menuGroupKey === 'string' ? item.menuGroupKey : undefined;
const nameMessageId =
- path && typeof item.name === "string"
+ path && typeof item.name === 'string'
? NAVIGATION_MENU_MESSAGE_IDS[path]
: undefined;
const children = Array.isArray(item.children)
? decorateNavigationMenuItems(item.children, false)
: undefined;
const isLiveOpsGroup =
- groupKey === "live" && Array.isArray(children) && children.length > 0;
+ groupKey === 'live' && Array.isArray(children) && children.length > 0;
const hasRenderableIcon = React.isValidElement(item.icon);
const localizedName = nameMessageId
? React.createElement(LocalizedNavigationText, {
@@ -594,7 +608,7 @@ const LiveOpsAttentionBridge: React.FC<{
};
const refreshWhenVisible = () => {
- if (document.visibilityState === "visible") {
+ if (document.visibilityState === 'visible') {
void refresh();
}
};
@@ -607,16 +621,16 @@ const LiveOpsAttentionBridge: React.FC<{
const intervalId = window.setInterval(() => {
void refresh();
}, LIVE_OPS_ATTENTION_REFRESH_MS);
- document.addEventListener("visibilitychange", refreshWhenVisible);
- window.addEventListener("focus", refreshOnFocus);
- window.addEventListener("storage", refreshOnFocus);
+ document.addEventListener('visibilitychange', refreshWhenVisible);
+ window.addEventListener('focus', refreshOnFocus);
+ window.addEventListener('storage', refreshOnFocus);
return () => {
cancelled = true;
window.clearInterval(intervalId);
- document.removeEventListener("visibilitychange", refreshWhenVisible);
- window.removeEventListener("focus", refreshOnFocus);
- window.removeEventListener("storage", refreshOnFocus);
+ document.removeEventListener('visibilitychange', refreshWhenVisible);
+ window.removeEventListener('focus', refreshOnFocus);
+ window.removeEventListener('storage', refreshOnFocus);
};
}, [enabled, pathname, search]);
@@ -628,7 +642,7 @@ const AuthSessionBootstrap: React.FC = ({
children,
}) => {
const [ready, setReady] = React.useState(() =>
- Boolean(loadStoredAuthSession())
+ Boolean(loadStoredAuthSession()),
);
React.useEffect(() => {
@@ -675,11 +689,12 @@ const ConsoleRuntimeProviders: React.FC = ({
}) => {
const intl = useIntl();
const currentLocale = normalizeConsoleLocale(intl.locale || getLocale());
- const localizedContent = isPublicRoute || isFullscreenDisplayRoute ? (
- children
- ) : (
- {children}
- );
+ const localizedContent =
+ isPublicRoute || isFullscreenDisplayRoute ? (
+ children
+ ) : (
+ {children}
+ );
return (
= ({
theme={aevatarThemeConfig}
>
-
-
- {localizedContent}
-
+
+
+
+
+ {localizedContent}
+
+
+
);
@@ -709,6 +728,7 @@ export const layout = ({
const search = window.location.search;
const collapseForRoute = shouldCollapseLayout(pathname, search);
const fullscreenDisplayRoute = isFullscreenDisplayRoute(pathname);
+ const workflowActivityVNextRoute = isWorkflowActivityVNextRoute(pathname);
return {
onPageChange: () => {
@@ -721,7 +741,7 @@ export const layout = ({
return;
}
- if (pathname === "/") {
+ if (pathname === '/') {
history.replace(DEFAULT_PROTECTED_ROUTE);
}
},
@@ -761,19 +781,17 @@ export const layout = ({
const requiresGlobalAuth = requiresGlobalAuthGate(pathname);
const liveSession = loadStoredAuthSession();
const needsProtectedRouteRedirect =
- requiresGlobalAuth &&
- !liveSession &&
- !hasRestorableAuthSession();
+ requiresGlobalAuth && !liveSession && !hasRestorableAuthSession();
const content = needsProtectedRouteRedirect ? (
) : requiresGlobalAuth && !liveSession ? (
-
- {children}
-
- ) : (
- children
- );
+
+ {children}
+
+ ) : (
+ children
+ );
return (
),
...initialState?.settings,
- title: "",
+ title: '',
menu: {
...(initialState?.settings.menu as Record | undefined),
collapsedWidth: 40,
collapsedShowGroupTitle: false,
collapsedShowTitle: false,
- type: "group",
+ type: 'group',
},
- contentStyle: fullscreenDisplayRoute
+ contentStyle: workflowActivityVNextRoute
? {
- background: "#09110f",
- display: "block",
- height: "100vh",
+ background: '#ffffff',
+ display: 'block',
+ height: 'auto',
+ inset: 0,
minHeight: 0,
- overflow: "hidden",
+ overflow: 'hidden',
padding: 0,
+ position: 'fixed',
+ width: '100%',
}
- : {
- background: "transparent",
- display: "flex",
- flexDirection: "column",
- height: "calc(100vh - 56px)",
- minHeight: 0,
- overflow: "hidden",
- padding: 0,
- },
+ : fullscreenDisplayRoute
+ ? {
+ background: '#09110f',
+ display: 'block',
+ height: '100vh',
+ minHeight: 0,
+ overflow: 'hidden',
+ padding: 0,
+ }
+ : {
+ background: 'transparent',
+ display: 'flex',
+ flexDirection: 'column',
+ height: 'calc(100vh - 56px)',
+ minHeight: 0,
+ overflow: 'hidden',
+ padding: 0,
+ },
defaultCollapsed: shouldDefaultCollapseLayout(pathname, search),
headerRender: fullscreenDisplayRoute ? false : undefined,
...(collapseForRoute ? { collapsed: true } : {}),
diff --git a/apps/aevatar-console-web/src/core-js-pure.d.ts b/apps/aevatar-console-web/src/core-js-pure.d.ts
new file mode 100644
index 0000000000..5842b6e57d
--- /dev/null
+++ b/apps/aevatar-console-web/src/core-js-pure.d.ts
@@ -0,0 +1,4 @@
+declare module 'core-js-pure/actual/json/parse' {
+ const parse: typeof JSON.parse;
+ export default parse;
+}
diff --git a/apps/aevatar-console-web/src/global.less b/apps/aevatar-console-web/src/global.less
index 310b577a0b..054c7692d9 100644
--- a/apps/aevatar-console-web/src/global.less
+++ b/apps/aevatar-console-web/src/global.less
@@ -464,6 +464,21 @@ body.aevatar-studio-host
line-height: 1.5;
}
+.aevatar-loading-overlay {
+ align-items: center;
+ background: color-mix(
+ in srgb,
+ var(--aevatar-loading-overlay-surface, #fff) 58%,
+ transparent
+ );
+ cursor: progress;
+ display: flex;
+ inset: 0;
+ justify-content: center;
+ position: absolute;
+ z-index: 20;
+}
+
@media (prefers-reduced-motion: reduce) {
.aevatar-loading-dot,
.aevatar-loading-pulse-dot,
@@ -472,6 +487,22 @@ body.aevatar-studio-host
opacity: 1;
transform: none;
}
+
+ .aevatar-content-skeleton .ant-skeleton.ant-skeleton-active
+ .ant-skeleton-avatar,
+ .aevatar-content-skeleton .ant-skeleton.ant-skeleton-active
+ .ant-skeleton-button,
+ .aevatar-content-skeleton .ant-skeleton.ant-skeleton-active
+ .ant-skeleton-image,
+ .aevatar-content-skeleton .ant-skeleton.ant-skeleton-active
+ .ant-skeleton-input,
+ .aevatar-content-skeleton .ant-skeleton.ant-skeleton-active
+ .ant-skeleton-paragraph
+ > li,
+ .aevatar-content-skeleton .ant-skeleton.ant-skeleton-active
+ .ant-skeleton-title {
+ animation: none;
+ }
}
body.aevatar-studio-host
@@ -2529,6 +2560,13 @@ ol {
color: var(--studio-muted-text);
}
+.aevatar-console-toast.ant-notification-notice {
+ .ant-notification-notice-close {
+ inset-inline-end: 12px;
+ top: 12px;
+ }
+}
+
.studio-catalog-toast {
position: fixed;
left: 50%;
diff --git a/apps/aevatar-console-web/src/locales/catalog.test.ts b/apps/aevatar-console-web/src/locales/catalog.test.ts
index 16543e97f7..be0af79c38 100644
--- a/apps/aevatar-console-web/src/locales/catalog.test.ts
+++ b/apps/aevatar-console-web/src/locales/catalog.test.ts
@@ -3,6 +3,7 @@ import zhCNMessages from './zh-CN';
import routes from '../../config/routes';
type ConsoleRoute = {
+ path: string;
name?: string;
routes?: ConsoleRoute[];
};
diff --git a/apps/aevatar-console-web/src/locales/channelMessages.en-US.ts b/apps/aevatar-console-web/src/locales/channelMessages.en-US.ts
new file mode 100644
index 0000000000..399fa53550
--- /dev/null
+++ b/apps/aevatar-console-web/src/locales/channelMessages.en-US.ts
@@ -0,0 +1,240 @@
+export default {
+ 'channels.error.contract':
+ 'Bot binding is not available yet. Please try again later.',
+ 'channels.guide.title': 'How to connect a bot',
+ 'channels.guide.add': 'Add bot in NyxID',
+ 'channels.guide.addHelp': 'Create your bot in NyxID.',
+ 'channels.guide.bind': 'Refresh and bind',
+ 'channels.guide.bindHelp':
+ 'Return here, refresh the table, then choose Bind.',
+ 'channels.guide.view': 'View your bound bot',
+ 'channels.guide.viewHelp': 'Find your bot and its skill in the table below.',
+ 'channels.guide.step': 'STEP {number}',
+ 'channels.bots.title': 'Channel bots',
+ 'channels.bots.empty': 'No bots yet',
+ 'channels.bots.emptyHelp': 'Add a bot in NyxID, then refresh this table.',
+ 'channels.column.binding': 'Binding',
+ 'channels.binding.bind': 'Bind',
+ 'channels.binding.bound': 'Bound',
+ 'channels.binding.unbound': 'Not bound',
+ 'channels.binding.unavailable': 'Unavailable',
+ 'channels.status.disabled': 'Disabled',
+ 'channels.skills.placeholder': 'Select a skill',
+ 'channels.skills.search': 'Search skills',
+ 'channels.skills.available': 'Available in Ornn',
+ 'channels.skills.loading': 'Loading skills',
+ 'channels.skills.failed': 'Could not load skills. Use Refresh to try again.',
+ 'channels.skills.empty': 'No skills found',
+ 'channels.skills.more': 'Load more skills',
+ 'channels.skills.create': 'Create new skill',
+ 'channels.skills.createHelp':
+ 'Opens Ornn in a new tab. Create your skill, then return here and refresh the list.',
+ 'channels.skills.refresh': 'Refresh skills',
+ 'channels.bind.title': 'Bind bot',
+ 'channels.bind.description':
+ 'Choose a skill and the services this bot can use.',
+ 'channels.bind.success': 'Bot bound successfully.',
+ 'channels.bind.confirming': 'Confirming your changes...',
+ 'channels.bind.delayed':
+ 'Your request was accepted. The updated binding is not visible yet. You can return to Channels and refresh the table.',
+ 'channels.bind.uncertain':
+ 'The request could not be confirmed. Return to Channels and refresh before trying again.',
+ 'channels.bind.conflict':
+ 'This bot could not be bound. Refresh the channel list and review its binding and routes in NyxID.',
+ 'channels.bind.failed':
+ 'Could not save this bot. Check your skill, services and NyxID access, then try again.',
+ 'channels.bind.configuration':
+ 'Bot binding is unavailable because the server callback address is not configured correctly. Contact your administrator.',
+ 'channels.edit.partialConfigSave':
+ 'Label saved, but the configuration could not be updated. Review your choices and try again.',
+ 'channels.edit.label': 'Label',
+ 'channels.edit.labelError':
+ 'Enter a non-empty label. If it is too long, shorten it and try again.',
+ 'channels.edit.labelFailed':
+ 'Could not save the label. Check it and try again.',
+ 'channels.edit.partialSave':
+ 'Label saved, but the skill name could not be updated. Try saving again.',
+ 'channels.edit': 'Edit',
+ 'channels.edit.title': 'Edit {platform}',
+ 'channels.edit.loadingTitle': 'Edit channel',
+ 'channels.edit.loading': 'Loading channel configuration',
+ 'channels.edit.configuration': 'Configuration',
+ 'channels.edit.save': 'Save changes',
+ 'channels.edit.defaults': 'NyxID defaults',
+ 'channels.edit.useDefaults': 'Use NyxID defaults',
+ 'channels.edit.unavailableService': 'Unavailable service',
+ 'channels.edit.servicesError':
+ 'Could not load your services. Try again before saving.',
+ 'channels.edit.servicesEmpty':
+ 'No services are available with your current authorization.',
+ 'channels.edit.missingServices':
+ 'Some saved services are no longer available. Deselect them before saving.',
+ 'channels.edit.selectionError':
+ 'Review your selected services and try again.',
+ 'channels.edit.skillError':
+ 'Check the skill name. Use no more than 128 characters.',
+ 'channels.edit.failed':
+ 'Could not save channel changes. Review your choices and try again.',
+ 'channels.edit.saved': 'Channel changes saved.',
+ 'channels.edit.submitted':
+ 'Changes submitted. They may take a moment to appear in channel details.',
+ 'channels.edit.readbackUnavailable':
+ 'Changes submitted, but the latest configuration could not be loaded. Refresh the channel details to view it.',
+ 'channels.edit.discardTitle': 'Discard your changes?',
+ 'channels.edit.discardHelp': 'Your unsaved channel changes will be lost.',
+ 'workflowActivityVNext.nav.channels': 'Channels',
+ 'channels.title': 'Connect your channels to your agent',
+ 'channels.description': 'Bring your NyxID bots into Aevatar.',
+ 'channels.intro':
+ 'Once connected, talk to your Aevatar bot directly from the channel.',
+ 'channels.available': 'Available channels',
+ 'channels.availableNow': 'Available',
+ 'channels.soon': 'Soon',
+ 'channels.connect': 'Connect',
+ 'channels.connectPlatform': 'Connect {platform}',
+ 'channels.platform.feishu': 'Feishu',
+ 'channels.platform.telegram.description':
+ 'Connect with a BotFather bot token. Webhook setup is handled for you.',
+ 'channels.platform.whatsapp.description': 'Chat with your bot on WhatsApp.',
+ 'channels.connected': 'Connected',
+ 'channels.connectedDescription': 'Channels connected to your account.',
+ 'channels.name.loading': 'Loading name…',
+ 'channels.name.unavailable': 'Name unavailable',
+ 'channels.name.retry': 'Reload channel name',
+ 'channels.column.name': 'Channel name',
+ 'channels.skill.notSet': 'Not set',
+ 'channels.skill.version': 'Version {version}',
+ 'channels.skill.open': 'Open {name} in Ornn',
+ 'channels.identifier.showFull': 'Show full ID',
+ 'channels.column.channel': 'Channel',
+ 'channels.column.skill': 'Skill',
+ 'channels.column.inbound': 'Inbound',
+ 'channels.column.delivery': 'Workflow delivery',
+ 'channels.column.actions': 'Actions',
+ 'channels.manage': 'Manage',
+ 'channels.managePlatform': '{platform} · Manage',
+ 'channels.refresh': 'Refresh channels',
+ 'channels.loading': 'Loading channels',
+ 'channels.status.checking': 'Checking…',
+ 'channels.status.active': 'Active',
+ 'channels.status.pending': 'Waiting for message',
+ 'channels.status.error': 'Needs attention',
+ 'channels.status.unknown': 'Unknown',
+ 'channels.delivery.ready': 'Ready',
+ 'channels.delivery.failed': 'Repair failed',
+ 'channels.delivery.repairing': 'Repairing',
+ 'channels.delivery.repairRequired': 'Needs repair',
+ 'channels.delivery.notEnabled': 'Not enabled',
+ 'channels.empty.title': 'No channels connected yet',
+ 'channels.empty.description':
+ 'Connect Telegram above to start talking to your agent.',
+ 'channels.retry': 'Try again',
+ 'channels.error.load': 'Could not load channels. Please try again.',
+ 'channels.error.refresh': 'Could not refresh channels. Try again.',
+ 'channels.error.names':
+ 'Could not load channel names. Use Refresh to try again.',
+ 'channels.error.unavailable':
+ 'This channel is unavailable or you do not have access.',
+ 'channels.details': 'Channel details',
+ 'channels.details.loading': 'Loading channel details',
+ 'channels.breadcrumb': 'Channel navigation',
+ 'channels.registration': 'Registration',
+ 'channels.inboundMessages': 'Inbound messages',
+ 'channels.services.title': 'Authorized services',
+ 'channels.services.loading': 'Loading service names',
+ 'channels.services.empty': 'No services authorized.',
+ 'channels.services.default':
+ 'Uses NyxID default authorization; individual services are not listed.',
+ 'channels.services.unavailable':
+ 'Authorization details are unavailable for this channel.',
+ 'channels.services.namesError':
+ 'Could not load service names. Please try again.',
+ 'channels.botId': 'Bot ID',
+ 'channels.provider': 'Provider',
+ 'channels.keyId': 'Agent key ID',
+ 'channels.openInNyxID': 'Open {label} {id} in NyxID (new tab)',
+ 'channels.back': 'Back to channels',
+ 'channels.remove': 'Remove',
+ 'channels.cancel': 'Cancel',
+ 'channels.remove.title': 'Remove this channel?',
+ 'channels.remove.description':
+ 'This bot will stop routing messages to Aevatar. To reconnect it, you will need to set it up again.',
+ 'channels.remove.error': 'Could not remove this channel. Please try again.',
+ 'channels.remove.pending':
+ 'Removal requested. Waiting for the channel list to confirm the change.',
+ 'channels.remove.confirming': 'Confirming removal…',
+ 'channels.remove.check': 'Check again',
+ 'channels.remove.success': 'Channel removed.',
+ 'channels.remove.warning':
+ 'Channel removed. Some cleanup needs attention in NyxID.',
+ 'channels.connect.title': 'Connect Telegram',
+ 'channels.connect.description':
+ 'Add your bot and choose the services it can use. Telegram webhook setup is automatic.',
+ 'channels.connect.botDetails': 'Bot details',
+ 'channels.connect.botToken': 'Bot token',
+ 'channels.connect.showToken': 'Show bot token',
+ 'channels.connect.hideToken': 'Hide bot token',
+ 'channels.connect.botFather': 'Get token from BotFather',
+ 'channels.connect.name': 'Channel name',
+ 'channels.connect.skillName': 'Skill',
+ 'channels.connect.skillNameDefault': 'Defaults to the Telegram bot name',
+ 'channels.connect.optional': '(optional)',
+ 'channels.connect.botNameDefault':
+ 'Defaults to the bot name + 6 random digits',
+ 'channels.connect.tokenRequired': 'Enter the bot token from BotFather.',
+ 'channels.connect.services': 'Services',
+ 'channels.connect.selected': '{count} selected',
+ 'channels.connect.selectAll': 'Select all',
+ 'channels.connect.selectAllResults': 'Select all results',
+ 'channels.connect.servicesHelp':
+ 'Only services available through your current NyxID authorization are shown.',
+ 'channels.connect.servicesLoading': 'Loading services',
+ 'channels.connect.servicesError':
+ 'Could not load your services. Retry before connecting.',
+ 'channels.connect.servicesEmpty':
+ 'No services are available with your current NyxID authorization.',
+ 'channels.connect.serviceRequired': 'Required',
+ 'channels.connect.requiredServicesMissing':
+ 'Required services unavailable: {services}. Check your NyxID access, then retry.',
+ 'channels.edit.replaceDefaults':
+ 'Saving replaces NyxID default access with the selected services.',
+ 'channels.connect.searchServices': 'Search services by name or slug',
+ 'channels.connect.serviceUnavailable': 'Unavailable',
+ 'channels.connect.personal': 'Personal',
+ 'channels.connect.organization': 'Organization',
+ 'channels.connect.noMatches': 'No services match your search.',
+ 'channels.connect.selectionHelp':
+ 'Only selected services will be available to this bot.',
+ 'channels.connect.selectionChanged':
+ 'Some selected services are no longer available. Deselect them before connecting.',
+ 'channels.connect.pending':
+ 'Your request was submitted. Check again to open your channel when it is ready.',
+ 'channels.connect.check': 'Check again',
+ 'channels.connect.success': 'Telegram channel created.',
+ 'channels.connect.error.confirmation':
+ 'Could not check your channel. Please check again.',
+ 'channels.connect.submitted': 'Request submitted',
+ 'channels.connect.error.token':
+ 'Check the bot token from BotFather and try again.',
+ 'channels.connect.error.botName':
+ 'Could not read the Telegram bot name. Please try again.',
+ 'channels.connect.error.services':
+ 'The selected services are no longer available. Review your selection and try again.',
+ 'channels.connect.error.skill':
+ 'Could not configure the bot skill. Check the Skill name and your access in Ornn.',
+ 'channels.connect.error.authorization':
+ 'Your session or service access needs attention. Sign in again and review your NyxID access.',
+ 'channels.connect.error.conflict':
+ 'This bot may already be connected. Check your channels before trying again.',
+ 'channels.connect.error.configuration':
+ 'Channel setup is unavailable. Ask your administrator to check the deployment configuration.',
+ 'channels.connect.error.rejected':
+ 'Could not connect Telegram. Check the bot token, Skill name, and selected services, then try again.',
+ 'channels.connect.error.uncertain':
+ 'Could not confirm the connection. Please try again.',
+ 'channels.connect.discardTitle': 'Discard this connection setup?',
+ 'channels.connect.discard': 'Discard',
+ 'channels.connect.stay': 'Stay',
+ 'channels.connect.discardHelp':
+ 'Your bot token and unsaved choices will be cleared.',
+};
diff --git a/apps/aevatar-console-web/src/locales/channelMessages.zh-CN.ts b/apps/aevatar-console-web/src/locales/channelMessages.zh-CN.ts
new file mode 100644
index 0000000000..841214d0a9
--- /dev/null
+++ b/apps/aevatar-console-web/src/locales/channelMessages.zh-CN.ts
@@ -0,0 +1,210 @@
+export default {
+ 'channels.error.contract': 'Bot 绑定功能暂未开放,请稍后重试。',
+ 'channels.guide.title': '如何连接 Bot',
+ 'channels.guide.add': '前往 NyxID 添加 Bot',
+ 'channels.guide.addHelp': '在 NyxID 中添加 Bot。',
+ 'channels.guide.bind': '刷新并绑定',
+ 'channels.guide.bindHelp': '返回此页面,刷新表格后点击绑定。',
+ 'channels.guide.view': '查看已绑定的 Bot',
+ 'channels.guide.viewHelp': '在下方表格中查看 Bot 和对应的 Skill。',
+ 'channels.guide.step': '步骤 {number}',
+ 'channels.bots.title': 'Channel Bot',
+ 'channels.bots.empty': '暂无 Bot',
+ 'channels.bots.emptyHelp': '在 NyxID 添加 Bot 后,刷新此表格。',
+ 'channels.column.binding': '绑定',
+ 'channels.binding.bind': '绑定',
+ 'channels.binding.bound': '已绑定',
+ 'channels.binding.unbound': '未绑定',
+ 'channels.binding.unavailable': '不可用',
+ 'channels.status.disabled': '已停用',
+ 'channels.skills.placeholder': '选择 Skill',
+ 'channels.skills.search': '搜索 Skill',
+ 'channels.skills.available': 'Ornn 中可用的 Skill',
+ 'channels.skills.loading': '正在加载 Skill',
+ 'channels.skills.failed': '无法加载 Skill,请点击刷新重试。',
+ 'channels.skills.empty': '未找到 Skill',
+ 'channels.skills.more': '加载更多 Skill',
+ 'channels.skills.create': '创建新 Skill',
+ 'channels.skills.createHelp':
+ '将在新标签页打开 Ornn。创建 Skill 后,请返回此处刷新列表。',
+ 'channels.skills.refresh': '刷新 Skill',
+ 'channels.bind.title': '绑定 Bot',
+ 'channels.bind.description': '选择此 Bot 使用的 Skill 和可访问的服务。',
+ 'channels.bind.success': 'Bot 绑定成功。',
+ 'channels.bind.confirming': '正在确认更改…',
+ 'channels.bind.delayed':
+ '请求已受理,绑定信息尚未更新。你可以返回 Channels 刷新表格查看。',
+ 'channels.bind.uncertain':
+ '无法确认请求结果。请先返回 Channels 刷新列表,再决定是否重试。',
+ 'channels.bind.conflict':
+ '无法绑定此 Bot。请刷新列表,并到 NyxID 检查绑定与路由。',
+ 'channels.bind.failed':
+ '无法保存此 Bot,请检查 Skill、Services 和 NyxID 授权后重试。',
+ 'channels.bind.configuration':
+ '服务端 Bot 回调地址未正确配置,暂时无法绑定。请联系管理员处理。',
+ 'channels.edit.partialConfigSave':
+ 'Label 已保存,但配置更新失败。请检查选择后重试。',
+ 'channels.edit.label': 'Label',
+ 'channels.edit.labelError': 'Label 不能为空;如果名称过长,请缩短后重试。',
+ 'channels.edit.labelFailed': '无法保存 Label,请检查后重试。',
+ 'channels.edit.partialSave':
+ 'Label 已保存,但 Skill 名称更新失败,请再次保存。',
+ 'channels.edit': '编辑',
+ 'channels.edit.title': '编辑 {platform}',
+ 'channels.edit.loadingTitle': '编辑渠道',
+ 'channels.edit.loading': '正在加载渠道配置',
+ 'channels.edit.configuration': '配置',
+ 'channels.edit.save': '保存更改',
+ 'channels.edit.defaults': 'NyxID 默认授权',
+ 'channels.edit.useDefaults': '使用 NyxID 默认授权',
+ 'channels.edit.unavailableService': '不可用的服务',
+ 'channels.edit.servicesError': '无法加载服务,请重试后再保存。',
+ 'channels.edit.servicesEmpty': '当前授权范围内暂无可用服务。',
+ 'channels.edit.missingServices':
+ '部分已保存的服务已不可用,请取消勾选后再保存。',
+ 'channels.edit.selectionError': '请检查选中的服务后重试。',
+ 'channels.edit.skillError': '请检查 Skill 名称,最多允许 128 个字符。',
+ 'channels.edit.failed': '无法保存渠道更改,请检查填写内容后重试。',
+ 'channels.edit.saved': '渠道更改已保存。',
+ 'channels.edit.submitted': '更改已提交,可能需要片刻才会显示在渠道详情中。',
+ 'channels.edit.readbackUnavailable':
+ '更改已提交,但暂时无法加载最新配置,请刷新渠道详情查看。',
+ 'channels.edit.discardTitle': '放弃更改?',
+ 'channels.edit.discardHelp': '未保存的渠道更改将会丢失。',
+ 'workflowActivityVNext.nav.channels': '渠道',
+ 'channels.title': '将渠道连接到你的 Agent',
+ 'channels.description': '将你的 NyxID Bot 连接到 Aevatar。',
+ 'channels.intro': '连接后,即可直接在渠道中与你的 Aevatar 机器人对话。',
+ 'channels.available': '可用渠道',
+ 'channels.availableNow': '可用',
+ 'channels.soon': '即将支持',
+ 'channels.connect': '连接',
+ 'channels.connectPlatform': '连接 {platform}',
+ 'channels.platform.feishu': '飞书',
+ 'channels.platform.telegram.description':
+ '使用 BotFather 机器人令牌接入,Webhook 自动配置。',
+ 'channels.platform.whatsapp.description': '在 WhatsApp 中与你的机器人对话。',
+ 'channels.connected': '已连接',
+ 'channels.connectedDescription': '连接到你账户的渠道。',
+ 'channels.name.loading': '正在加载名称…',
+ 'channels.name.unavailable': '名称暂不可用',
+ 'channels.name.retry': '重新加载渠道名称',
+ 'channels.column.name': 'Channel 名字',
+ 'channels.skill.notSet': '未设置',
+ 'channels.skill.version': '版本 {version}',
+ 'channels.skill.open': '在 Ornn 中打开 {name}',
+ 'channels.identifier.showFull': '查看完整 ID',
+ 'channels.column.channel': '渠道',
+ 'channels.column.skill': '技能',
+ 'channels.column.inbound': '消息入站',
+ 'channels.column.delivery': '工作流回传',
+ 'channels.column.actions': '操作',
+ 'channels.manage': '管理',
+ 'channels.managePlatform': '{platform} · 管理',
+ 'channels.refresh': '刷新渠道',
+ 'channels.loading': '正在加载渠道',
+ 'channels.status.checking': '查询中…',
+ 'channels.status.active': '活跃',
+ 'channels.status.pending': '等待消息',
+ 'channels.status.error': '需要处理',
+ 'channels.status.unknown': '未知',
+ 'channels.delivery.ready': '就绪',
+ 'channels.delivery.failed': '修复失败',
+ 'channels.delivery.repairing': '修复中',
+ 'channels.delivery.repairRequired': '需要修复',
+ 'channels.delivery.notEnabled': '未启用',
+ 'channels.empty.title': '尚未连接渠道',
+ 'channels.empty.description': '在上方连接 Telegram,开始与你的 Agent 对话。',
+ 'channels.retry': '重试',
+ 'channels.error.load': '无法加载渠道,请重试。',
+ 'channels.error.refresh': '无法刷新渠道,请重试。',
+ 'channels.error.names': '无法加载 Channel 名称,请点击刷新重试。',
+ 'channels.error.unavailable': '此渠道不可用或你没有访问权限。',
+ 'channels.details': '渠道详情',
+ 'channels.details.loading': '正在加载渠道详情',
+ 'channels.breadcrumb': '渠道导航',
+ 'channels.registration': '接入记录',
+ 'channels.inboundMessages': '消息入站',
+ 'channels.services.title': '已授权服务',
+ 'channels.services.loading': '正在加载服务名称',
+ 'channels.services.empty': '未授权任何服务。',
+ 'channels.services.default': '使用 NyxID 默认授权,未列出具体服务。',
+ 'channels.services.unavailable': '此渠道的授权详情暂不可用。',
+ 'channels.services.namesError': '无法加载服务名称,请重试。',
+ 'channels.botId': '机器人 ID',
+ 'channels.provider': '提供方',
+ 'channels.keyId': 'Agent 密钥 ID',
+ 'channels.openInNyxID': '在 NyxID 中打开{label} {id}(新标签页)',
+ 'channels.back': '返回渠道',
+ 'channels.remove': '移除',
+ 'channels.cancel': '取消',
+ 'channels.remove.title': '移除此渠道?',
+ 'channels.remove.description':
+ '此机器人将不再向 Aevatar 转发消息。重新连接时,需要再次完成接入设置。',
+ 'channels.remove.error': '无法移除此渠道,请重试。',
+ 'channels.remove.pending': '已提交移除请求,正在等待渠道列表确认更新。',
+ 'channels.remove.confirming': '正在确认移除结果…',
+ 'channels.remove.check': '重新检查',
+ 'channels.remove.success': '渠道已移除。',
+ 'channels.remove.warning': '渠道已移除,部分清理操作需要在 NyxID 中处理。',
+ 'channels.connect.title': '连接 Telegram',
+ 'channels.connect.description':
+ '添加机器人,选择它可以使用的服务。Telegram Webhook 将自动配置。',
+ 'channels.connect.botDetails': '机器人信息',
+ 'channels.connect.botToken': '机器人令牌',
+ 'channels.connect.showToken': '显示机器人令牌',
+ 'channels.connect.hideToken': '隐藏机器人令牌',
+ 'channels.connect.botFather': '从 BotFather 获取令牌',
+ 'channels.connect.name': 'Channel 名字',
+ 'channels.connect.skillName': 'Skill',
+ 'channels.connect.skillNameDefault': '默认使用 Telegram 机器人名字',
+ 'channels.connect.optional': '(可选)',
+ 'channels.connect.botNameDefault': '默认使用机器人名字 + 6 位随机数字',
+ 'channels.connect.tokenRequired': '请输入从 BotFather 获取的机器人令牌。',
+ 'channels.connect.services': '服务',
+ 'channels.connect.selected': '已选 {count} 项',
+ 'channels.connect.selectAll': '全选',
+ 'channels.connect.selectAllResults': '全选搜索结果',
+ 'channels.connect.servicesHelp': '仅显示当前 NyxID 授权范围内可用的服务。',
+ 'channels.connect.servicesLoading': '正在加载服务',
+ 'channels.connect.servicesError': '无法加载服务,请重试后再连接。',
+ 'channels.connect.servicesEmpty': '当前 NyxID 授权范围内暂无可用服务。',
+ 'channels.connect.serviceRequired': '必选',
+ 'channels.connect.requiredServicesMissing':
+ '必选服务不可用:{services}。请检查 NyxID 访问权限后重试。',
+ 'channels.edit.replaceDefaults':
+ '保存后将使用所选服务替代 NyxID 默认授权范围。',
+ 'channels.connect.searchServices': '按名称或标识搜索服务',
+ 'channels.connect.serviceUnavailable': '不可用',
+ 'channels.connect.personal': '个人',
+ 'channels.connect.organization': '组织',
+ 'channels.connect.noMatches': '没有匹配的服务。',
+ 'channels.connect.selectionHelp': '此机器人只能使用选中的服务。',
+ 'channels.connect.selectionChanged':
+ '部分选中服务已不可用,请取消勾选后再连接。',
+ 'channels.connect.pending':
+ '请求已提交。可点击再次检查,准备完成后将打开你的渠道详情。',
+ 'channels.connect.check': '再次检查',
+ 'channels.connect.success': 'Telegram 渠道已创建。',
+ 'channels.connect.error.confirmation': '暂时无法查看渠道,请再次检查。',
+ 'channels.connect.submitted': '请求已提交',
+ 'channels.connect.error.token': '请检查 BotFather 提供的令牌后重试。',
+ 'channels.connect.error.botName':
+ '暂时无法获取 Telegram 机器人名称,请重试。',
+ 'channels.connect.error.services': '所选服务已不可用,请重新检查选择后重试。',
+ 'channels.connect.error.skill':
+ '无法配置机器人 Skill,请检查 Skill 名称和你在 Ornn 中的访问权限。',
+ 'channels.connect.error.authorization':
+ '登录状态或服务权限需要处理,请重新登录并检查 NyxID 访问权限。',
+ 'channels.connect.error.conflict':
+ '此机器人可能已连接,请先查看渠道列表再重试。',
+ 'channels.connect.error.configuration':
+ '渠道接入暂不可用,请联系管理员检查部署配置。',
+ 'channels.connect.error.rejected':
+ '无法连接 Telegram,请检查机器人令牌、Skill 名称和所选服务后重试。',
+ 'channels.connect.error.uncertain': '暂时无法确认连接结果,请重试。',
+ 'channels.connect.discardTitle': '放弃此次连接设置?',
+ 'channels.connect.discard': '放弃',
+ 'channels.connect.stay': '继续填写',
+ 'channels.connect.discardHelp': '机器人令牌和未保存的选择将被清除。',
+};
diff --git a/apps/aevatar-console-web/src/locales/en-US.ts b/apps/aevatar-console-web/src/locales/en-US.ts
index b9abd7d698..33f9512039 100644
--- a/apps/aevatar-console-web/src/locales/en-US.ts
+++ b/apps/aevatar-console-web/src/locales/en-US.ts
@@ -1,12 +1,20 @@
+import channelMessages from './channelMessages.en-US';
import projectMessages from './projectMessages.en-US';
+import workflowActivityVNextMessages from './workflowActivityVNextMessages.en-US';
const enUSMessages = {
...projectMessages,
+ ...channelMessages,
+ 'workflowCanvasBenchmark.title': 'Workflow canvas benchmark',
+ ...workflowActivityVNextMessages,
'common.appName': 'Aevatar',
+ 'console.home.unavailable': 'Could not open your workspace',
+ 'console.home.retry': 'Try again',
'common.language.english': 'English',
'common.language.label': 'Language',
'common.language.switch': 'Switch language',
'common.language.zhCN': '中文',
+ 'common.user.account': 'Account',
'common.user.logout': 'Logout',
'common.user.settings': 'Settings',
'common.user.signIn': 'Sign in',
@@ -58,7 +66,8 @@ const enUSMessages = {
'pages.chat.index.deleteChatTitle': 'Delete conversation?',
'pages.chat.index.emptyDescription':
'Describe the Team, Member, or Workflow you want to create.',
- 'pages.chat.index.failedToLoadConversation': 'Conversation could not be loaded',
+ 'pages.chat.index.failedToLoadConversation':
+ 'Conversation could not be loaded',
'pages.chat.index.failedToLoadHistory': 'Chat history could not be loaded',
'pages.chat.index.historyStoredInWorkspace':
'History is saved to this workspace.',
@@ -173,16 +182,15 @@ const enUSMessages = {
'pages.missionwall.status.unknown': 'UNKNOWN',
'pages.missionwall.status.waiting': 'WAIT',
'pages.missionwall.state.auditLoading': 'Loading selected run',
- 'pages.missionwall.state.auditPendingTitle':
- 'No step flow for this run yet',
+ 'pages.missionwall.state.auditPendingTitle': 'No step flow for this run yet',
'pages.missionwall.state.emptyKicker': 'Waiting for runs',
'pages.missionwall.state.emptyTitle': 'No published workflows are visible',
'pages.missionwall.state.loadingKicker': 'Loading runtime',
'pages.missionwall.state.loadingTitle': 'Loading workflow runs',
- 'pages.missionwall.state.publishedWorkflowTitle':
- 'No visible run',
+ 'pages.missionwall.state.publishedWorkflowTitle': 'No visible run',
'pages.missionwall.stageSubtitle': 'Team {teamName} · {memberName}',
'pages.missionwall.stageTitle': '{workflowName} · Step Flow',
+ 'pages.missionwall.stepFlow': 'Step Flow',
'pages.missionwall.stepStatus.active': 'ACTIVE',
'pages.missionwall.stepStatus.completed': 'COMPLETED',
'pages.missionwall.stepStatus.failed': 'FAILED',
@@ -716,7 +724,14 @@ const enUSMessages = {
'Value matched against branch keys such as bug, feature, or _default.',
'shared.studio.nodeConfiguration.switch.on.label': 'Switch on',
'shared.studio.nodeConfiguration.switch.on.placeholder': '$input',
- 'shared.studio.nodeConfiguration.toolCall.tool.label': 'Tool',
+ 'shared.studio.nodeConfiguration.toolCall.arguments.description':
+ "Property names and value types must match the tool's expected input.",
+ 'shared.studio.nodeConfiguration.toolCall.arguments.label': 'Arguments',
+ 'shared.studio.nodeConfiguration.toolCall.arguments.placeholder':
+ '\'{\'"query":"$input"\'}\'',
+ 'shared.studio.nodeConfiguration.toolCall.tool.description':
+ "Use the exact name from the workflow template or target role's tool setup, for example web_search.",
+ 'shared.studio.nodeConfiguration.toolCall.tool.label': 'Tool name',
'shared.studio.nodeConfiguration.toolCall.tool.placeholder': 'web_search',
'shared.studio.nodeConfiguration.transform.operation.label': 'Operation',
'shared.studio.nodeConfiguration.transform.operation.option.count':
@@ -758,6 +773,9 @@ const enUSMessages = {
'shared.studio.nodeConfiguration.workflowCall.workflow.label': 'Workflow',
'shared.studio.nodeConfiguration.workflowCall.workflow.placeholder':
'child_workflow',
+ 'shared.workflowExecutionLogs.collapse': 'Collapse workflow logs',
+ 'shared.workflowExecutionLogs.consoleAria': 'Workflow run console',
+ 'shared.workflowExecutionLogs.tokenUsage': 'Token usage',
'teams.members.actions.build': 'Build',
'teams.members.actions.clearEntry': 'Clear entry member',
'teams.members.actions.create': 'Create member',
@@ -810,8 +828,10 @@ const enUSMessages = {
'teams.automations.authorization.error': 'Authorization could not continue',
'teams.automations.authorization.exactAccess': 'Exact access',
'teams.automations.authorization.expiry': 'Credential expiry',
- 'teams.automations.authorization.noExternalGrants': 'No external NyxID service or owner LLM model grant is required.',
- 'teams.automations.authorization.noOwnerLLMGrant': 'No owner LLM model grant is required for this workflow.',
+ 'teams.automations.authorization.noExternalGrants':
+ 'No external NyxID service or owner LLM model grant is required.',
+ 'teams.automations.authorization.noOwnerLLMGrant':
+ 'No owner LLM model grant is required for this workflow.',
'teams.automations.authorization.preparing': 'Preparing authorization review',
'teams.automations.authorization.review': 'Review authorization',
'teams.automations.authorization.reviewAgain': 'Review again',
@@ -820,10 +840,14 @@ const enUSMessages = {
'teams.automations.authorizationStatus.active': 'Credential active',
'teams.automations.authorizationStatus.deleting': 'Deleting',
'teams.automations.authorizationStatus.failed': 'Authorization failed',
- 'teams.automations.authorizationStatus.needs_authorization': 'Authorization required',
- 'teams.automations.authorizationStatus.provisioning_pending': 'Preparing authorization',
- 'teams.automations.authorizationStatus.replacement_pending': 'Replacing authorization',
- 'teams.automations.authorizationStatus.revocation_pending': 'Revocation pending',
+ 'teams.automations.authorizationStatus.needs_authorization':
+ 'Authorization required',
+ 'teams.automations.authorizationStatus.provisioning_pending':
+ 'Preparing authorization',
+ 'teams.automations.authorizationStatus.replacement_pending':
+ 'Replacing authorization',
+ 'teams.automations.authorizationStatus.revocation_pending':
+ 'Revocation pending',
'teams.automations.columns.actions': 'Actions',
'teams.automations.columns.automation': 'Automation',
'teams.automations.columns.member': 'Member',
@@ -916,8 +940,7 @@ const enUSMessages = {
'teams.automations.loading': 'Loading automations',
'teams.automations.member.publishFirst':
'Publish this member before adding recurring work.',
- 'teams.automations.member.publishedServiceReady':
- 'Published service ready',
+ 'teams.automations.member.publishedServiceReady': 'Published service ready',
'teams.automations.member.unknown': 'Unknown member',
'teams.automations.memberDescription':
'Dedicated Agent Keys are authorized per schedule and held by Aevatar.',
@@ -927,7 +950,8 @@ const enUSMessages = {
'teams.automations.memberTitle': '{memberName} automations',
'teams.automations.memberUnavailable.description':
'This member is not part of the current Team. Choose another member.',
- 'teams.automations.memberUnavailable.title': 'Member unavailable for automation',
+ 'teams.automations.memberUnavailable.title':
+ 'Member unavailable for automation',
'teams.automations.member.workflowOnly':
'Only workflow members can have recurring work.',
'teams.automations.schedule.next': 'Next {time}',
@@ -937,12 +961,16 @@ const enUSMessages = {
'teams.automations.messages.createSuccess': 'Automation created.',
'teams.automations.messages.authorizationAccepted':
'Authorization request accepted',
+ 'teams.automations.messages.actionFailed':
+ 'Automation action could not be completed.',
'teams.automations.messages.deleteAccepted': 'Delete request accepted',
'teams.automations.messages.cronRequired': 'Enter a cron expression first.',
'teams.automations.messages.deleteSuccess': 'Automation deleted.',
'teams.automations.messages.disableSuccess': 'Automation paused.',
'teams.automations.messages.enableSuccess': 'Automation resumed.',
'teams.automations.messages.previewFailed': 'Preview failed: {message}',
+ 'teams.automations.messages.previewFailedGeneric':
+ 'Could not preview scheduled runs.',
'teams.automations.messages.promptTooLong':
'Recurring prompt must be {maxLength} characters or fewer.',
'teams.automations.messages.runNowFailed': 'Run request failed: {message}',
@@ -959,6 +987,8 @@ const enUSMessages = {
'The selected member does not have a service identity yet.',
'teams.automations.messages.updateFailed':
'Automation was not updated: {message}',
+ 'teams.automations.messages.updateFailedGeneric':
+ 'Automation could not be updated.',
'teams.automations.messages.updateSuccess': 'Automation updated.',
'teams.automations.noPublishedMember.description':
'Automations need a member with a published service identity before they can run.',
@@ -1147,6 +1177,16 @@ const enUSMessages = {
'Studio resolved draft workflow {workflowId}, but loading it failed: {reason}',
'teamMemberWorkflowStudio.alerts.linkedWorkflowLoadFailed.title':
'Workflow draft could not be loaded.',
+ 'teamMemberWorkflowStudio.alerts.actionError.title':
+ 'Workflow changes need attention',
+ 'teamMemberWorkflowStudio.alerts.draftSaveFailed.description':
+ "We couldn't save the workflow draft. Review your changes and try again.",
+ 'teamMemberWorkflowStudio.alerts.memberCreateFailed.description':
+ "We couldn't create the workflow member. Review your changes and try again.",
+ 'teamMemberWorkflowStudio.alerts.memberLinkFailed.description':
+ "We couldn't finish linking the workflow member. Your draft is still available. Save again to retry.",
+ 'teamMemberWorkflowStudio.alerts.saveAndPublishFailed.description':
+ "We couldn't validate and publish the workflow. Review the workflow and try again.",
'teamMemberWorkflowStudio.common.close': 'Close',
'teamMemberWorkflowStudio.executionPanel.consoleAria': 'Draft run console',
'teamMemberWorkflowStudio.explicitRequest.approval': 'Approval',
@@ -1170,6 +1210,7 @@ const enUSMessages = {
'teamMemberWorkflowStudio.executionPanel.clear': 'Clear logs',
'teamMemberWorkflowStudio.executionPanel.copyAll': 'Copy all logs',
'teamMemberWorkflowStudio.executionPanel.copyAllDone': 'Copied all logs.',
+ 'teamMemberWorkflowStudio.executionPanel.copyFailed': 'Could not copy logs.',
'teamMemberWorkflowStudio.executionPanel.copySelected': 'Copy selected log',
'teamMemberWorkflowStudio.executionPanel.copySelectedDone':
'Copied selected log.',
@@ -1205,13 +1246,14 @@ const enUSMessages = {
'teamMemberWorkflowStudio.executionPanel.output': 'Output',
'teamMemberWorkflowStudio.executionPanel.overview': 'Overview',
'teamMemberWorkflowStudio.executionPanel.rawFrames':
- '{count} run event(s) received, but no node output is available yet.',
+ '{count} run event(s) received. Waiting for the first node to start.',
'teamMemberWorkflowStudio.executionPanel.resultFirst': 'Result',
'teamMemberWorkflowStudio.executionPanel.runLog': 'Run log',
'teamMemberWorkflowStudio.executionPanel.selectLog':
'Select a log entry to inspect its input, output, and raw event data.',
'teamMemberWorkflowStudio.executionPanel.steps': 'Steps',
'teamMemberWorkflowStudio.executionPanel.status.error': 'Error',
+ 'teamMemberWorkflowStudio.executionPanel.status.notRun': 'Not run',
'teamMemberWorkflowStudio.executionPanel.status.pending': 'Pending',
'teamMemberWorkflowStudio.executionPanel.status.recorded': 'Recorded',
'teamMemberWorkflowStudio.executionPanel.status.running': 'Running',
@@ -1226,10 +1268,8 @@ const enUSMessages = {
'No safely scoped executions are available for this workflow member.',
'teamMemberWorkflowStudio.executionsPanel.fallbackName': 'Workflow execution',
'teamMemberWorkflowStudio.executionsPanel.inspect': 'Inspect',
- 'teamMemberWorkflowStudio.executionsPanel.sectionAria':
- 'Workflow executions',
- 'teamMemberWorkflowStudio.executionsPanel.serviceMeta':
- 'Service ready',
+ 'teamMemberWorkflowStudio.executionsPanel.sectionAria': 'Workflow executions',
+ 'teamMemberWorkflowStudio.executionsPanel.serviceMeta': 'Service ready',
'teamMemberWorkflowStudio.executionsPanel.title': 'Executions',
'teamMemberWorkflowStudio.executionsPanel.unknownStatus': 'unknown',
'teamMemberWorkflowStudio.graph.branchCount.one': '1 branch',
@@ -1391,6 +1431,31 @@ const enUSMessages = {
'teamMemberWorkflowStudio.runsPanel.sectionAria': 'Member runs',
'teamMemberWorkflowStudio.runsPanel.title': 'Member runs',
'teamMemberWorkflowStudio.runsPanel.unknownStatus': 'unknown',
+ 'teamMemberWorkflowStudio.toast.draftRunCompleted': 'Draft run completed.',
+ 'teamMemberWorkflowStudio.toast.draftRunFailed':
+ 'Draft run failed. Review the result and try again.',
+ 'teamMemberWorkflowStudio.toast.draftSaveFailed':
+ 'Could not save the workflow draft. Review the details and try again.',
+ 'teamMemberWorkflowStudio.toast.draftSaved': 'Workflow draft saved.',
+ 'teamMemberWorkflowStudio.toast.memberCreateFailed':
+ 'Could not create the workflow member. Review the details and try again.',
+ 'teamMemberWorkflowStudio.toast.memberLinkFailed':
+ 'Could not finish linking the workflow member. Save again to retry.',
+ 'teamMemberWorkflowStudio.toast.memberCreated': 'Workflow member created.',
+ 'teamMemberWorkflowStudio.toast.memberPublished':
+ 'Workflow member published.',
+ 'teamMemberWorkflowStudio.toast.publishFailed':
+ 'Could not publish the workflow member. Review the details and try again.',
+ 'teamMemberWorkflowStudio.toast.publishedMemberStatusRefreshed':
+ 'Published member status refreshed.',
+ 'teamMemberWorkflowStudio.toast.publishedWorkflowSaved':
+ 'Published workflow saved.',
+ 'teamMemberWorkflowStudio.toast.publishStatusFailed':
+ 'Could not refresh the publish status. Review the details and try again.',
+ 'teamMemberWorkflowStudio.toast.saveAndPublishFailed':
+ 'Could not save and publish the workflow. Review the details and try again.',
+ 'teamMemberWorkflowStudio.toast.yamlApplyFailed':
+ 'Could not apply the YAML changes. Review the details and try again.',
'teamMemberWorkflowStudio.yamlPanel.apply': 'Apply to draft',
'teamMemberWorkflowStudio.yamlPanel.cancel': 'Cancel',
'teamMemberWorkflowStudio.yamlPanel.closeAria': 'Close YAML editor',
@@ -1507,6 +1572,22 @@ const enUSMessages = {
'pages.runs.memberPublishedRuns.event': 'Event',
'pages.runs.memberPublishedRuns.member': 'Team member',
'pages.runs.memberPublishedRuns.step': 'Step',
+ 'pages.runs.index.invocation.accepted.waiting.for.activity':
+ 'Invocation accepted. Waiting for activity.',
+ 'pages.runs.index.prompt.required.before.sending':
+ 'Enter a prompt before sending.',
+ 'pages.runs.index.resume.request.accepted.waiting.for.run':
+ 'Resume request accepted. Waiting for the run to continue.',
+ 'pages.runs.index.resume.request.not.accepted':
+ 'The resume request was not accepted. Try again.',
+ 'pages.runs.index.run.request.failed': 'Could not start the run.',
+ 'pages.runs.index.selected.service.unavailable':
+ 'The selected service is unavailable. Retrying with the workspace default.',
+ 'pages.runs.index.signal.accepted.waiting.for.run':
+ 'Signal accepted. Waiting for the run to continue.',
+ 'pages.runs.index.signal.not.accepted':
+ 'The signal was not accepted. Try again.',
+ 'pages.runs.index.stop.failed': 'Could not stop the run.',
'pages.runs.runsinspectorpane.actor': 'Actor',
'pages.runs.runsinspectorpane.command': 'Command',
'pages.runs.runsinspectorpane.command.accepted': 'Command accepted',
@@ -1540,24 +1621,98 @@ const enUSMessages = {
'pages.scopes.invoke.selected.service': 'the selected service',
'pages.scopes.invoke.service': 'Service',
'pages.scopes.invoke.version.ready': 'Version ready',
- 'pages.scopes.scopeserviceruntimeworkbench.actor.available': 'Actor available',
+ 'pages.scopes.scopeserviceruntimeworkbench.actor.available':
+ 'Actor available',
'pages.scopes.scopeserviceruntimeworkbench.binding': 'Binding',
'pages.scopes.scopeserviceruntimeworkbench.binding.accepted.for.retirement':
'Binding was accepted for retirement.',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.change.accepted':
+ 'Binding change was accepted. Check the list for its current status.',
'pages.scopes.scopeserviceruntimeworkbench.binding.created':
'Binding was created.',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.id.label': 'Binding ID',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.id.required':
+ 'Enter a binding ID.',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.kind.connector':
+ 'Connector',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.kind.label':
+ 'Binding type',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.kind.secret': 'Secret',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.kind.service': 'Service',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.retirement.failed':
+ 'Unable to request binding retirement. Try again.',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.save.failed':
+ 'Unable to save this binding. Try again.',
'pages.scopes.scopeserviceruntimeworkbench.binding.updated':
'Binding was updated.',
+ 'pages.scopes.scopeserviceruntimeworkbench.bindings.preparing.description':
+ 'Refresh to see when bindings are ready.',
+ 'pages.scopes.scopeserviceruntimeworkbench.bindings.preparing.title':
+ 'Bindings are still being prepared',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.accepted.description':
+ 'Checking the latest status.',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.accepted.title':
+ 'Request accepted',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.delayed.description':
+ 'The latest list has not reflected this request yet. Refresh to check again.',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.delayed.title':
+ 'Update is still pending.',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.failed.description':
+ 'Refresh the latest status before trying again.',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.failed.title':
+ 'Could not confirm the update',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.observed.description':
+ 'The latest list reflects this change.',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.observed.title':
+ 'Update confirmed.',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.observing.description':
+ 'Waiting for the latest list to update.',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.observing.title':
+ 'Checking current status',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.dismiss': 'Dismiss',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.refresh': 'Refresh',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.refreshing.description':
+ 'Checking the latest list.',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.refreshing.title':
+ 'Refreshing current status',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.submitting.description':
+ 'Sending your update.',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.submitting.title':
+ 'Saving update',
+ 'pages.scopes.scopeserviceruntimeworkbench.connector.id.label':
+ 'Connector ID',
+ 'pages.scopes.scopeserviceruntimeworkbench.connector.id.required':
+ 'Enter a connector ID.',
+ 'pages.scopes.scopeserviceruntimeworkbench.connector.type.label':
+ 'Connector type',
+ 'pages.scopes.scopeserviceruntimeworkbench.connector.type.required':
+ 'Enter a connector type.',
+ 'pages.scopes.scopeserviceruntimeworkbench.create.binding': 'Create binding',
+ 'pages.scopes.scopeserviceruntimeworkbench.display.name.label':
+ 'Display name',
'pages.scopes.scopeserviceruntimeworkbench.endpoint': 'Endpoint',
'pages.scopes.scopeserviceruntimeworkbench.endpoints': 'endpoints',
'pages.scopes.scopeserviceruntimeworkbench.policy.count': '{value1} policies',
+ 'pages.scopes.scopeserviceruntimeworkbench.policy.ids.label': 'Policy IDs',
'pages.scopes.scopeserviceruntimeworkbench.ready': 'Ready',
'pages.scopes.scopeserviceruntimeworkbench.revision.accepted.for.retirement':
'Revision was accepted for retirement.',
+ 'pages.scopes.scopeserviceruntimeworkbench.revision.retirement.failed':
+ 'Unable to request revision retirement. Try again.',
'pages.scopes.scopeserviceruntimeworkbench.service': 'Service',
'pages.scopes.scopeserviceruntimeworkbench.service.target': 'Service target',
+ 'pages.scopes.scopeserviceruntimeworkbench.save.binding': 'Save binding',
+ 'pages.scopes.scopeserviceruntimeworkbench.secret.name.label': 'Secret name',
+ 'pages.scopes.scopeserviceruntimeworkbench.secret.name.required':
+ 'Enter a secret name.',
'pages.scopes.scopeserviceruntimeworkbench.serving.version.ready':
'serving version ready',
+ 'pages.scopes.scopeserviceruntimeworkbench.target.endpoint.optional.label':
+ 'Target endpoint (optional)',
+ 'pages.scopes.scopeserviceruntimeworkbench.target.service.label':
+ 'Target service',
+ 'pages.scopes.scopeserviceruntimeworkbench.target.service.required':
+ 'Select a target service.',
'pages.scopes.scopeserviceruntimeworkbench.version': 'Version',
'pages.services.index.artifact.attached': 'Artifact attached',
'pages.services.index.endpoint': 'Endpoint',
@@ -1574,6 +1729,8 @@ const enUSMessages = {
'pages.studio.bind.studiomemberbindpanel.revision.ready': 'Revision ready',
'pages.studio.bind.studiomemberbindpanel.run.completed': 'Run completed',
'pages.studio.bind.studiomemberbindpanel.service.ready': 'Service ready',
+ 'pages.studio.bind.studiomemberbindpanel.smoke.test.request.failed':
+ 'Could not complete the smoke test. Try again.',
'pages.studio.studiobuildpanels.command.accepted': 'command: accepted',
'pages.studio.studiobuildpanels.current.run.ready': 'current run: ready',
'pages.studio.studiobuildpanels.events.count': 'events: {count}',
@@ -1587,6 +1744,8 @@ const enUSMessages = {
'pages.studio.studiobuildpanels.script': 'Script',
'pages.studio.studiobuildpanels.script.applied': 'Applied script',
'pages.studio.studiobuildpanels.script.draft': 'Script draft',
+ 'pages.studio.studiobuildpanels.workflowActionFailed':
+ 'Could not complete the workflow action. Review the details and try again.',
'pages.studio.studiobuildpanels.script.promotion.version.summary':
'Script promotion version summary',
'pages.studio.studiobuildpanels.version.ready': 'version ready',
@@ -1622,8 +1781,7 @@ const enUSMessages = {
'pages.studio.studioinvokediagnosticsdrawer.endpoint.ready': 'Endpoint ready',
'pages.studio.studiomemberinvokeinspector.service.ready': 'Service ready',
'pages.studio.studiomemberinvokeinspector.version.ready': 'Version ready',
- 'pages.studio.studioworkbenchsections.runtime.available':
- 'Runtime available',
+ 'pages.studio.studioworkbenchsections.runtime.available': 'Runtime available',
'pages.studio.studioworkbenchsections.runtime.available.2':
'Runtime available',
'pages.studio.studioworkbenchsections.runtime.available.3':
@@ -1632,6 +1790,36 @@ const enUSMessages = {
'Runtime available',
'pages.studio.studioworkbenchsections.runtime.facts.available':
'Runtime facts available',
+ 'pages.studio.studiomemberinvokepanel.copyFailed':
+ 'Could not copy this value.',
+ 'pages.workflows.workflowyamlviewer.copy.success':
+ 'YAML copied to clipboard.',
+ 'pages.workflows.workflowyamlviewer.failed.to.copy.yaml':
+ 'Failed to copy YAML.',
+ 'pages.chat.chatadvancedconsole.timelineActionFailed':
+ 'Run action could not be completed. Try again.',
+ 'pages.deployments.index.actionFailed':
+ 'Deployment action could not be completed. Try again.',
+ 'pages.gagents.index.bindingActionFailed':
+ 'Binding action could not be completed. Try again.',
+ 'pages.gagents.index.registryActionFailed':
+ 'Actor registry action could not be completed. Try again.',
+ 'pages.governance.governanceworkbench.actionFailed':
+ 'Governance action could not be completed. Try again.',
+ 'pages.studio.bind.studiomemberbindpanel.bindingActionFailed':
+ 'Binding action could not be completed. Try again.',
+ 'pages.studio.studiobuildpanels.workflowSaveFailed':
+ 'Could not save workflow. Try again.',
+ 'pages.studio.studiofilesdetailpane.connectorSaveFailed':
+ 'Could not save connector catalog. Try again.',
+ 'pages.studio.studiofilesdetailpane.conversationDeleteFailed':
+ 'Could not delete conversation. Try again.',
+ 'pages.studio.studiofilesdetailpane.roleSaveFailed':
+ 'Could not save role catalog. Try again.',
+ 'pages.studio.studioinspectorpane.nodeActionFailed':
+ 'Could not apply node changes. Try again.',
+ 'pages.studio.studioworkbenchsections.executionActionFailed':
+ 'Execution action could not be completed. Try again.',
'menu.WorkOrder Details': 'Request details',
'teams.detail.tabs.workOrders': 'Requests',
'workOrders.actions.backToRequests': 'Back to requests',
diff --git a/apps/aevatar-console-web/src/locales/projectMessages.en-US.ts b/apps/aevatar-console-web/src/locales/projectMessages.en-US.ts
index 9cbefe0cdd..cd185ce144 100644
--- a/apps/aevatar-console-web/src/locales/projectMessages.en-US.ts
+++ b/apps/aevatar-console-web/src/locales/projectMessages.en-US.ts
@@ -2314,6 +2314,7 @@ const projectMessages = {
"pages.primitives.index.exposed.parameters": "Exposed parameters",
"pages.primitives.index.filter.categories": "Filter categories",
"pages.primitives.index.filter.connector": "Filter connector",
+ "pages.primitives.index.loading.connectors": "Loading connectors",
"pages.primitives.index.none": "None",
"pages.primitives.index.open.behavior.definition": "Open behavior definition",
"pages.primitives.index.optional": "Optional",
@@ -3160,6 +3161,7 @@ const projectMessages = {
"pages.settings.index.runtime.url": "Runtime URL",
"pages.settings.index.save.config": "Save config",
"pages.settings.index.save.failed": "Save failed",
+ "pages.settings.index.save.failed.toast": "Settings could not be saved. Try again.",
"pages.settings.index.save.not.accepted": "The settings write was not accepted.",
"pages.settings.index.save.pending": "Save pending",
"pages.settings.index.saved.route": "Saved route",
@@ -3450,8 +3452,10 @@ const projectMessages = {
"pages.studio.explorer.explorercontentview.script.package": "Script package",
"pages.studio.explorer.explorercontentview.thinking": "Thinking",
"pages.studio.explorer.explorerdetailpane.could.not.delete.file": "Could not delete file",
+ "pages.studio.explorer.explorerdetailpane.could.not.delete.file.toast": "Could not delete file. Try again.",
"pages.studio.explorer.explorerdetailpane.could.not.load.file": "Could not load file",
"pages.studio.explorer.explorerdetailpane.could.not.save.file": "Could not save file",
+ "pages.studio.explorer.explorerdetailpane.could.not.save.file.toast": "Could not save file. Try again.",
"pages.studio.explorer.explorerdetailpane.delete": "Delete",
"pages.studio.explorer.explorerdetailpane.delete.now": "Delete now",
"pages.studio.explorer.explorerdetailpane.delete.this.file.from.storage": "Delete this file from Storage Explorer?",
@@ -3491,7 +3495,7 @@ const projectMessages = {
"pages.studio.index.bind.the.selected.typed.gagent.2": "Bind the selected typed GAgent as this member service, then Studio can reveal the invoke URL and endpoint contract.",
"pages.studio.index.binding.request.accepted.running": "{displayName} binding request was accepted and is still running.{status} Studio will keep refreshing the status before treating it as bound.",
"pages.studio.index.binding.request.accepted.running.with.freshness": "{displayName} binding request was accepted and is still running.{freshness} Studio will keep refreshing the status before treating it as bound.",
- "pages.studio.index.binding.request.was.accepted.and.is": "{value1} binding request was accepted and is still running.{value2}{value3} Studio will keep refreshing the status before treating it as bound.",
+ "pages.studio.index.binding.request.was.accepted.and.is": "{value1} binding request was accepted. Studio is waiting for the published contract before treating it as bound.{value2}{value3}",
"pages.studio.index.build.gagent.prompt": "Define GAgent type, role, initial prompt, tools and state persistence within Build",
"pages.studio.index.build.member.workbench.authoring": "The Build phase first determines which implementation method is used for the current member, and then directly completes authoring and dry-run in the same workbench.",
"pages.studio.index.call.the.current.member": "Call the current member and retain the running observation context",
@@ -4734,7 +4738,6 @@ const projectMessages = {
"pages.studio.studioworkbenchsections.wait.for.external.signal": "Wait for external signal",
"pages.studio.studioworkbenchsections.waiting.for.execution": "Waiting for execution",
"pages.studio.studioworkbenchsections.waiting.for.manual.approval": "Waiting for manual approval",
- "pages.studio.studioworkbenchsections.waiting.for.manual.input": "Waiting for manual input",
"pages.studio.studioworkbenchsections.warming.up": "Warming up",
"pages.studio.studioworkbenchsections.workflow.graph.unavailable": "Workflow graph unavailable.",
"pages.studio.studioworkbenchsections.workflow.graph.unavailable.for.this": "Workflow graph unavailable for this member.",
diff --git a/apps/aevatar-console-web/src/locales/projectMessages.zh-CN.ts b/apps/aevatar-console-web/src/locales/projectMessages.zh-CN.ts
index d570b469b6..66a9aec042 100644
--- a/apps/aevatar-console-web/src/locales/projectMessages.zh-CN.ts
+++ b/apps/aevatar-console-web/src/locales/projectMessages.zh-CN.ts
@@ -2314,6 +2314,7 @@ const projectMessages = {
"pages.primitives.index.exposed.parameters": "个已暴露参数",
"pages.primitives.index.filter.categories": "筛选分类",
"pages.primitives.index.filter.connector": "筛选连接器",
+ "pages.primitives.index.loading.connectors": "正在加载连接器",
"pages.primitives.index.none": "无",
"pages.primitives.index.open.behavior.definition": "打开行为定义",
"pages.primitives.index.optional": "可选",
@@ -3160,6 +3161,7 @@ const projectMessages = {
"pages.settings.index.runtime.url": "运行时 URL",
"pages.settings.index.save.config": "保存配置",
"pages.settings.index.save.failed": "保存失败",
+ "pages.settings.index.save.failed.toast": "设置无法保存,请重试。",
"pages.settings.index.save.not.accepted": "设置写入未被接受。",
"pages.settings.index.save.pending": "保存待确认",
"pages.settings.index.saved.route": "已保存路由",
@@ -3450,8 +3452,10 @@ const projectMessages = {
"pages.studio.explorer.explorercontentview.script.package": "脚本包",
"pages.studio.explorer.explorercontentview.thinking": "思考",
"pages.studio.explorer.explorerdetailpane.could.not.delete.file": "无法删除文件",
+ "pages.studio.explorer.explorerdetailpane.could.not.delete.file.toast": "无法删除文件,请重试。",
"pages.studio.explorer.explorerdetailpane.could.not.load.file": "无法加载文件",
"pages.studio.explorer.explorerdetailpane.could.not.save.file": "无法保存文件",
+ "pages.studio.explorer.explorerdetailpane.could.not.save.file.toast": "无法保存文件,请重试。",
"pages.studio.explorer.explorerdetailpane.delete": "删除",
"pages.studio.explorer.explorerdetailpane.delete.now": "立即删除",
"pages.studio.explorer.explorerdetailpane.delete.this.file.from.storage": "从存储 Explorer 中删除此文件吗?",
@@ -3491,7 +3495,7 @@ const projectMessages = {
"pages.studio.index.bind.the.selected.typed.gagent.2": "将所选类型的 GAgent 绑定为此成员服务,然后 Studio 可以显示调用 URL 和 Endpoint 合约。",
"pages.studio.index.binding.request.accepted.running": "{displayName} 的绑定请求已受理,仍在运行中。{status} Studio 会继续刷新状态,确认后再视为已绑定。",
"pages.studio.index.binding.request.accepted.running.with.freshness": "{displayName} 的绑定请求已受理,仍在运行中。{freshness} Studio 会继续刷新状态,确认后再视为已绑定。",
- "pages.studio.index.binding.request.was.accepted.and.is": "{value1} 的绑定请求已接受并且仍在运行。{value2}{value3} Studio 会持续刷新状态,确认后再视为已绑定。",
+ "pages.studio.index.binding.request.was.accepted.and.is": "{value1} 的绑定请求已受理。Studio 正在等待已发布的服务合同,在确认之前不会将其视为已绑定。{value2}{value3}",
"pages.studio.index.build.gagent.prompt": "在 Build 内定义 GAgent 类型、角色、初始 Prompt、工具和状态持久化。",
"pages.studio.index.build.member.workbench.authoring": "Build 阶段先确定当前成员采用哪种实现方式,然后在同一个工作台里完成创作和试运行。",
"pages.studio.index.call.the.current.member": "调用当前成员并保留运行观察上下文",
@@ -4734,7 +4738,6 @@ const projectMessages = {
"pages.studio.studioworkbenchsections.wait.for.external.signal": "等待外部信号",
"pages.studio.studioworkbenchsections.waiting.for.execution": "等待执行",
"pages.studio.studioworkbenchsections.waiting.for.manual.approval": "等待人工审批",
- "pages.studio.studioworkbenchsections.waiting.for.manual.input": "等待人工输入",
"pages.studio.studioworkbenchsections.warming.up": "预热中",
"pages.studio.studioworkbenchsections.workflow.graph.unavailable": "Workflow 图不可用。",
"pages.studio.studioworkbenchsections.workflow.graph.unavailable.for.this": "该成员无法使用 Workflow 图。",
diff --git a/apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts b/apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts
new file mode 100644
index 0000000000..352cc11a9f
--- /dev/null
+++ b/apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.en-US.ts
@@ -0,0 +1,1040 @@
+const workflowActivityVNextMessages = {
+ 'workflowActivityVNext.home.loading': 'Opening your workflows…',
+ 'workflowActivityVNext.home.unavailable': 'Workflows unavailable',
+ 'workflowActivityVNext.home.loadFailed':
+ "We couldn't load your workspace. Try again.",
+ 'workflowActivityVNext.home.scopeUnavailable':
+ 'Your account has no available workspace. Check your access and try again.',
+ 'workflowActivityVNext.home.signInRequired':
+ 'Sign in to open your workflows.',
+ 'workflowActivityVNext.home.signIn': 'Sign in',
+
+ 'workflowActivityVNext.workflows.refreshFailed':
+ "The workflow list couldn't refresh. The last loaded list is still shown.",
+ 'workflowActivityVNext.new.cancelGeneration': 'Cancel generation',
+ 'workflowActivityVNext.new.generating': 'Generating workflow…',
+ 'workflowActivityVNext.new.validating': 'Checking generated workflow…',
+ 'workflowActivityVNext.new.savingGenerated': 'Saving workflow…',
+ 'workflowActivityVNext.new.generationTimedOut':
+ 'Generation took longer than two minutes. No draft was saved. You can try again.',
+ 'workflowActivityVNext.new.generationCancelled':
+ 'Generation cancelled. Your name and description are kept; no draft was saved.',
+ 'workflowActivityVNext.new.generationReceiving':
+ 'Generation updates received. Waiting for the completed workflow.',
+ 'workflowActivityVNext.new.generationWaiting':
+ 'Waiting for generation updates. You can cancel while you wait.',
+ 'workflowActivityVNext.new.generationDetails': 'Generation details',
+ 'workflowActivityVNext.workflows.deleteSuccess': 'Draft deleted',
+ 'workflowActivityVNext.workflows.checkFailed':
+ "Couldn't check whether the change has finished",
+ 'workflowActivityVNext.workflows.changeObserving':
+ 'Request accepted. Checking for completion…',
+ 'workflowActivityVNext.workflows.changeDelayed':
+ 'This is taking longer than expected. Completion is still unconfirmed.',
+ 'workflowActivityVNext.workflows.checkingDescription':
+ 'We will keep checking for up to 30 seconds. Closing this dialog does not undo the request.',
+ 'workflowActivityVNext.workflows.checkAgainDescription':
+ 'Check again to see the result. This only checks the status and does not submit another request.',
+ 'workflowActivityVNext.arguments.addItem': 'Add item',
+ 'workflowActivityVNext.arguments.addItemAria': 'Add item to {path}',
+ 'workflowActivityVNext.arguments.addProperty': 'Add property',
+ 'workflowActivityVNext.arguments.addPropertyAria': 'Add property to {path}',
+ 'workflowActivityVNext.arguments.columnProperty': 'Property',
+ 'workflowActivityVNext.arguments.columnType': 'Type',
+ 'workflowActivityVNext.arguments.emptyArray': 'No items yet',
+ 'workflowActivityVNext.arguments.emptyObject': 'No properties yet',
+ 'workflowActivityVNext.arguments.duplicateProperty':
+ 'Property names must be unique. "{property}" already exists in this object.',
+ 'workflowActivityVNext.arguments.invalidObject':
+ 'Enter a valid JSON object before switching to Fields.',
+ 'workflowActivityVNext.arguments.item': 'Item {number}',
+ 'workflowActivityVNext.arguments.jsonAria': 'Arguments JSON',
+ 'workflowActivityVNext.arguments.mode.fields': 'Fields',
+ 'workflowActivityVNext.arguments.mode.json': 'JSON',
+ 'workflowActivityVNext.arguments.nullValue': 'No value',
+ 'workflowActivityVNext.arguments.propertyName': 'Property name',
+ 'workflowActivityVNext.arguments.propertyNameAria':
+ 'Property name for {path}',
+ 'workflowActivityVNext.arguments.removeItemAria': 'Remove item {number}',
+ 'workflowActivityVNext.arguments.removePropertyAria': 'Remove {path}',
+ 'workflowActivityVNext.arguments.type.array': 'Array',
+ 'workflowActivityVNext.arguments.type.boolean': 'Boolean',
+ 'workflowActivityVNext.arguments.type.null': 'Null',
+ 'workflowActivityVNext.arguments.type.number': 'Number',
+ 'workflowActivityVNext.arguments.type.object': 'Object',
+ 'workflowActivityVNext.arguments.type.string': 'String',
+ 'workflowActivityVNext.arguments.unnamedProperty': 'unnamed property',
+ 'workflowActivityVNext.arguments.unsupportedNumber':
+ 'This JSON contains a number that Fields mode cannot represent safely. Edit it in JSON or use a string.',
+ 'workflowActivityVNext.arguments.valueAria': 'Value for {path}',
+ 'workflowActivityVNext.arguments.valueTypeAria': 'Value type for {path}',
+ 'workflowActivityVNext.activity.allOrigins': 'All sources',
+ 'workflowActivityVNext.activity.allStatuses': 'All statuses',
+ 'workflowActivityVNext.activity.afterPlaceholder': 'Activity after',
+ 'workflowActivityVNext.activity.beforePlaceholder': 'Activity before',
+ 'workflowActivityVNext.activity.clearWorkflowFilter': 'Show all workflows',
+ 'workflowActivityVNext.activity.columnDuration': 'Duration',
+ 'workflowActivityVNext.activity.columnInputPreview': 'Input preview',
+ 'workflowActivityVNext.activity.columnOrigin': 'Source',
+ 'workflowActivityVNext.activity.columnRun': 'Workflow',
+ 'workflowActivityVNext.activity.columnStarted': 'Started',
+ 'workflowActivityVNext.activity.columnStatus': 'Status',
+ 'workflowActivityVNext.activity.columnUpdated': 'Updated',
+ 'workflowActivityVNext.activity.description':
+ 'Review recent workflow runs and open one for details.',
+ 'workflowActivityVNext.activity.empty': 'No runs yet',
+ 'workflowActivityVNext.activity.emptyDescription':
+ 'Runs will appear here after a workflow starts.',
+ 'workflowActivityVNext.activity.loading': 'Loading activity…',
+ 'workflowActivityVNext.activity.noMatch': 'No matching runs',
+ 'workflowActivityVNext.activity.originChat': 'Chat',
+ 'workflowActivityVNext.activity.originEditor': 'Editor',
+ 'workflowActivityVNext.activity.originFilter': 'Run source',
+ 'workflowActivityVNext.activity.originMember': 'Team member',
+ 'workflowActivityVNext.activity.originSchedule': 'Schedule',
+ 'workflowActivityVNext.activity.originService': 'Service',
+ 'workflowActivityVNext.activity.nextPage': 'Next page',
+ 'workflowActivityVNext.activity.page': 'Page {page}',
+ 'workflowActivityVNext.activity.pageOf': 'Page {page} of {total}',
+ 'workflowActivityVNext.activity.pageUnavailable': "Couldn't load this page",
+ 'workflowActivityVNext.activity.previousPage': 'Previous page',
+ 'workflowActivityVNext.activity.search': 'Search runs',
+ 'workflowActivityVNext.activity.searchAria': 'Search runs',
+ 'workflowActivityVNext.activity.searchButton': 'Search',
+ 'workflowActivityVNext.activity.statusCancelled': 'Cancelled',
+ 'workflowActivityVNext.activity.statusCompleted': 'Completed',
+ 'workflowActivityVNext.activity.statusFilter': 'Run status',
+ 'workflowActivityVNext.activity.statusQueued': 'Queued',
+ 'workflowActivityVNext.activity.statusRunning': 'Running',
+ 'workflowActivityVNext.activity.statusTimedOut': 'Timed out',
+ 'workflowActivityVNext.activity.statusWaiting': 'Waiting',
+ 'workflowActivityVNext.activity.title': 'Activity',
+ 'workflowActivityVNext.activity.unavailable': 'Activity unavailable',
+ 'workflowActivityVNext.activity.unnamed': 'Unnamed workflow',
+ 'workflowActivityVNext.activity.unavailableDescription':
+ 'Try again to load recent workflow runs.',
+ 'workflowActivityVNext.activity.removeWorkflowFilterAria':
+ 'Remove workflow filter {workflowId}',
+ 'workflowActivityVNext.activity.removeScheduleFilterAria':
+ 'Remove schedule filter {scheduleId}',
+ 'workflowActivityVNext.activity.scheduleFilterLabel':
+ 'Schedule: {scheduleId}',
+ 'workflowActivityVNext.activity.removeOriginFilterAria':
+ 'Remove source filter {origin}',
+ 'workflowActivityVNext.activity.originFilterContextLabel': 'Source: {origin}',
+ 'workflowActivityVNext.activity.workflowFilterInvalidDescription':
+ 'This Activity link does not contain a workflow identity.',
+ 'workflowActivityVNext.activity.workflowFilterInvalidTitle':
+ 'Choose a workflow to filter Activity',
+ 'workflowActivityVNext.activity.workflowFilterLabel':
+ 'Workflow: {workflowId}',
+ 'workflowActivityVNext.activity.workflowFilterLoading':
+ 'Loading workflow activity…',
+ 'workflowActivityVNext.activity.workflowFilterResolutionFailed':
+ 'Workflow activity unavailable',
+ 'workflowActivityVNext.activity.workflowFilterResolutionFailedDescription':
+ 'Try again or remove the workflow filter.',
+ 'workflowActivityVNext.activity.workflowFilterUnavailableDescription':
+ 'No runs are shown because this workflow does not expose an Activity filter.',
+ 'workflowActivityVNext.activity.workflowFilterUnavailableTitle':
+ 'Activity filtering is unavailable',
+ 'workflowActivityVNext.brand.subtitle': 'AUTOMATION LEDGER',
+ 'workflowActivityVNext.common.back': 'Back',
+ 'workflowActivityVNext.common.cancel': 'Cancel',
+ 'workflowActivityVNext.common.close': 'Close',
+ 'workflowActivityVNext.common.empty': 'Empty',
+ 'workflowActivityVNext.common.failed': 'Failed',
+ 'workflowActivityVNext.common.no': 'No',
+ 'workflowActivityVNext.common.open': 'Open',
+ 'workflowActivityVNext.common.pending': 'Pending',
+ 'workflowActivityVNext.common.refresh': 'Refresh',
+ 'workflowActivityVNext.common.retry': 'Retry',
+ 'workflowActivityVNext.common.run': 'Run',
+ 'workflowActivityVNext.common.succeeded': 'Succeeded',
+ 'workflowActivityVNext.common.technicalDetails': 'Technical details',
+ 'workflowActivityVNext.common.unavailable': 'Unavailable',
+ 'workflowActivityVNext.common.unknown': 'Unknown',
+ 'workflowActivityVNext.common.yes': 'Yes',
+ 'workflowActivityVNext.editor.addNode': 'Add node',
+ 'workflowActivityVNext.editor.addNodeFailed': "Couldn't add node",
+ 'workflowActivityVNext.editor.activityDelayed':
+ 'This run is taking longer to appear in Activity',
+ 'workflowActivityVNext.editor.activityDelayedDescription':
+ 'Try again to check the latest status.',
+ 'workflowActivityVNext.editor.activityObserved': 'Observed in Activity',
+ 'workflowActivityVNext.editor.activityObservedDescription':
+ 'You can review its details and progress.',
+ 'workflowActivityVNext.editor.activityUnavailable': 'Activity unavailable',
+ 'workflowActivityVNext.editor.activityUnavailableDescription':
+ 'Try again to check the latest status.',
+ 'workflowActivityVNext.editor.backAria': 'Back to workflows',
+ 'workflowActivityVNext.editor.canvas': 'Canvas',
+ 'workflowActivityVNext.editor.canvasAria': 'Workflow canvas',
+ 'workflowActivityVNext.editor.canvasUpdateFailed': "Couldn't update workflow",
+ 'workflowActivityVNext.editor.description':
+ 'Build, test, and refine this workflow.',
+ 'workflowActivityVNext.editor.discardLeave': 'Discard and leave',
+ 'workflowActivityVNext.editor.emptyCanvas':
+ 'Add the first executable node to make this workflow runnable.',
+ 'workflowActivityVNext.editor.loading': 'Loading workflow…',
+ 'workflowActivityVNext.editor.loadingDescription': 'Preparing the editor…',
+ 'workflowActivityVNext.editor.openActivity': 'Open Activity',
+ 'workflowActivityVNext.editor.publish': 'Publish',
+ 'workflowActivityVNext.editor.publishBeforeRun':
+ 'Publish this workflow before running it.',
+ 'workflowActivityVNext.editor.publishLatestBeforeRun':
+ 'Save and publish the latest changes before running.',
+ 'workflowActivityVNext.editor.publicationStatusAria':
+ 'Workflow publication status',
+ 'workflowActivityVNext.editor.publishUnavailable':
+ "Publishing isn't available for this workflow yet.",
+ 'workflowActivityVNext.editor.publishedRunDrawer': 'Run published workflow',
+ 'workflowActivityVNext.editor.publishedTargetUnavailable':
+ 'Publish this workflow before running it.',
+ 'workflowActivityVNext.editor.waitForEditorBeforeRun':
+ 'Wait for the workflow update to finish.',
+ 'workflowActivityVNext.editor.waitForPublishedRun':
+ 'Wait for the published revision to become available.',
+ 'workflowActivityVNext.publish.addExecutableStep':
+ 'Add at least one executable step before publishing.',
+ 'workflowActivityVNext.publish.applyNodeChanges':
+ 'Apply or discard node configuration before publishing.',
+ 'workflowActivityVNext.publish.blocked': 'Publish blocked · {count} issues',
+ 'workflowActivityVNext.publish.blockedOne': 'Publish blocked · 1 issue',
+ 'workflowActivityVNext.publish.failed': "Publication couldn't be confirmed",
+ 'workflowActivityVNext.publish.failedDescription':
+ 'Resolve the error or try publishing again.',
+ 'workflowActivityVNext.publish.forbiddenDescription':
+ "You don't have access to check this publication.",
+ 'workflowActivityVNext.publish.published': 'Published',
+ 'workflowActivityVNext.publish.publishedServiceId': 'Published service ID',
+ 'workflowActivityVNext.publish.publishing': 'Publishing',
+ 'workflowActivityVNext.publish.revisionId': 'Revision ID',
+ 'workflowActivityVNext.publish.readinessIssues': 'Publish readiness issues',
+ 'workflowActivityVNext.publish.success': 'Workflow published',
+ 'workflowActivityVNext.publish.submissionFailed':
+ "Workflow couldn't be submitted",
+ 'workflowActivityVNext.publish.submissionFailedDescription':
+ 'Review the error and try publishing again.',
+ 'workflowActivityVNext.publish.validationRejected':
+ "Workflow isn't ready to publish",
+ 'workflowActivityVNext.publish.validationRejectedDescription':
+ 'Fix the workflow configuration below, then publish again.',
+ 'workflowActivityVNext.publish.resolvePublication':
+ 'Resolve the current publication status before publishing again.',
+ 'workflowActivityVNext.publish.saveBeforePublishing':
+ 'Save this workflow before publishing.',
+ 'workflowActivityVNext.publish.saveChangesBeforePublishing':
+ 'Save your changes before publishing.',
+ 'workflowActivityVNext.publish.waitForPublication':
+ 'Wait for the current publication to finish.',
+ 'workflowActivityVNext.publish.waitForEditorUpdate':
+ 'Wait for the workflow step update to finish.',
+ 'workflowActivityVNext.publish.waitForSave':
+ 'Wait for workflow validation and saving to finish.',
+ 'workflowActivityVNext.publish.waitForSavedDraft':
+ 'Wait for the saved draft to become readable.',
+ 'workflowActivityVNext.publish.unauthorizedDescription':
+ 'Sign in again to check this publication.',
+ 'workflowActivityVNext.editor.checkLatestStatus': 'Check latest status',
+ 'workflowActivityVNext.editor.currentStep': 'Current step',
+ 'workflowActivityVNext.editor.expandRunConsole': 'Expand workflow logs',
+ 'workflowActivityVNext.editor.failureSummary': 'Failure summary',
+ 'workflowActivityVNext.editor.fullDetailsNotice':
+ 'Open run details for the full timeline, diagnostics, and recovery actions.',
+ 'workflowActivityVNext.editor.openRunDetails': 'Open run details',
+ 'workflowActivityVNext.editor.outputSummary': 'Output summary',
+ 'workflowActivityVNext.editor.publishedRunPanel.inputRequired':
+ 'Published workflow runs can start without manual input.',
+ 'workflowActivityVNext.editor.publishedRunPanel.removeEmptyFile':
+ 'Remove empty file {name} before starting the published run.',
+ 'workflowActivityVNext.editor.resizePublishedRunPanel':
+ 'Resize published run panel',
+ 'workflowActivityVNext.editor.resizeRunConsole':
+ 'Resize workflow run console',
+ 'workflowActivityVNext.editor.runAgain': 'Run again',
+ 'workflowActivityVNext.editor.runFailed': 'Run failed',
+ 'workflowActivityVNext.editor.runInput': 'Input',
+ 'workflowActivityVNext.editor.runInputExample':
+ 'For example: Review order 42',
+ 'workflowActivityVNext.editor.runInputHelp':
+ 'This workflow accepts one text input. For example: Review order 42.',
+ 'workflowActivityVNext.editor.runInputRequired':
+ 'Input or an attached file is required.',
+ 'workflowActivityVNext.editor.runInputRequiredTag': 'Required',
+ 'workflowActivityVNext.editor.runPanel': 'Test run',
+ 'workflowActivityVNext.editor.runResult': 'Run result',
+ 'workflowActivityVNext.editor.runUnavailable':
+ 'Add at least one valid step before running.',
+ 'workflowActivityVNext.editor.retryActivityObservation': 'Check again',
+ 'workflowActivityVNext.editor.saveDelayed':
+ 'Save is taking longer than expected',
+ 'workflowActivityVNext.editor.saveFailed': "Workflow couldn't be saved",
+ 'workflowActivityVNext.editor.saveStatusAria': 'Workflow save status',
+ 'workflowActivityVNext.editor.saveStatusFailed': 'Save failed',
+ 'workflowActivityVNext.editor.saveOpenFailed':
+ "Workflow was saved but couldn't be reopened",
+ 'workflowActivityVNext.editor.savingProgress': 'Saving workflow…',
+ 'workflowActivityVNext.editor.save': 'Save workflow',
+ 'workflowActivityVNext.editor.saveLeave': 'Save and leave',
+ 'workflowActivityVNext.editor.saveSuccess': 'Workflow saved',
+ 'workflowActivityVNext.editor.saved': 'Saved',
+ 'workflowActivityVNext.editor.savedAt': 'Saved at {updatedAt}',
+ 'workflowActivityVNext.editor.stay': 'Stay',
+ 'workflowActivityVNext.editor.submitRun': 'Start run',
+ 'workflowActivityVNext.editor.submittedInput': 'Submitted input',
+ 'workflowActivityVNext.editor.snapshotNotice':
+ 'Run again uses this exact input and workflow snapshot.',
+ 'workflowActivityVNext.editor.unavailable': 'Workflow unavailable',
+ 'workflowActivityVNext.editor.unavailableDescription':
+ "This workflow couldn't be loaded.",
+ 'workflowActivityVNext.editor.unavailableGuidance':
+ 'Try again to reopen the workflow.',
+ 'workflowActivityVNext.editor.unsaved': 'Unsaved',
+ 'workflowActivityVNext.editor.unsavedChanges': 'Unsaved changes',
+ 'workflowActivityVNext.editor.updatingNode': 'Updating node…',
+ 'workflowActivityVNext.editor.validating': 'Validating workflow…',
+ 'workflowActivityVNext.editor.unsavedDescription':
+ 'Save your changes, discard them, or stay in the editor.',
+ 'workflowActivityVNext.editor.unsavedTitle': 'Unsaved workflow changes',
+ 'workflowActivityVNext.editor.untitled': 'Untitled workflow',
+ 'workflowActivityVNext.editor.viewRun': 'View run',
+ 'workflowActivityVNext.editor.yaml': 'YAML',
+ 'workflowActivityVNext.editor.yamlReadFailed':
+ 'Workflow YAML could not be read.',
+ 'workflowActivityVNext.nodeInspector.advanced': 'Advanced JSON',
+ 'workflowActivityVNext.nodeInspector.advancedDescription':
+ "Edit this step's runtime parameters as JSON.",
+ 'workflowActivityVNext.nodeInspector.applyChanges': 'Apply step',
+ 'workflowActivityVNext.nodeInspector.applyBeforeSave':
+ 'Apply this step before saving the workflow.',
+ 'workflowActivityVNext.nodeInspector.applyFailed':
+ "Couldn't apply configuration",
+ 'workflowActivityVNext.nodeInspector.applyJson': 'Apply JSON',
+ 'workflowActivityVNext.nodeInspector.branches': 'Branches',
+ 'workflowActivityVNext.nodeInspector.branchesUnavailable':
+ 'Branches unavailable',
+ 'workflowActivityVNext.nodeInspector.close': 'Close configuration',
+ 'workflowActivityVNext.nodeInspector.closeAria': 'Close node configuration',
+ 'workflowActivityVNext.nodeInspector.configuration': 'Settings',
+ 'workflowActivityVNext.nodeInspector.configurationDescription':
+ 'Set what this step needs before the workflow runs.',
+ 'workflowActivityVNext.nodeInspector.discard': 'Discard changes',
+ 'workflowActivityVNext.nodeInspector.discardDescription':
+ 'Your unapplied changes to this step will be lost.',
+ 'workflowActivityVNext.nodeInspector.discardTitle': 'Discard node changes?',
+ 'workflowActivityVNext.nodeInspector.errorDetails': 'Error details',
+ 'workflowActivityVNext.nodeInspector.fieldExample': 'Example: {value}',
+ 'workflowActivityVNext.nodeInspector.fieldOptional': 'Optional',
+ 'workflowActivityVNext.nodeInspector.fieldRequired': 'Required',
+ 'workflowActivityVNext.nodeInspector.nextStep': 'Next step',
+ 'workflowActivityVNext.nodeInspector.noBranches': 'No branches',
+ 'workflowActivityVNext.nodeInspector.noSettings':
+ 'No settings are needed for this step.',
+ 'workflowActivityVNext.nodeInspector.notSet': 'Not set',
+ 'workflowActivityVNext.nodeInspector.purpose.assign':
+ 'Store a value for later steps in this workflow.',
+ 'workflowActivityVNext.nodeInspector.purpose.cache':
+ 'Reuse a previous result when the same cache key appears.',
+ 'workflowActivityVNext.nodeInspector.purpose.checkpoint':
+ 'Record a named recovery point in this workflow.',
+ 'workflowActivityVNext.nodeInspector.purpose.conditional':
+ 'Continue only when the configured condition is true.',
+ 'workflowActivityVNext.nodeInspector.purpose.connectorCall':
+ 'Call an operation provided by a configured connector.',
+ 'workflowActivityVNext.nodeInspector.purpose.default':
+ 'Configure how this step behaves when the workflow runs.',
+ 'workflowActivityVNext.nodeInspector.purpose.delay':
+ 'Pause this workflow before the next step continues.',
+ 'workflowActivityVNext.nodeInspector.purpose.dynamicWorkflow':
+ 'Create and run workflow steps from generated YAML.',
+ 'workflowActivityVNext.nodeInspector.purpose.emit':
+ 'Publish an event for another workflow or system listener.',
+ 'workflowActivityVNext.nodeInspector.purpose.evaluate':
+ 'Score the current result against clear criteria.',
+ 'workflowActivityVNext.nodeInspector.purpose.foreach':
+ 'Run the same child step for every input item.',
+ 'workflowActivityVNext.nodeInspector.purpose.guard':
+ 'Check the input before allowing the workflow to continue.',
+ 'workflowActivityVNext.nodeInspector.purpose.humanApproval':
+ 'Pause until a person approves or rejects the next action.',
+ 'workflowActivityVNext.nodeInspector.purpose.humanInput':
+ 'Pause and collect information from a person.',
+ 'workflowActivityVNext.nodeInspector.purpose.llmCall':
+ 'Send an instruction and workflow input to an AI model.',
+ 'workflowActivityVNext.nodeInspector.purpose.mapReduce':
+ 'Process input chunks separately, then combine the results.',
+ 'workflowActivityVNext.nodeInspector.purpose.parallel':
+ 'Run several workers at the same time and combine their work.',
+ 'workflowActivityVNext.nodeInspector.purpose.race':
+ 'Run several workers and continue with the first results.',
+ 'workflowActivityVNext.nodeInspector.purpose.reflect':
+ 'Review and improve a result for a limited number of rounds.',
+ 'workflowActivityVNext.nodeInspector.purpose.retrieveFacts':
+ 'Find relevant facts to use in later workflow steps.',
+ 'workflowActivityVNext.nodeInspector.purpose.switch':
+ 'Choose the next branch by matching the current value.',
+ 'workflowActivityVNext.nodeInspector.purpose.toolCall':
+ 'Run a registered tool with the input you provide.',
+ 'workflowActivityVNext.nodeInspector.purpose.transform':
+ 'Transform the current value before the next step uses it.',
+ 'workflowActivityVNext.nodeInspector.purpose.vote':
+ 'Choose a result from the available worker responses.',
+ 'workflowActivityVNext.nodeInspector.purpose.waitSignal':
+ 'Pause until the expected signal arrives or time runs out.',
+ 'workflowActivityVNext.nodeInspector.purpose.while':
+ 'Repeat a child step while the condition remains true.',
+ 'workflowActivityVNext.nodeInspector.purpose.workflowCall':
+ 'Run another workflow and use its result here.',
+ 'workflowActivityVNext.nodeInspector.purpose.workflowYamlValidate':
+ 'Check generated workflow YAML before it is used.',
+ 'workflowActivityVNext.nodeInspector.rawConfigurationAria':
+ 'Raw configuration',
+ 'workflowActivityVNext.nodeInspector.rawConfigurationError':
+ 'Configuration must be a JSON object.',
+ 'workflowActivityVNext.nodeInspector.sectionAria': 'Configure {name}',
+ 'workflowActivityVNext.nodeInspector.stepDetails': 'Technical details',
+ 'workflowActivityVNext.nodeInspector.stepId': 'Step ID',
+ 'workflowActivityVNext.nodeInspector.targetRole': 'Target role',
+ 'workflowActivityVNext.nodeInspector.title': 'Configure {name}',
+ 'workflowActivityVNext.nodeInspector.type': 'Type',
+ 'workflowActivityVNext.failure.accessDenied':
+ 'You do not have access to use this service or model.',
+ 'workflowActivityVNext.failure.accessDeniedGuidance':
+ 'Request access or choose a service and model available to your account.',
+ 'workflowActivityVNext.failure.cancelled': 'Run cancelled.',
+ 'workflowActivityVNext.failure.cancelledGuidance':
+ 'The run stopped without being reported as a system failure.',
+ 'workflowActivityVNext.failure.chooseAllowedService':
+ 'Choose allowed service',
+ 'workflowActivityVNext.failure.copyTrackingId': 'Copy tracking ID',
+ 'workflowActivityVNext.failure.internal':
+ 'The request could not be completed.',
+ 'workflowActivityVNext.failure.internalGuidance':
+ 'Try again or contact support with the tracking ID when one is available.',
+ 'workflowActivityVNext.failure.invalidInput':
+ 'The input or workflow definition needs attention.',
+ 'workflowActivityVNext.failure.invalidInputGuidance':
+ 'Review the affected input or workflow step before trying again.',
+ 'workflowActivityVNext.failure.rateLimited': 'This request was rate limited.',
+ 'workflowActivityVNext.failure.rateLimitedGuidance':
+ 'Wait for the quota window to reset before trying again.',
+ 'workflowActivityVNext.failure.reloadLatest': 'Reload latest',
+ 'workflowActivityVNext.failure.resourceMissing':
+ 'This run is no longer available.',
+ 'workflowActivityVNext.failure.resourceMissingGuidance':
+ 'Refresh Activity or return to the run list.',
+ 'workflowActivityVNext.failure.retryAfter': 'Try again in {seconds} seconds.',
+ 'workflowActivityVNext.failure.reviewInput': 'Review input',
+ 'workflowActivityVNext.failure.sessionExpired': 'Your session has expired.',
+ 'workflowActivityVNext.failure.sessionExpiredGuidance':
+ 'Sign in again to continue.',
+ 'workflowActivityVNext.failure.signInAgain': 'Sign in again',
+ 'workflowActivityVNext.failure.stateConflict':
+ 'This run changed since it was loaded.',
+ 'workflowActivityVNext.failure.stateConflictGuidance':
+ 'Reload the latest state before continuing.',
+ 'workflowActivityVNext.failure.timeoutOrOffline':
+ 'The request timed out or the connection was interrupted.',
+ 'workflowActivityVNext.failure.timeoutOrOfflineGuidance':
+ 'Check the connection, then try again.',
+ 'workflowActivityVNext.failure.upstreamUnavailable':
+ 'The selected service is temporarily unavailable.',
+ 'workflowActivityVNext.failure.upstreamUnavailableGuidance':
+ 'Check service status or try again later.',
+ 'workflowActivityVNext.nav.activity': 'Activity',
+ 'workflowActivityVNext.nav.aria': 'Workflow workbench',
+ 'workflowActivityVNext.nav.openMenu': 'Open navigation',
+ 'workflowActivityVNext.nav.settings': 'Settings',
+ 'workflowActivityVNext.nav.workflows': 'Workflows',
+ 'workflowActivityVNext.new.back': 'Back to workflows',
+ 'workflowActivityVNext.new.changeMethod': 'Change method',
+ 'workflowActivityVNext.new.chooserAria': 'Workflow creation methods',
+ 'workflowActivityVNext.new.createBlank': 'Create and open',
+ 'workflowActivityVNext.new.createFailed': "Workflow couldn't be created",
+ 'workflowActivityVNext.new.createUnconfirmed':
+ "Workflow creation couldn't be confirmed. Check Workflows before trying again.",
+ 'workflowActivityVNext.new.creatingDescription':
+ 'This usually takes only a moment.',
+ 'workflowActivityVNext.new.description': 'Choose how you want to start.',
+ 'workflowActivityVNext.new.defaultWorkspace': 'Default workspace',
+ 'workflowActivityVNext.new.directory': 'Save to',
+ 'workflowActivityVNext.new.generate': 'Generate and open',
+ 'workflowActivityVNext.new.goal': 'What should this workflow do?',
+ 'workflowActivityVNext.new.mode.blank': 'Start blank',
+ 'workflowActivityVNext.new.mode.blank.description':
+ 'Start with an empty canvas and add steps yourself.',
+ 'workflowActivityVNext.new.mode.describe': 'Describe',
+ 'workflowActivityVNext.new.mode.describe.description':
+ 'Turn a goal into a workflow you can review and edit.',
+ 'workflowActivityVNext.new.mode.import': 'Import YAML',
+ 'workflowActivityVNext.new.mode.import.description':
+ 'Bring in an existing YAML workflow.',
+ 'workflowActivityVNext.new.mode.template': 'Use template',
+ 'workflowActivityVNext.new.mode.template.description':
+ 'Start from a ready-made workflow and customize it.',
+ 'workflowActivityVNext.new.name': 'Workflow name',
+ 'workflowActivityVNext.new.noDirectories':
+ 'No save location is available. Try again later or contact your administrator.',
+ 'workflowActivityVNext.new.noDirectoriesDescription':
+ 'A save location is required to own the workflow. You can prepare your input here while access is restored.',
+ 'workflowActivityVNext.new.observationFailed': "Workflow couldn't be opened",
+ 'workflowActivityVNext.new.observing': 'Creating workflow…',
+ 'workflowActivityVNext.new.projectionDelayed':
+ 'This is taking longer than expected',
+ 'workflowActivityVNext.new.retryDescription':
+ 'Your work is safe. Try again to finish opening the workflow.',
+ 'workflowActivityVNext.new.retryObservation': 'Try again',
+ 'workflowActivityVNext.new.reviewAccess': 'Review access',
+ 'workflowActivityVNext.new.saveTargetRequired':
+ 'Choose an available save location before creating the workflow.',
+ 'workflowActivityVNext.new.templateBrowser.title': 'Start from a template',
+ 'workflowActivityVNext.new.templateBrowser.description':
+ 'Browse public templates, inspect details, or create a draft directly.',
+ 'workflowActivityVNext.new.templateBrowser.changeMethod': 'Change method',
+ 'workflowActivityVNext.new.templateBrowser.search': 'Search templates',
+ 'workflowActivityVNext.new.templateBrowser.sort': 'Sort templates',
+ 'workflowActivityVNext.new.templateBrowser.sortBy': 'Sort by',
+ 'workflowActivityVNext.new.templateBrowser.sort.recent':
+ 'Last updated: newest first',
+ 'workflowActivityVNext.new.templateBrowser.sort.nameAsc': 'Name: A to Z',
+ 'workflowActivityVNext.new.templateBrowser.sort.nameDesc': 'Name: Z to A',
+ 'workflowActivityVNext.new.templateBrowser.sort.oldest':
+ 'Last updated: oldest first',
+ 'workflowActivityVNext.new.templateBrowser.loading': 'Loading templates…',
+ 'workflowActivityVNext.new.templateBrowser.loadFailed':
+ 'Templates could not be loaded',
+ 'workflowActivityVNext.new.templateBrowser.unavailable':
+ 'Templates are not available in this environment.',
+ 'workflowActivityVNext.new.templateBrowser.unavailableDescription':
+ 'The template catalog is not available in this environment yet.',
+ 'workflowActivityVNext.new.templateBrowser.retry': 'Retry',
+ 'workflowActivityVNext.new.templateBrowser.signIn':
+ 'Sign in to browse public workflow templates.',
+ 'workflowActivityVNext.new.templateBrowser.noSearchResults':
+ 'No templates matched your search.',
+ 'workflowActivityVNext.new.templateBrowser.noTemplates':
+ 'No public workflow templates are available yet.',
+ 'workflowActivityVNext.new.templateBrowser.view': 'View',
+ 'workflowActivityVNext.new.templateBrowser.viewNamed': 'View {name}',
+ 'workflowActivityVNext.new.templateBrowser.use': 'Use template',
+ 'workflowActivityVNext.new.templateBrowser.useNamed': 'Use template {name}',
+ 'workflowActivityVNext.new.templateBrowser.catalogue':
+ 'Workflow template catalogue',
+ 'workflowActivityVNext.new.templateBrowser.template': 'Template',
+ 'workflowActivityVNext.new.templateBrowser.connection': 'Connection',
+ 'workflowActivityVNext.new.templateBrowser.does': 'Does',
+ 'workflowActivityVNext.new.templateBrowser.updatedColumn': 'Updated',
+ 'workflowActivityVNext.new.templateBrowser.actions': 'Actions',
+ 'workflowActivityVNext.new.templateBrowser.none': 'None',
+ 'workflowActivityVNext.new.templateBrowser.llmProvider': 'LLM provider',
+ 'workflowActivityVNext.new.templateBrowser.runsSteps': 'Runs {count} {unit}',
+ 'workflowActivityVNext.new.templateBrowser.stepsTooltipTitle':
+ 'Workflow steps ({count})',
+ 'workflowActivityVNext.new.templateBrowser.stepsTooltipLoading':
+ 'Loading step details…',
+ 'workflowActivityVNext.new.templateBrowser.stepsTooltipUnavailable':
+ 'Step details are unavailable. Open View to inspect this template.',
+ 'workflowActivityVNext.new.templateBrowser.stepsTooltipEmpty':
+ 'No workflow steps are exposed for this template.',
+ 'workflowActivityVNext.new.templateBrowser.step': 'step',
+ 'workflowActivityVNext.new.templateBrowser.steps': 'steps',
+ 'workflowActivityVNext.new.templateBrowser.fallbackDescription':
+ 'A ready-made workflow for your workspace.',
+ 'workflowActivityVNext.new.templateBrowser.contract': '{name} contract',
+ 'workflowActivityVNext.new.templateBrowser.detailLoading':
+ 'Loading template details…',
+ 'workflowActivityVNext.new.templateBrowser.detailFailed':
+ 'Template details could not be loaded',
+ 'workflowActivityVNext.new.templateBrowser.noLongerAvailable':
+ 'This template is no longer available.',
+ 'workflowActivityVNext.new.templateBrowser.preview': 'Workflow preview',
+ 'workflowActivityVNext.new.templateBrowser.previewDescription':
+ '{count} {unit} and the paths between them.',
+ 'workflowActivityVNext.new.templateBrowser.noPreviewSteps':
+ 'This template does not expose any workflow steps.',
+ 'workflowActivityVNext.new.templateBrowser.source':
+ 'Source: public workflow template · version {version}',
+ 'workflowActivityVNext.new.templateBrowser.cancel': 'Cancel',
+ 'workflowActivityVNext.new.templateBrowser.useThis': 'Use this template',
+ 'workflowActivityVNext.new.templateBrowser.templateOutOfDate':
+ 'Template is out of date',
+ 'workflowActivityVNext.new.templateBrowser.templateOutOfDateDescription':
+ 'This template changed while you were viewing it. Refresh the catalog and try again.',
+ 'workflowActivityVNext.new.templateBrowser.refreshCatalog': 'Refresh catalog',
+ 'workflowActivityVNext.new.templateBrowser.creationFailed':
+ 'Template creation failed',
+ 'workflowActivityVNext.new.templateBrowser.couldNotUse':
+ 'Workflow template could not be used',
+ 'workflowActivityVNext.new.templateBrowser.creating': 'Creating workflow…',
+ 'workflowActivityVNext.new.templateBrowser.projectionDelayed':
+ 'This is taking longer than expected',
+ 'workflowActivityVNext.new.templateBrowser.observationFailed':
+ "Workflow couldn't be opened",
+ 'workflowActivityVNext.new.templateBrowser.safeToRetry':
+ 'Your work is safe. Try again to finish opening the workflow.',
+ 'workflowActivityVNext.new.templateBrowser.creatingDescription':
+ 'This usually takes only a moment.',
+ 'workflowActivityVNext.new.templateBrowser.tryAgain': 'Try again',
+ 'workflowActivityVNext.new.title': 'New workflow',
+ 'workflowActivityVNext.new.validateCreate': 'Import and open',
+ 'workflowActivityVNext.new.workspaceUnavailable':
+ 'Save locations unavailable',
+ 'workflowActivityVNext.new.workspaceUnavailableDescription':
+ 'Choose a creation method now. Your input stays on this page while you restore access.',
+ 'workflowActivityVNext.new.yaml': 'Workflow YAML',
+ 'workflowActivityVNext.run.backAria': 'Back to Activity',
+ 'workflowActivityVNext.run.commandId': 'Request ID',
+ 'workflowActivityVNext.run.code': 'Code',
+ 'workflowActivityVNext.run.completedSteps': 'Completed steps',
+ 'workflowActivityVNext.run.completionTokens': 'Completion tokens',
+ 'workflowActivityVNext.run.confirmRetry': 'Confirm retry',
+ 'workflowActivityVNext.run.confirmRunAgain': 'Confirm run again',
+ 'workflowActivityVNext.run.confirmTitle': 'Confirm new run',
+ 'workflowActivityVNext.run.costWarning':
+ 'This action may incur model or tool costs again.',
+ 'workflowActivityVNext.run.correlationId': 'Tracking ID',
+ 'workflowActivityVNext.run.cost': 'Cost',
+ 'workflowActivityVNext.run.diagnostics': 'Diagnostics',
+ 'workflowActivityVNext.run.error': 'Final error',
+ 'workflowActivityVNext.run.failedSummary': 'The run did not complete.',
+ 'workflowActivityVNext.run.description':
+ 'Review the result, steps, and history for this run.',
+ 'workflowActivityVNext.run.title': 'Run details',
+ 'workflowActivityVNext.run.forkAccepted': 'New run accepted',
+ 'workflowActivityVNext.run.forkAcceptedDescription':
+ 'The request was accepted. Open the new run to follow its progress.',
+ 'workflowActivityVNext.run.fromStep': 'From step {step}',
+ 'workflowActivityVNext.run.graph': 'Graph',
+ 'workflowActivityVNext.run.graphEmpty': 'No graph is available yet.',
+ 'workflowActivityVNext.run.graphEmptyDescription':
+ 'This run has not materialized a graph view yet.',
+ 'workflowActivityVNext.run.graphLoading': 'Loading run graph…',
+ 'workflowActivityVNext.run.graphSummary': '{nodes} nodes · {edges} edges',
+ 'workflowActivityVNext.run.graphUnavailable': 'Run graph unavailable',
+ 'workflowActivityVNext.run.historyLoading': 'Loading run history…',
+ 'workflowActivityVNext.run.historyUnavailable': 'Run history is unavailable.',
+ 'workflowActivityVNext.run.input': 'Input',
+ 'workflowActivityVNext.run.loading': 'Loading run…',
+ 'workflowActivityVNext.run.loadingDescription': 'Loading run details…',
+ 'workflowActivityVNext.run.logs': 'Logs',
+ 'workflowActivityVNext.run.newActorId': 'Run address',
+ 'workflowActivityVNext.run.noHistory': 'No published runs yet.',
+ 'workflowActivityVNext.run.noHistoryDescription':
+ 'This workflow has no other runs in the current history context.',
+ 'workflowActivityVNext.run.noSteps': 'No steps are available yet.',
+ 'workflowActivityVNext.run.noDiagnostics': 'No diagnostics were returned.',
+ 'workflowActivityVNext.run.noTimeline': 'No timeline events are visible yet.',
+ 'workflowActivityVNext.run.output': 'Final output',
+ 'workflowActivityVNext.run.message': 'Message',
+ 'workflowActivityVNext.run.openNewRun': 'Open new run',
+ 'workflowActivityVNext.run.originalRun': 'Original run',
+ 'workflowActivityVNext.run.parentRun': 'Parent run',
+ 'workflowActivityVNext.run.parentStep': 'Parent step',
+ 'workflowActivityVNext.run.priorOutputs': 'Prior step outputs',
+ 'workflowActivityVNext.run.priorOutputsNotReused':
+ 'Prior step outputs will not be reused.',
+ 'workflowActivityVNext.run.priorOutputsReused':
+ 'Prior step outputs will be reused.',
+ 'workflowActivityVNext.run.promptTokens': 'Prompt tokens',
+ 'workflowActivityVNext.run.requestedSteps': 'Requested steps',
+ 'workflowActivityVNext.run.requestParameters': 'Request parameters',
+ 'workflowActivityVNext.run.refreshFailed':
+ "Some run details couldn't be refreshed",
+ 'workflowActivityVNext.run.refreshing': 'Refreshing…',
+ 'workflowActivityVNext.run.refreshingDetail': 'Refreshing run details…',
+ 'workflowActivityVNext.run.refreshSucceeded': 'Run details refreshed',
+ 'workflowActivityVNext.run.relatedRuns': 'Related runs',
+ 'workflowActivityVNext.run.lineageUnavailable':
+ 'Related run history is unavailable.',
+ 'workflowActivityVNext.run.detailFallbackDescription':
+ 'The run detail request is unavailable, but the selected Activity row is still visible.',
+ 'workflowActivityVNext.run.detailUnavailableTitle':
+ 'Detailed run data is temporarily unavailable.',
+ 'workflowActivityVNext.run.detailUnavailableDescription':
+ 'The selected run remains highlighted in its workflow history. Retry to load its immutable detail, graph, timeline, and output.',
+ 'workflowActivityVNext.run.noRelatedRuns': 'No related runs.',
+ 'workflowActivityVNext.run.openRunAria': 'Open {runId}',
+ 'workflowActivityVNext.run.retryHistory': 'Retry history',
+ 'workflowActivityVNext.run.retry': 'Retry failed step',
+ 'workflowActivityVNext.run.retryUnavailable':
+ 'Retry is available when one step has failed.',
+ 'workflowActivityVNext.run.runAgain': 'Run again',
+ 'workflowActivityVNext.run.runAgainUnavailable':
+ "Run again isn't available for this run.",
+ 'workflowActivityVNext.run.rootRun': 'Root run',
+ 'workflowActivityVNext.run.sourceRun': 'Source run',
+ 'workflowActivityVNext.run.sourceImmutable':
+ "This creates a separate run. The source run won't change.",
+ 'workflowActivityVNext.run.subWorkflows': 'Sub-workflows',
+ 'workflowActivityVNext.run.childRuns': 'Child runs',
+ 'workflowActivityVNext.run.details': 'Details',
+ 'workflowActivityVNext.run.attempt': 'Attempt',
+ 'workflowActivityVNext.run.depth': 'Depth',
+ 'workflowActivityVNext.run.definitionRevision': 'Definition revision',
+ 'workflowActivityVNext.run.definitionVersion': 'Definition version',
+ 'workflowActivityVNext.run.editWorkflow': 'Edit workflow',
+ 'workflowActivityVNext.run.reviewInput': 'Review input',
+ 'workflowActivityVNext.run.reviewSettings': 'Review settings',
+ 'workflowActivityVNext.run.recommendedNextSteps': 'Recommended: {actions}',
+ 'workflowActivityVNext.run.roleReplies': 'Role replies',
+ 'workflowActivityVNext.run.severity': 'Severity',
+ 'workflowActivityVNext.run.startFailed': "The new run couldn't be started",
+ 'workflowActivityVNext.run.startingStep': 'Starting step',
+ 'workflowActivityVNext.run.statusUrl': 'Status URL',
+ 'workflowActivityVNext.run.statisticsUsage': 'Statistics and usage',
+ 'workflowActivityVNext.run.step': 'Step',
+ 'workflowActivityVNext.run.stepFailedSummary': 'This step did not complete.',
+ 'workflowActivityVNext.run.steps': 'Steps',
+ 'workflowActivityVNext.run.timeline': 'Timeline',
+ 'workflowActivityVNext.run.totalSteps': 'Total steps',
+ 'workflowActivityVNext.run.totalTokens': 'Total tokens',
+ 'workflowActivityVNext.run.type': 'Type',
+ 'workflowActivityVNext.run.unavailable': 'Run unavailable',
+ 'workflowActivityVNext.run.unavailableDescription':
+ "This run couldn't be loaded.",
+ 'workflowActivityVNext.run.unavailableGuidance':
+ 'Try again to load this run.',
+ 'workflowActivityVNext.settings.account': 'Account',
+ 'workflowActivityVNext.settings.accountDescription':
+ 'Your profile and access.',
+ 'workflowActivityVNext.settings.accountLoading': 'Loading account…',
+ 'workflowActivityVNext.settings.accountUnavailable': 'Account unavailable',
+ 'workflowActivityVNext.settings.advanced': 'Advanced',
+ 'workflowActivityVNext.settings.advancedDescription':
+ 'Read-only execution details used when starting workflows.',
+ 'workflowActivityVNext.settings.ai': 'AI defaults',
+ 'workflowActivityVNext.settings.aiDescription':
+ 'Choose the default service and model for new AI sessions.',
+ 'workflowActivityVNext.settings.authenticated': 'Signed in',
+ 'workflowActivityVNext.settings.catalogUnavailable':
+ 'Your saved choice is unchanged. Try again to load available services.',
+ 'workflowActivityVNext.settings.catalogUnavailableTitle':
+ 'Services unavailable',
+ 'workflowActivityVNext.settings.defaultModel': 'Default model',
+ 'workflowActivityVNext.settings.defaultModelHelp':
+ 'Leave unset to use the service default.',
+ 'workflowActivityVNext.settings.delayed':
+ 'Changes are taking longer to appear',
+ 'workflowActivityVNext.settings.description': 'Personal defaults and access.',
+ 'workflowActivityVNext.settings.discard': 'Restore saved settings',
+ 'workflowActivityVNext.settings.discardLeave': 'Discard and leave',
+ 'workflowActivityVNext.settings.email': 'Email',
+ 'workflowActivityVNext.settings.emailVerification': 'Email verification',
+ 'workflowActivityVNext.settings.executionTarget': 'Execution target',
+ 'workflowActivityVNext.settings.expires': 'Expires',
+ 'workflowActivityVNext.settings.failed': "Changes couldn't be saved",
+ 'workflowActivityVNext.settings.groups': 'Groups',
+ 'workflowActivityVNext.settings.llmLoading': 'Loading AI defaults…',
+ 'workflowActivityVNext.settings.llmUnavailable': 'AI defaults unavailable',
+ 'workflowActivityVNext.settings.name': 'Name',
+ 'workflowActivityVNext.settings.manageServiceAccess': 'Manage service access',
+ 'workflowActivityVNext.settings.localRuntimeLabel': 'Local runtime',
+ 'workflowActivityVNext.settings.notLoaded': 'Not loaded',
+ 'workflowActivityVNext.settings.notAccepted':
+ 'The settings update was not accepted.',
+ 'workflowActivityVNext.settings.observed': 'Changes saved',
+ 'workflowActivityVNext.settings.providerDefault': 'Provider default',
+ 'workflowActivityVNext.settings.preferredService': 'Preferred service',
+ 'workflowActivityVNext.settings.preferredServiceHelp':
+ 'Choose which service new sessions use.',
+ 'workflowActivityVNext.settings.provider': 'Sign-in method',
+ 'workflowActivityVNext.settings.profile': 'Profile',
+ 'workflowActivityVNext.settings.profileUnavailable':
+ 'Profile details are unavailable.',
+ 'workflowActivityVNext.settings.remoteRuntimeLabel': 'Remote runtime',
+ 'workflowActivityVNext.settings.roles': 'Roles',
+ 'workflowActivityVNext.settings.runtimeLoading':
+ 'Loading connection details…',
+ 'workflowActivityVNext.settings.runtimeUnavailable':
+ 'Workflow execution details unavailable',
+ 'workflowActivityVNext.settings.runtimeUrl': 'Runtime URL',
+ 'workflowActivityVNext.settings.scope': 'Scope',
+ 'workflowActivityVNext.settings.serviceAccessFailed':
+ 'Could not start service access review. Try again.',
+ 'workflowActivityVNext.settings.sessionActive': 'Active',
+ 'workflowActivityVNext.settings.sessionExpired': 'Expired',
+ 'workflowActivityVNext.settings.sessionExpiringSoon': 'Expiring soon',
+ 'workflowActivityVNext.settings.sessionInvalid': 'Invalid',
+ 'workflowActivityVNext.settings.sessionState': 'Session state',
+ 'workflowActivityVNext.settings.sessionAccess': 'Session & access',
+ 'workflowActivityVNext.settings.signInAgain': 'Sign in again',
+ 'workflowActivityVNext.settings.signOut': 'Sign out',
+ 'workflowActivityVNext.settings.retryGuidance':
+ 'Try loading this section again.',
+ 'workflowActivityVNext.settings.save': 'Save changes',
+ 'workflowActivityVNext.settings.saveLeave': 'Save and leave',
+ 'workflowActivityVNext.settings.saveObserved': 'Settings saved',
+ 'workflowActivityVNext.settings.savedModelUnavailable':
+ 'The saved model is unavailable. Your saved value remains unchanged.',
+ 'workflowActivityVNext.settings.sectionsAria': 'Settings sections',
+ 'workflowActivityVNext.settings.serviceDefaultModel':
+ 'Uses the service default model.',
+ 'workflowActivityVNext.settings.stay': 'Stay',
+ 'workflowActivityVNext.settings.systemDefault': 'System default',
+ 'workflowActivityVNext.settings.systemDefaultModel':
+ 'Uses the system-selected service and model.',
+ 'workflowActivityVNext.settings.title': 'Settings',
+ 'workflowActivityVNext.settings.unauthorized': 'Unauthorized',
+ 'workflowActivityVNext.settings.unauthorizedDescription':
+ 'Your current session cannot view account or capability details.',
+ 'workflowActivityVNext.settings.unsaved': 'Unsaved changes',
+ 'workflowActivityVNext.settings.unsavedActionsAria':
+ 'Unsaved settings actions',
+ 'workflowActivityVNext.settings.unsavedDescription':
+ 'Your AI defaults have not been saved.',
+ 'workflowActivityVNext.settings.unsavedLeaveDescription':
+ 'Save your changes, discard them, or stay in Settings.',
+ 'workflowActivityVNext.settings.unsavedLeaveTitle':
+ 'Unsaved AI default changes',
+ 'workflowActivityVNext.settings.userId': 'User ID',
+ 'workflowActivityVNext.settings.verified': 'Verified',
+ 'workflowActivityVNext.settings.notVerified': 'Not verified',
+ 'workflowActivityVNext.settings.workflowExecution': 'Workflow execution',
+ 'workflowActivityVNext.schedule.actionAccepted': 'Schedule action accepted.',
+ 'workflowActivityVNext.schedule.backToSchedules': 'Back to schedules',
+ 'workflowActivityVNext.schedule.change': 'Change schedule',
+ 'workflowActivityVNext.schedule.completedAt': 'Completed {date}',
+ 'workflowActivityVNext.schedule.detailLoadFailed':
+ "Schedule couldn't be loaded",
+ 'workflowActivityVNext.schedule.detailLoading': 'Loading schedule details…',
+ 'workflowActivityVNext.schedule.detailTitle': 'Schedule details',
+ 'workflowActivityVNext.schedule.historyLoading': 'Loading history…',
+ 'workflowActivityVNext.schedule.historyLoadFailed':
+ "History couldn't be loaded",
+ 'workflowActivityVNext.schedule.selectedContextAria':
+ 'Schedule {scheduleName} in workflow {workflowName}',
+ 'workflowActivityVNext.schedule.scheduleContext': 'Schedule: {name}',
+ 'workflowActivityVNext.schedule.workflowContext': 'Workflow: {name}',
+ 'workflowActivityVNext.schedule.overview': 'Overview',
+ 'workflowActivityVNext.schedule.history': 'History',
+ 'workflowActivityVNext.schedule.recentAttempts': 'Recent attempts',
+ 'workflowActivityVNext.schedule.historyDescription':
+ 'Schedule attempts can fail before a Workflow Run exists.',
+ 'workflowActivityVNext.schedule.viewRelatedRuns':
+ 'View related runs in Activity',
+ 'workflowActivityVNext.schedule.scheduledTime': 'Scheduled time',
+ 'workflowActivityVNext.schedule.source': 'Source',
+ 'workflowActivityVNext.schedule.outcome': 'Schedule outcome',
+ 'workflowActivityVNext.schedule.completedTime': 'Completed time',
+ 'workflowActivityVNext.schedule.technicalDetails': 'Technical details',
+ 'workflowActivityVNext.schedule.noAttempts': 'No attempts yet',
+ 'workflowActivityVNext.schedule.scheduledAttemptFailed':
+ 'The scheduled attempt could not start the Workflow.',
+ 'workflowActivityVNext.schedule.manualAttemptFailed':
+ 'The manual attempt could not start the Workflow.',
+ 'workflowActivityVNext.schedule.openRunAria':
+ 'Open Run created by the Schedule attempt at {date}',
+ 'workflowActivityVNext.schedule.runCreated': 'Run created',
+ 'workflowActivityVNext.schedule.failedToStart': 'Failed to start',
+ 'workflowActivityVNext.schedule.attemptAccepted': 'Accepted',
+ 'workflowActivityVNext.schedule.enable': 'Enable',
+ 'workflowActivityVNext.schedule.failed': 'Failed',
+ 'workflowActivityVNext.schedule.failedCount': '{count} failed',
+ 'workflowActivityVNext.schedule.failedFires': 'Failed fires',
+ 'workflowActivityVNext.schedule.lastFire': 'Last fire',
+ 'workflowActivityVNext.schedule.manual': 'Manual',
+ 'workflowActivityVNext.schedule.nextFireLabel': 'Next fire',
+ 'workflowActivityVNext.schedule.noFires': 'No attempts yet',
+ 'workflowActivityVNext.schedule.pause': 'Pause',
+ 'workflowActivityVNext.schedule.recentFires': 'Recent fires',
+ 'workflowActivityVNext.schedule.runNow': 'Run now',
+ 'workflowActivityVNext.schedule.scheduled': 'Scheduled',
+ 'workflowActivityVNext.schedule.scheduledAt': 'Scheduled {date}',
+ 'workflowActivityVNext.schedule.totalCount': '{count} total',
+ 'workflowActivityVNext.schedule.totalFires': 'Total fires',
+ 'workflowActivityVNext.schedule.viewAria': 'View {name}',
+ 'workflowActivityVNext.schedule.create': 'Create schedule',
+ 'workflowActivityVNext.schedule.createTitle': 'Create schedule',
+ 'workflowActivityVNext.schedule.created':
+ 'Schedule request accepted. It will appear in the list shortly.',
+ 'workflowActivityVNext.schedule.cron': 'Cron expression',
+ 'workflowActivityVNext.schedule.cronSummary': 'Cron: {cron}',
+ 'workflowActivityVNext.schedule.runs': 'Runs',
+ 'workflowActivityVNext.schedule.technicalFormat': 'Technical format',
+ 'workflowActivityVNext.schedule.hourly': 'Every hour',
+ 'workflowActivityVNext.schedule.daily': 'Every day',
+ 'workflowActivityVNext.schedule.everyWeekdaySummary':
+ 'Every weekday at {time}',
+ 'workflowActivityVNext.schedule.everyDaySummary': 'Every day at {time}',
+ 'workflowActivityVNext.schedule.customRecurrence': 'Custom schedule',
+ 'workflowActivityVNext.schedule.deleteAccepted':
+ 'Schedule deletion accepted.',
+ 'workflowActivityVNext.schedule.deleteAria': 'Delete {name}',
+ 'workflowActivityVNext.schedule.disableAria': 'Disable {name}',
+ 'workflowActivityVNext.schedule.disabled': 'Disabled',
+ 'workflowActivityVNext.schedule.paused': 'Paused',
+ 'workflowActivityVNext.schedule.more': 'More',
+ 'workflowActivityVNext.schedule.moreActionsAria': 'More schedule actions',
+ 'workflowActivityVNext.schedule.editAction': 'Edit schedule',
+ 'workflowActivityVNext.schedule.deleteConfirmTitle': 'Delete {name}?',
+ 'workflowActivityVNext.schedule.deleteConfirmDescription':
+ '{name} will stop running on schedule.',
+ 'workflowActivityVNext.schedule.deleteAction': 'Delete schedule',
+ 'workflowActivityVNext.schedule.keepSchedule': 'Keep schedule',
+ 'workflowActivityVNext.schedule.editAria': 'Edit {name}',
+ 'workflowActivityVNext.schedule.editTitle': 'Edit schedule',
+ 'workflowActivityVNext.schedule.empty': 'No schedules yet',
+ 'workflowActivityVNext.schedule.emptyDescription':
+ 'Create a recurring schedule for this published Workflow.',
+ 'workflowActivityVNext.schedule.enableAria': 'Enable {name}',
+ 'workflowActivityVNext.schedule.enabled': 'Enabled',
+ 'workflowActivityVNext.schedule.howOften': 'How often',
+ 'workflowActivityVNext.schedule.loadFailed': "Schedules couldn't be loaded",
+ 'workflowActivityVNext.schedule.loading': 'Loading schedules…',
+ 'workflowActivityVNext.schedule.name': 'Name',
+ 'workflowActivityVNext.schedule.new': 'New schedule',
+ 'workflowActivityVNext.schedule.noPrompt': 'No prompt',
+ 'workflowActivityVNext.schedule.nextFire': 'Next {date}',
+ 'workflowActivityVNext.schedule.noUpcomingAttempt': 'No upcoming attempt',
+ 'workflowActivityVNext.schedule.nextScheduled': 'Next scheduled',
+ 'workflowActivityVNext.schedule.lastAttempt': 'Last attempt',
+ 'workflowActivityVNext.schedule.totalAttempts': 'Total attempts',
+ 'workflowActivityVNext.schedule.failedAttempts': 'Failed attempts',
+ 'workflowActivityVNext.schedule.advancedDetails': 'Advanced details',
+ 'workflowActivityVNext.schedule.nextFiveFires': 'Next five fire times',
+ 'workflowActivityVNext.schedule.open': 'Schedules',
+ 'workflowActivityVNext.schedule.openAria': 'Manage schedules for {name}',
+ 'workflowActivityVNext.schedule.preview': 'Preview next fires',
+ 'workflowActivityVNext.schedule.previewTitle': 'Next scheduled fires',
+ 'workflowActivityVNext.schedule.previewing': 'Previewing schedule…',
+ 'workflowActivityVNext.schedule.published': 'Published',
+ 'workflowActivityVNext.schedule.prompt': 'Run input (optional)',
+ 'workflowActivityVNext.schedule.promptReview': 'Run input',
+ 'workflowActivityVNext.schedule.publishBeforeOpen':
+ 'Publish this Workflow before managing schedules.',
+ 'workflowActivityVNext.schedule.refreshAria': 'Refresh schedules',
+ 'workflowActivityVNext.schedule.refreshing': 'Refreshing schedules…',
+ 'workflowActivityVNext.schedule.refreshingDetail':
+ 'Refreshing schedule details…',
+ 'workflowActivityVNext.schedule.refreshingHistory':
+ 'Refreshing schedule history…',
+ 'workflowActivityVNext.schedule.overviewRegionAria': 'Schedule overview',
+ 'workflowActivityVNext.schedule.historyRegionAria': 'Schedule history',
+ 'workflowActivityVNext.schedule.runNowAria': 'Run {name} now',
+ 'workflowActivityVNext.schedule.repeat': 'Repeat',
+ 'workflowActivityVNext.schedule.repeatSummary': '{repeat} at {time}',
+ 'workflowActivityVNext.schedule.weekly': 'Every week',
+ 'workflowActivityVNext.schedule.monthly': 'Every month',
+ 'workflowActivityVNext.schedule.dayOfWeek': 'Day of week',
+ 'workflowActivityVNext.schedule.dayOfMonth': 'Day of month',
+ 'workflowActivityVNext.schedule.weeklySummary': 'Every {day} at {time}',
+ 'workflowActivityVNext.schedule.monthlySummary':
+ 'Every month on day {day} at {time}',
+ 'workflowActivityVNext.schedule.weekday.0': 'Sunday',
+ 'workflowActivityVNext.schedule.weekday.1': 'Monday',
+ 'workflowActivityVNext.schedule.weekday.2': 'Tuesday',
+ 'workflowActivityVNext.schedule.weekday.3': 'Wednesday',
+ 'workflowActivityVNext.schedule.weekday.4': 'Thursday',
+ 'workflowActivityVNext.schedule.weekday.5': 'Friday',
+ 'workflowActivityVNext.schedule.weekday.6': 'Saturday',
+ 'workflowActivityVNext.schedule.reviewAction': 'Review schedule',
+ 'workflowActivityVNext.schedule.reviewTitle': 'Review schedule',
+ 'workflowActivityVNext.schedule.save': 'Save changes',
+ 'workflowActivityVNext.schedule.scheduleName': 'Schedule name',
+ 'workflowActivityVNext.schedule.time': 'Time',
+ 'workflowActivityVNext.schedule.timezone': 'Timezone',
+ 'workflowActivityVNext.schedule.title': 'Schedules',
+ 'workflowActivityVNext.schedule.titleForWorkflow': 'Schedules for {name}',
+ 'workflowActivityVNext.schedule.updateAccepted': 'Schedule update accepted.',
+ 'workflowActivityVNext.schedule.useRepeatBuilder': 'use the repeat builder',
+ 'workflowActivityVNext.schedule.unavailableDescription':
+ 'Publish a runnable Workflow before creating a recurring schedule.',
+ 'workflowActivityVNext.schedule.unavailableTitle':
+ 'Schedule is unavailable until this Workflow is published',
+ 'workflowActivityVNext.schedule.updated': 'Schedule update accepted',
+ 'workflowActivityVNext.schedule.weekdays': 'Weekdays',
+ 'workflowActivityVNext.schedule.writeCron': 'write it as cron instead',
+ 'workflowActivityVNext.schedule.workflow': 'Workflow',
+ 'workflowActivityVNext.schedule.enabledAfterCreation':
+ 'Enabled after creation',
+ 'workflowActivityVNext.state.forbidden':
+ "You don't have access to this workspace",
+ 'workflowActivityVNext.state.unauthorized': 'Sign in to continue',
+ 'workflowActivityVNext.unavailable.body':
+ 'Check the address or return to another section.',
+ 'workflowActivityVNext.unavailable.description':
+ "The page you requested isn't available.",
+ 'workflowActivityVNext.unavailable.title': 'Unavailable',
+ 'workflowActivityVNext.workflows.activeView': 'Active workflows',
+ 'workflowActivityVNext.workflows.allView': 'All workflows',
+ 'workflowActivityVNext.workflows.archive': 'Archive',
+ 'workflowActivityVNext.workflows.archiveCheckAgain': 'Check again',
+ 'workflowActivityVNext.workflows.archiveConfirm': 'Archive workflow',
+ 'workflowActivityVNext.workflows.archiveDescription':
+ 'This stops new runs for the published workflow. Its editable draft, published revisions, and Activity history remain available. Publishing it again restores it.',
+ 'workflowActivityVNext.workflows.archiveFailed':
+ "Workflow couldn't be archived",
+ 'workflowActivityVNext.workflows.archiveSuccess': 'Workflow archived',
+ 'workflowActivityVNext.workflows.archiveTitle': 'Archive this workflow?',
+ 'workflowActivityVNext.workflows.archiveTryAgain': 'Try again',
+ 'workflowActivityVNext.workflows.archivedStatus': 'Archived',
+ 'workflowActivityVNext.workflows.archivedView': 'Show archived workflows',
+ 'workflowActivityVNext.workflows.clearFilters': 'Clear filters',
+ 'workflowActivityVNext.workflows.columnActions': 'Actions',
+ 'workflowActivityVNext.workflows.columnStatus': 'Status',
+ 'workflowActivityVNext.workflows.columnUpdated': 'Last updated',
+ 'workflowActivityVNext.workflows.columnWorkflow': 'Workflow',
+ 'workflowActivityVNext.workflows.copyReference': 'Copy workflow reference',
+ 'workflowActivityVNext.workflows.description':
+ 'Create, edit, and run your workflows.',
+ 'workflowActivityVNext.workflows.descriptionAria': 'Description for {name}',
+ 'workflowActivityVNext.workflows.draftsUnavailable':
+ 'Draft workflows unavailable',
+ 'workflowActivityVNext.workflows.draftsUnavailableDescription':
+ 'Try again to load draft workflows.',
+ 'workflowActivityVNext.workflows.draftsView': 'Drafts',
+ 'workflowActivityVNext.workflows.draftStatus': 'Draft',
+ 'workflowActivityVNext.workflows.duplicateNameWarning':
+ 'Another workflow already uses this name. Duplicate names are allowed.',
+ 'workflowActivityVNext.workflows.deleteDescription':
+ 'This deletes only the editable draft. Published versions and run history remain available.',
+ 'workflowActivityVNext.workflows.deleteDraft': 'Delete draft',
+ 'workflowActivityVNext.workflows.deleteFailed': "Draft couldn't be deleted",
+ 'workflowActivityVNext.workflows.deleteTitle': 'Delete editable draft?',
+ 'workflowActivityVNext.workflows.empty': 'No workflows yet',
+ 'workflowActivityVNext.workflows.emptyDescription':
+ 'Create a workflow to get started.',
+ 'workflowActivityVNext.workflows.loadMore': 'Load more',
+ 'workflowActivityVNext.workflows.loadMoreFailed':
+ "More workflows couldn't be loaded",
+ 'workflowActivityVNext.workflows.loading': 'Loading workflows…',
+ 'workflowActivityVNext.workflows.moreActionsAria':
+ 'More actions for {name} in {owner}',
+ 'workflowActivityVNext.workflows.new': 'New workflow',
+ 'workflowActivityVNext.workflows.noMatch': 'No matching workflows',
+ 'workflowActivityVNext.workflows.noMatchDescription':
+ 'Try a different search or filter.',
+ 'workflowActivityVNext.workflows.openAria': 'Open {name} in {owner}',
+ 'workflowActivityVNext.workflows.runAria': 'Run {name}',
+ 'workflowActivityVNext.workflows.refreshAria': 'Refresh workflows',
+ 'workflowActivityVNext.workflows.retryAria': 'Retry workflows',
+ 'workflowActivityVNext.workflows.runFromEditor':
+ 'Open the editor to validate and run this workflow.',
+ 'workflowActivityVNext.workflows.search': 'Search workflows',
+ 'workflowActivityVNext.workflows.searchAria': 'Search workflows',
+ 'workflowActivityVNext.workflows.title': 'Workflows',
+ 'workflowActivityVNext.workflows.unavailable': 'Workflows unavailable',
+ 'workflowActivityVNext.workflows.unavailableDescription':
+ 'Try again to load your workflows.',
+ 'workflowActivityVNext.workflows.viewFilter': 'Workflow view',
+ 'workflowActivityVNext.workflows.partialUnavailable':
+ "Some workflows couldn't be loaded",
+ 'workflowActivityVNext.workflows.publishedStatus': 'Published',
+ 'workflowActivityVNext.workflows.referenceCopied':
+ 'Workflow reference copied',
+ 'workflowActivityVNext.workflows.referenceCopyFailed':
+ "Workflow reference couldn't be copied",
+ 'workflowActivityVNext.workflows.rename': 'Rename',
+ 'workflowActivityVNext.workflows.renameFailed':
+ "Workflow couldn't be renamed",
+ 'workflowActivityVNext.workflows.renameSave': 'Save name',
+ 'workflowActivityVNext.workflows.renameSuccess': 'Workflow renamed',
+ 'workflowActivityVNext.workflows.renameTitle': 'Rename workflow',
+ 'workflowActivityVNext.workflows.viewActivity': 'View activity',
+ 'workflowActivityVNext.workflows.viewActivityAria':
+ 'View activity for {name} in {owner}',
+ 'workflowActivityVNext.workflows.workspaceOwner': 'Workspace',
+} as const;
+
+export default workflowActivityVNextMessages;
diff --git a/apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts b/apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts
new file mode 100644
index 0000000000..3b25d49dc6
--- /dev/null
+++ b/apps/aevatar-console-web/src/locales/workflowActivityVNextMessages.zh-CN.ts
@@ -0,0 +1,975 @@
+import enUSMessages from './workflowActivityVNextMessages.en-US';
+
+const workflowActivityVNextMessages: Record =
+ {
+ ...enUSMessages,
+ 'workflowActivityVNext.home.loading': '正在打开你的工作流…',
+ 'workflowActivityVNext.home.unavailable': '暂时无法打开工作流',
+ 'workflowActivityVNext.home.loadFailed': '暂时无法获取你的工作区,请重试。',
+ 'workflowActivityVNext.home.scopeUnavailable':
+ '当前账号没有可用工作区,请检查访问权限后重试。',
+ 'workflowActivityVNext.home.signInRequired': '登录后即可打开你的工作流。',
+ 'workflowActivityVNext.home.signIn': '登录',
+
+ 'workflowActivityVNext.workflows.refreshFailed':
+ '工作流列表刷新失败,当前仍显示上次加载的列表。',
+ 'workflowActivityVNext.new.cancelGeneration': '取消生成',
+ 'workflowActivityVNext.new.generating': '正在生成工作流…',
+ 'workflowActivityVNext.new.validating': '正在校验生成的工作流…',
+ 'workflowActivityVNext.new.savingGenerated': '正在保存工作流…',
+ 'workflowActivityVNext.new.generationTimedOut':
+ '生成已超过两分钟,未保存草稿。你可以重试。',
+ 'workflowActivityVNext.new.generationCancelled':
+ '已取消生成,名称和描述已保留,未保存草稿。',
+ 'workflowActivityVNext.new.generationReceiving':
+ '已收到生成进度,正在等待完整工作流。',
+ 'workflowActivityVNext.new.generationWaiting':
+ '正在等待生成进度,你可以随时取消。',
+ 'workflowActivityVNext.new.generationDetails': '生成详情',
+ 'workflowActivityVNext.workflows.deleteSuccess': '草稿已删除',
+ 'workflowActivityVNext.workflows.checkFailed': '暂时无法确认操作是否完成',
+ 'workflowActivityVNext.workflows.changeObserving':
+ '请求已接受,正在确认是否完成…',
+ 'workflowActivityVNext.workflows.changeDelayed':
+ '处理时间比预期更长,暂时还无法确认是否完成。',
+ 'workflowActivityVNext.workflows.checkingDescription':
+ '将持续检查最多 30 秒。关闭弹窗不会撤销已提交的请求。',
+ 'workflowActivityVNext.workflows.checkAgainDescription':
+ '点击“再次检查”查看结果,只检查状态,不会重复提交请求。',
+ 'workflowActivityVNext.arguments.addItem': '添加一项',
+ 'workflowActivityVNext.arguments.addItemAria': '向 {path} 添加一项',
+ 'workflowActivityVNext.arguments.addProperty': '添加属性',
+ 'workflowActivityVNext.arguments.addPropertyAria': '向 {path} 添加属性',
+ 'workflowActivityVNext.arguments.columnProperty': '属性',
+ 'workflowActivityVNext.arguments.columnType': '类型',
+ 'workflowActivityVNext.arguments.emptyArray': '暂无项目',
+ 'workflowActivityVNext.arguments.emptyObject': '暂无属性',
+ 'workflowActivityVNext.arguments.duplicateProperty':
+ '属性名不能重复,此对象中已存在“{property}”。',
+ 'workflowActivityVNext.arguments.invalidObject':
+ '请先输入有效的 JSON 对象,再切换到字段模式。',
+ 'workflowActivityVNext.arguments.item': '第 {number} 项',
+ 'workflowActivityVNext.arguments.jsonAria': '参数 JSON',
+ 'workflowActivityVNext.arguments.mode.fields': '字段',
+ 'workflowActivityVNext.arguments.mode.json': 'JSON',
+ 'workflowActivityVNext.arguments.nullValue': '无值',
+ 'workflowActivityVNext.arguments.propertyName': '属性名',
+ 'workflowActivityVNext.arguments.propertyNameAria': '{path} 的属性名',
+ 'workflowActivityVNext.arguments.removeItemAria': '删除第 {number} 项',
+ 'workflowActivityVNext.arguments.removePropertyAria': '删除 {path}',
+ 'workflowActivityVNext.arguments.type.array': '数组',
+ 'workflowActivityVNext.arguments.type.boolean': '布尔值',
+ 'workflowActivityVNext.arguments.type.null': '空值',
+ 'workflowActivityVNext.arguments.type.number': '数字',
+ 'workflowActivityVNext.arguments.type.object': '对象',
+ 'workflowActivityVNext.arguments.type.string': '文本',
+ 'workflowActivityVNext.arguments.unnamedProperty': '未命名属性',
+ 'workflowActivityVNext.arguments.unsupportedNumber':
+ '此 JSON 包含字段模式无法安全表示的数字。请在 JSON 模式中编辑,或将其改为字符串。',
+ 'workflowActivityVNext.arguments.valueAria': '{path} 的值',
+ 'workflowActivityVNext.arguments.valueTypeAria': '{path} 的值类型',
+ 'workflowActivityVNext.activity.allOrigins': '全部来源',
+ 'workflowActivityVNext.activity.allStatuses': '全部状态',
+ 'workflowActivityVNext.activity.afterPlaceholder': '开始时间之后',
+ 'workflowActivityVNext.activity.beforePlaceholder': '开始时间之前',
+ 'workflowActivityVNext.activity.clearWorkflowFilter': '显示全部工作流',
+ 'workflowActivityVNext.activity.columnDuration': '耗时',
+ 'workflowActivityVNext.activity.columnInputPreview': '输入摘要',
+ 'workflowActivityVNext.activity.columnOrigin': '来源',
+ 'workflowActivityVNext.activity.columnRun': '工作流',
+ 'workflowActivityVNext.activity.columnStarted': '开始时间',
+ 'workflowActivityVNext.activity.columnStatus': '状态',
+ 'workflowActivityVNext.activity.columnUpdated': '更新时间',
+ 'workflowActivityVNext.activity.description':
+ '查看最近的工作流运行并打开详情。',
+ 'workflowActivityVNext.activity.empty': '暂无运行记录',
+ 'workflowActivityVNext.activity.emptyDescription':
+ '工作流开始运行后,记录会显示在这里。',
+ 'workflowActivityVNext.activity.loading': '正在加载活动记录…',
+ 'workflowActivityVNext.activity.noMatch': '没有匹配的运行记录',
+ 'workflowActivityVNext.activity.originChat': '聊天',
+ 'workflowActivityVNext.activity.originEditor': '编辑器',
+ 'workflowActivityVNext.activity.originFilter': '运行来源',
+ 'workflowActivityVNext.activity.originMember': '团队成员',
+ 'workflowActivityVNext.activity.originSchedule': '计划任务',
+ 'workflowActivityVNext.activity.originService': '服务',
+ 'workflowActivityVNext.activity.nextPage': '下一页',
+ 'workflowActivityVNext.activity.page': '第 {page} 页',
+ 'workflowActivityVNext.activity.pageOf': '第 {page} / {total} 页',
+ 'workflowActivityVNext.activity.pageUnavailable': '无法加载当前页',
+ 'workflowActivityVNext.activity.previousPage': '上一页',
+ 'workflowActivityVNext.activity.search': '搜索运行记录',
+ 'workflowActivityVNext.activity.searchAria': '搜索运行记录',
+ 'workflowActivityVNext.activity.searchButton': '搜索',
+ 'workflowActivityVNext.activity.statusCancelled': '已取消',
+ 'workflowActivityVNext.activity.statusCompleted': '已完成',
+ 'workflowActivityVNext.activity.statusFilter': '运行状态',
+ 'workflowActivityVNext.activity.statusQueued': '排队中',
+ 'workflowActivityVNext.activity.statusRunning': '运行中',
+ 'workflowActivityVNext.activity.statusTimedOut': '已超时',
+ 'workflowActivityVNext.activity.statusWaiting': '等待中',
+ 'workflowActivityVNext.activity.title': '活动',
+ 'workflowActivityVNext.activity.unavailable': '活动记录不可用',
+ 'workflowActivityVNext.activity.unnamed': '未命名工作流',
+ 'workflowActivityVNext.activity.unavailableDescription':
+ '请重试加载最近的工作流运行记录。',
+ 'workflowActivityVNext.activity.removeWorkflowFilterAria':
+ '移除工作流筛选条件 {workflowId}',
+ 'workflowActivityVNext.activity.removeScheduleFilterAria':
+ '移除计划任务筛选条件 {scheduleId}',
+ 'workflowActivityVNext.activity.scheduleFilterLabel':
+ '计划任务:{scheduleId}',
+ 'workflowActivityVNext.activity.removeOriginFilterAria':
+ '移除来源筛选条件 {origin}',
+ 'workflowActivityVNext.activity.originFilterContextLabel': '来源:{origin}',
+ 'workflowActivityVNext.activity.workflowFilterInvalidDescription':
+ '此活动链接不包含工作流身份。',
+ 'workflowActivityVNext.activity.workflowFilterInvalidTitle':
+ '请选择要筛选活动的工作流',
+ 'workflowActivityVNext.activity.workflowFilterLabel':
+ '工作流:{workflowId}',
+ 'workflowActivityVNext.activity.workflowFilterLoading':
+ '正在加载工作流活动…',
+ 'workflowActivityVNext.activity.workflowFilterResolutionFailed':
+ '工作流活动不可用',
+ 'workflowActivityVNext.activity.workflowFilterResolutionFailedDescription':
+ '请重试或移除工作流筛选条件。',
+ 'workflowActivityVNext.activity.workflowFilterUnavailableDescription':
+ '此工作流未提供活动筛选条件,因此不显示任何运行记录。',
+ 'workflowActivityVNext.activity.workflowFilterUnavailableTitle':
+ '活动筛选不可用',
+ 'workflowActivityVNext.brand.subtitle': '自动化账本',
+ 'workflowActivityVNext.common.back': '返回',
+ 'workflowActivityVNext.common.cancel': '取消',
+ 'workflowActivityVNext.common.close': '关闭',
+ 'workflowActivityVNext.common.empty': '空',
+ 'workflowActivityVNext.common.failed': '失败',
+ 'workflowActivityVNext.common.no': '否',
+ 'workflowActivityVNext.common.open': '打开',
+ 'workflowActivityVNext.common.pending': '等待中',
+ 'workflowActivityVNext.common.refresh': '刷新',
+ 'workflowActivityVNext.common.retry': '重试',
+ 'workflowActivityVNext.common.run': '运行',
+ 'workflowActivityVNext.common.succeeded': '成功',
+ 'workflowActivityVNext.common.technicalDetails': '技术详情',
+ 'workflowActivityVNext.common.unavailable': '不可用',
+ 'workflowActivityVNext.common.unknown': '未知',
+ 'workflowActivityVNext.common.yes': '是',
+ 'workflowActivityVNext.editor.addNode': '添加节点',
+ 'workflowActivityVNext.editor.addNodeFailed': '无法添加节点',
+ 'workflowActivityVNext.editor.activityDelayed':
+ '此运行显示在活动中所需时间较长',
+ 'workflowActivityVNext.editor.activityDelayedDescription':
+ '请再次检查最新状态。',
+ 'workflowActivityVNext.editor.activityObserved': '已在活动中记录',
+ 'workflowActivityVNext.editor.activityObservedDescription':
+ '你可以查看运行详情和进度。',
+ 'workflowActivityVNext.editor.activityUnavailable': '活动暂不可用',
+ 'workflowActivityVNext.editor.activityUnavailableDescription':
+ '请再次检查最新状态。',
+ 'workflowActivityVNext.editor.backAria': '返回工作流列表',
+ 'workflowActivityVNext.editor.canvas': '画布',
+ 'workflowActivityVNext.editor.canvasAria': '工作流画布',
+ 'workflowActivityVNext.editor.canvasUpdateFailed': '无法更新工作流',
+ 'workflowActivityVNext.editor.description': '构建、测试并完善这个工作流。',
+ 'workflowActivityVNext.editor.discardLeave': '放弃并离开',
+ 'workflowActivityVNext.editor.emptyCanvas':
+ '添加第一个可执行节点,使此工作流可运行。',
+ 'workflowActivityVNext.editor.loading': '正在加载工作流…',
+ 'workflowActivityVNext.editor.loadingDescription': '正在准备编辑器…',
+ 'workflowActivityVNext.editor.openActivity': '打开活动',
+ 'workflowActivityVNext.editor.publish': '发布',
+ 'workflowActivityVNext.editor.publishBeforeRun':
+ '请先发布此工作流,再运行。',
+ 'workflowActivityVNext.editor.publishLatestBeforeRun':
+ '请先保存并发布最新更改,再运行。',
+ 'workflowActivityVNext.editor.publicationStatusAria': '工作流发布状态',
+ 'workflowActivityVNext.editor.publishUnavailable': '此工作流暂时无法发布。',
+ 'workflowActivityVNext.editor.publishedRunDrawer': '运行已发布工作流',
+ 'workflowActivityVNext.editor.publishedTargetUnavailable':
+ '请先发布此工作流,再运行。',
+ 'workflowActivityVNext.editor.waitForEditorBeforeRun':
+ '请等待工作流更新完成。',
+ 'workflowActivityVNext.editor.waitForPublishedRun':
+ '请等待已发布版本可用。',
+ 'workflowActivityVNext.publish.addExecutableStep':
+ '发布前请至少添加一个可执行步骤。',
+ 'workflowActivityVNext.publish.applyNodeChanges':
+ '发布前请应用或放弃节点配置更改。',
+ 'workflowActivityVNext.publish.blocked': '发布受阻 · {count} 个问题',
+ 'workflowActivityVNext.publish.blockedOne': '发布受阻 · 1 个问题',
+ 'workflowActivityVNext.publish.failed': '无法确认发布状态',
+ 'workflowActivityVNext.publish.failedDescription':
+ '请处理错误或再次尝试发布。',
+ 'workflowActivityVNext.publish.forbiddenDescription':
+ '你无权查看此发布状态。',
+ 'workflowActivityVNext.publish.published': '已发布',
+ 'workflowActivityVNext.publish.publishedServiceId': '已发布服务 ID',
+ 'workflowActivityVNext.publish.publishing': '正在发布',
+ 'workflowActivityVNext.publish.revisionId': '版本 ID',
+ 'workflowActivityVNext.publish.readinessIssues': '发布就绪问题',
+ 'workflowActivityVNext.publish.success': '工作流已发布',
+ 'workflowActivityVNext.publish.submissionFailed': '无法提交工作流发布',
+ 'workflowActivityVNext.publish.submissionFailedDescription':
+ '请查看错误后重试发布。',
+ 'workflowActivityVNext.publish.validationRejected':
+ '工作流尚未满足发布条件',
+ 'workflowActivityVNext.publish.validationRejectedDescription':
+ '请修正下方工作流配置,然后重新发布。',
+ 'workflowActivityVNext.publish.resolvePublication':
+ '再次发布前请先处理当前发布状态。',
+ 'workflowActivityVNext.publish.saveBeforePublishing':
+ '请先保存此工作流,再发布。',
+ 'workflowActivityVNext.publish.saveChangesBeforePublishing':
+ '请先保存更改,再发布。',
+ 'workflowActivityVNext.publish.waitForPublication':
+ '请等待当前发布流程完成。',
+ 'workflowActivityVNext.publish.waitForEditorUpdate':
+ '请等待工作流步骤更新完成。',
+ 'workflowActivityVNext.publish.waitForSave': '请等待工作流验证和保存完成。',
+ 'workflowActivityVNext.publish.waitForSavedDraft':
+ '请等待已保存草稿变为可读取状态。',
+ 'workflowActivityVNext.publish.unauthorizedDescription':
+ '请重新登录后检查此发布状态。',
+ 'workflowActivityVNext.editor.checkLatestStatus': '检查最新状态',
+ 'workflowActivityVNext.editor.currentStep': '当前步骤',
+ 'workflowActivityVNext.editor.expandRunConsole': '展开工作流日志',
+ 'workflowActivityVNext.editor.failureSummary': '失败摘要',
+ 'workflowActivityVNext.editor.fullDetailsNotice':
+ '打开运行详情可查看完整时间线、诊断信息和恢复操作。',
+ 'workflowActivityVNext.editor.openRunDetails': '打开运行详情',
+ 'workflowActivityVNext.editor.outputSummary': '输出摘要',
+ 'workflowActivityVNext.editor.publishedRunPanel.inputRequired':
+ '已发布工作流可以在没有手动输入时启动。',
+ 'workflowActivityVNext.editor.publishedRunPanel.removeEmptyFile':
+ '启动已发布运行前,请移除空文件 {name}。',
+ 'workflowActivityVNext.editor.resizePublishedRunPanel':
+ '调整已发布运行面板大小',
+ 'workflowActivityVNext.editor.resizeRunConsole': '调整工作流运行控制台大小',
+ 'workflowActivityVNext.editor.runAgain': '再次运行',
+ 'workflowActivityVNext.editor.runFailed': '运行失败',
+ 'workflowActivityVNext.editor.runInput': '输入',
+ 'workflowActivityVNext.editor.runInputExample': '例如:审核订单 42',
+ 'workflowActivityVNext.editor.runInputHelp':
+ '此工作流接受一个文本输入。例如:审核订单 42。',
+ 'workflowActivityVNext.editor.runInputRequired': '请输入内容或添加附件。',
+ 'workflowActivityVNext.editor.runInputRequiredTag': '必填',
+ 'workflowActivityVNext.editor.runPanel': '测试运行',
+ 'workflowActivityVNext.editor.runResult': '运行结果',
+ 'workflowActivityVNext.editor.runUnavailable': '请先添加至少一个有效步骤。',
+ 'workflowActivityVNext.editor.retryActivityObservation': '再次检查',
+ 'workflowActivityVNext.editor.saveDelayed': '保存时间比预期更长',
+ 'workflowActivityVNext.editor.saveFailed': '无法保存工作流',
+ 'workflowActivityVNext.editor.saveStatusAria': '工作流保存状态',
+ 'workflowActivityVNext.editor.saveStatusFailed': '保存失败',
+ 'workflowActivityVNext.editor.saveOpenFailed':
+ '工作流已保存,但暂时无法重新打开',
+ 'workflowActivityVNext.editor.savingProgress': '正在保存工作流…',
+ 'workflowActivityVNext.editor.save': '保存工作流',
+ 'workflowActivityVNext.editor.saveLeave': '保存并离开',
+ 'workflowActivityVNext.editor.saveSuccess': '工作流已保存',
+ 'workflowActivityVNext.editor.saved': '已保存',
+ 'workflowActivityVNext.editor.savedAt': '已保存于 {updatedAt}',
+ 'workflowActivityVNext.editor.stay': '留在此处',
+ 'workflowActivityVNext.editor.submitRun': '开始运行',
+ 'workflowActivityVNext.editor.submittedInput': '已提交输入',
+ 'workflowActivityVNext.editor.snapshotNotice':
+ '再次运行将使用完全相同的输入和工作流快照。',
+ 'workflowActivityVNext.editor.unavailable': '工作流不可用',
+ 'workflowActivityVNext.editor.unavailableDescription': '无法加载此工作流。',
+ 'workflowActivityVNext.editor.unavailableGuidance': '请重试打开此工作流。',
+ 'workflowActivityVNext.editor.unsaved': '未保存',
+ 'workflowActivityVNext.editor.unsavedChanges': '有未保存的更改',
+ 'workflowActivityVNext.editor.updatingNode': '正在更新节点…',
+ 'workflowActivityVNext.editor.validating': '正在验证工作流…',
+ 'workflowActivityVNext.editor.unsavedDescription':
+ '保存更改、放弃更改,或留在编辑器。',
+ 'workflowActivityVNext.editor.unsavedTitle': '工作流有未保存更改',
+ 'workflowActivityVNext.editor.untitled': '未命名工作流',
+ 'workflowActivityVNext.editor.viewRun': '查看运行',
+ 'workflowActivityVNext.editor.yaml': 'YAML',
+ 'workflowActivityVNext.editor.yamlReadFailed': '无法读取工作流 YAML。',
+ 'workflowActivityVNext.nodeInspector.advanced': '高级 JSON',
+ 'workflowActivityVNext.nodeInspector.advancedDescription':
+ '以 JSON 编辑此步骤的运行参数。',
+ 'workflowActivityVNext.nodeInspector.applyChanges': '应用步骤',
+ 'workflowActivityVNext.nodeInspector.applyBeforeSave':
+ '请先应用此步骤,再保存工作流。',
+ 'workflowActivityVNext.nodeInspector.applyFailed': '无法应用配置',
+ 'workflowActivityVNext.nodeInspector.applyJson': '应用 JSON',
+ 'workflowActivityVNext.nodeInspector.branches': '分支',
+ 'workflowActivityVNext.nodeInspector.branchesUnavailable': '分支不可用',
+ 'workflowActivityVNext.nodeInspector.close': '关闭配置',
+ 'workflowActivityVNext.nodeInspector.closeAria': '关闭节点配置',
+ 'workflowActivityVNext.nodeInspector.configuration': '设置',
+ 'workflowActivityVNext.nodeInspector.configurationDescription':
+ '设置此步骤在工作流运行前所需的内容。',
+ 'workflowActivityVNext.nodeInspector.discard': '放弃更改',
+ 'workflowActivityVNext.nodeInspector.discardDescription':
+ '此步骤中尚未应用的更改将丢失。',
+ 'workflowActivityVNext.nodeInspector.discardTitle': '放弃节点更改?',
+ 'workflowActivityVNext.nodeInspector.errorDetails': '错误详情',
+ 'workflowActivityVNext.nodeInspector.fieldExample': '示例:{value}',
+ 'workflowActivityVNext.nodeInspector.fieldOptional': '选填',
+ 'workflowActivityVNext.nodeInspector.fieldRequired': '必填',
+ 'workflowActivityVNext.nodeInspector.nextStep': '下一步骤',
+ 'workflowActivityVNext.nodeInspector.noBranches': '没有分支',
+ 'workflowActivityVNext.nodeInspector.noSettings': '此步骤无需设置。',
+ 'workflowActivityVNext.nodeInspector.notSet': '未设置',
+ 'workflowActivityVNext.nodeInspector.purpose.assign':
+ '保存一个值,供工作流后续步骤使用。',
+ 'workflowActivityVNext.nodeInspector.purpose.cache':
+ '遇到相同缓存键时复用之前的结果。',
+ 'workflowActivityVNext.nodeInspector.purpose.checkpoint':
+ '在工作流中记录一个具名恢复点。',
+ 'workflowActivityVNext.nodeInspector.purpose.conditional':
+ '仅在配置的条件为真时继续。',
+ 'workflowActivityVNext.nodeInspector.purpose.connectorCall':
+ '调用已配置连接器提供的操作。',
+ 'workflowActivityVNext.nodeInspector.purpose.default':
+ '设置此步骤在工作流运行时的行为。',
+ 'workflowActivityVNext.nodeInspector.purpose.delay':
+ '暂停工作流一段时间后再继续下一步。',
+ 'workflowActivityVNext.nodeInspector.purpose.dynamicWorkflow':
+ '根据生成的 YAML 创建并运行工作流步骤。',
+ 'workflowActivityVNext.nodeInspector.purpose.emit':
+ '向其他工作流或系统监听器发布事件。',
+ 'workflowActivityVNext.nodeInspector.purpose.evaluate':
+ '按照明确标准为当前结果评分。',
+ 'workflowActivityVNext.nodeInspector.purpose.foreach':
+ '对每个输入项运行相同的子步骤。',
+ 'workflowActivityVNext.nodeInspector.purpose.guard':
+ '在工作流继续前检查输入。',
+ 'workflowActivityVNext.nodeInspector.purpose.humanApproval':
+ '暂停并等待人工批准或拒绝下一操作。',
+ 'workflowActivityVNext.nodeInspector.purpose.humanInput':
+ '暂停并向用户收集信息。',
+ 'workflowActivityVNext.nodeInspector.purpose.llmCall':
+ '将指令和工作流输入发送给 AI 模型。',
+ 'workflowActivityVNext.nodeInspector.purpose.mapReduce':
+ '分别处理输入分块,再合并所有结果。',
+ 'workflowActivityVNext.nodeInspector.purpose.parallel':
+ '同时运行多个执行者并合并结果。',
+ 'workflowActivityVNext.nodeInspector.purpose.race':
+ '同时运行多个执行者,并采用最先返回的结果。',
+ 'workflowActivityVNext.nodeInspector.purpose.reflect':
+ '在限定轮次内检查并改进结果。',
+ 'workflowActivityVNext.nodeInspector.purpose.retrieveFacts':
+ '查找后续工作流步骤需要的相关事实。',
+ 'workflowActivityVNext.nodeInspector.purpose.switch':
+ '根据当前值选择下一分支。',
+ 'workflowActivityVNext.nodeInspector.purpose.toolCall':
+ '使用你提供的输入运行一个已注册工具。',
+ 'workflowActivityVNext.nodeInspector.purpose.transform':
+ '转换当前值,供下一步骤使用。',
+ 'workflowActivityVNext.nodeInspector.purpose.vote':
+ '从可用的执行结果中选出一个结果。',
+ 'workflowActivityVNext.nodeInspector.purpose.waitSignal':
+ '暂停并等待指定信号或超时。',
+ 'workflowActivityVNext.nodeInspector.purpose.while':
+ '在条件为真时重复执行子步骤。',
+ 'workflowActivityVNext.nodeInspector.purpose.workflowCall':
+ '运行另一个工作流并在此使用其结果。',
+ 'workflowActivityVNext.nodeInspector.purpose.workflowYamlValidate':
+ '使用生成的工作流 YAML 前先检查其有效性。',
+ 'workflowActivityVNext.nodeInspector.rawConfigurationAria': '原始配置',
+ 'workflowActivityVNext.nodeInspector.rawConfigurationError':
+ '配置必须为 JSON 对象。',
+ 'workflowActivityVNext.nodeInspector.sectionAria': '配置 {name}',
+ 'workflowActivityVNext.nodeInspector.stepDetails': '技术详情',
+ 'workflowActivityVNext.nodeInspector.stepId': '步骤 ID',
+ 'workflowActivityVNext.nodeInspector.targetRole': '目标角色',
+ 'workflowActivityVNext.nodeInspector.title': '配置 {name}',
+ 'workflowActivityVNext.nodeInspector.type': '类型',
+ 'workflowActivityVNext.failure.accessDenied': '你无权使用此服务或模型。',
+ 'workflowActivityVNext.failure.accessDeniedGuidance':
+ '请申请权限,或选择你的账户可用的服务和模型。',
+ 'workflowActivityVNext.failure.cancelled': '运行已取消。',
+ 'workflowActivityVNext.failure.cancelledGuidance':
+ '此次运行已停止,但未被报告为系统故障。',
+ 'workflowActivityVNext.failure.chooseAllowedService': '选择可用服务',
+ 'workflowActivityVNext.failure.copyTrackingId': '复制跟踪 ID',
+ 'workflowActivityVNext.failure.internal': '请求无法完成。',
+ 'workflowActivityVNext.failure.internalGuidance':
+ '请重试;如果仍然失败,请在有跟踪 ID 时联系支持。',
+ 'workflowActivityVNext.failure.invalidInput': '输入或工作流定义需要处理。',
+ 'workflowActivityVNext.failure.invalidInputGuidance':
+ '请检查受影响的输入或工作流步骤后再试。',
+ 'workflowActivityVNext.failure.rateLimited': '请求受到速率限制。',
+ 'workflowActivityVNext.failure.rateLimitedGuidance':
+ '请等待配额窗口重置后再试。',
+ 'workflowActivityVNext.failure.reloadLatest': '加载最新状态',
+ 'workflowActivityVNext.failure.resourceMissing': '此次运行已不可用。',
+ 'workflowActivityVNext.failure.resourceMissingGuidance':
+ '请刷新活动记录或返回运行列表。',
+ 'workflowActivityVNext.failure.retryAfter': '{seconds} 秒后重试。',
+ 'workflowActivityVNext.failure.reviewInput': '检查输入',
+ 'workflowActivityVNext.failure.sessionExpired': '登录会话已过期。',
+ 'workflowActivityVNext.failure.sessionExpiredGuidance':
+ '请重新登录后继续。',
+ 'workflowActivityVNext.failure.signInAgain': '重新登录',
+ 'workflowActivityVNext.failure.stateConflict': '加载后此次运行已发生变化。',
+ 'workflowActivityVNext.failure.stateConflictGuidance':
+ '请加载最新状态后再继续。',
+ 'workflowActivityVNext.failure.timeoutOrOffline': '请求超时或连接中断。',
+ 'workflowActivityVNext.failure.timeoutOrOfflineGuidance':
+ '请检查网络连接后再试。',
+ 'workflowActivityVNext.failure.upstreamUnavailable': '所选服务暂时不可用。',
+ 'workflowActivityVNext.failure.upstreamUnavailableGuidance':
+ '请检查服务状态或稍后重试。',
+ 'workflowActivityVNext.nav.activity': '活动',
+ 'workflowActivityVNext.nav.aria': '工作流工作台',
+ 'workflowActivityVNext.nav.openMenu': '打开导航',
+ 'workflowActivityVNext.nav.settings': '设置',
+ 'workflowActivityVNext.nav.workflows': '工作流',
+ 'workflowActivityVNext.new.back': '返回工作流列表',
+ 'workflowActivityVNext.new.changeMethod': '更换创建方式',
+ 'workflowActivityVNext.new.chooserAria': '工作流创建方式',
+ 'workflowActivityVNext.new.createBlank': '创建并打开',
+ 'workflowActivityVNext.new.createFailed': '无法创建工作流',
+ 'workflowActivityVNext.new.createUnconfirmed':
+ '无法确认工作流是否已创建,请先返回工作流列表检查,再决定是否重试。',
+ 'workflowActivityVNext.new.creatingDescription': '通常只需要片刻时间。',
+ 'workflowActivityVNext.new.description': '选择一种开始方式。',
+ 'workflowActivityVNext.new.defaultWorkspace': '默认工作区',
+ 'workflowActivityVNext.new.directory': '保存到',
+ 'workflowActivityVNext.new.generate': '生成并打开',
+ 'workflowActivityVNext.new.goal': '描述你希望这个工作流完成什么',
+ 'workflowActivityVNext.new.mode.blank': '从空白开始',
+ 'workflowActivityVNext.new.mode.blank.description':
+ '从空白画布开始,自行添加步骤。',
+ 'workflowActivityVNext.new.mode.describe': '描述需求',
+ 'workflowActivityVNext.new.mode.describe.description':
+ '把目标转成可检查、可编辑的工作流。',
+ 'workflowActivityVNext.new.mode.import': '导入 YAML',
+ 'workflowActivityVNext.new.mode.import.description':
+ '导入已有的 YAML 工作流。',
+ 'workflowActivityVNext.new.mode.template': '使用模板',
+ 'workflowActivityVNext.new.mode.template.description':
+ '从现成工作流开始并按需修改。',
+ 'workflowActivityVNext.new.name': '工作流名称',
+ 'workflowActivityVNext.new.noDirectories':
+ '没有可用的保存位置,请稍后重试或联系管理员。',
+ 'workflowActivityVNext.new.noDirectoriesDescription':
+ '工作流需要明确的保存位置来确定归属;恢复访问权限期间,你可以先在此准备输入。',
+ 'workflowActivityVNext.new.observationFailed': '无法打开工作流',
+ 'workflowActivityVNext.new.observing': '正在创建工作流…',
+ 'workflowActivityVNext.new.projectionDelayed': '所需时间比预期更长',
+ 'workflowActivityVNext.new.retryDescription':
+ '你的内容已保留,请重试以完成打开工作流。',
+ 'workflowActivityVNext.new.retryObservation': '重试',
+ 'workflowActivityVNext.new.reviewAccess': '检查访问权限',
+ 'workflowActivityVNext.new.saveTargetRequired':
+ '请先选择可用的保存位置,再创建工作流。',
+ 'workflowActivityVNext.new.templateBrowser.title': '从模板开始',
+ 'workflowActivityVNext.new.templateBrowser.description':
+ '浏览公开模板、查看详情,或直接创建草稿。',
+ 'workflowActivityVNext.new.templateBrowser.changeMethod': '更换创建方式',
+ 'workflowActivityVNext.new.templateBrowser.search': '搜索模板',
+ 'workflowActivityVNext.new.templateBrowser.sort': '模板排序',
+ 'workflowActivityVNext.new.templateBrowser.sortBy': '排序方式',
+ 'workflowActivityVNext.new.templateBrowser.sort.recent':
+ '更新时间:最新优先',
+ 'workflowActivityVNext.new.templateBrowser.sort.nameAsc': '名称:A 到 Z',
+ 'workflowActivityVNext.new.templateBrowser.sort.nameDesc': '名称:Z 到 A',
+ 'workflowActivityVNext.new.templateBrowser.sort.oldest':
+ '更新时间:最早优先',
+ 'workflowActivityVNext.new.templateBrowser.loading': '正在加载模板…',
+ 'workflowActivityVNext.new.templateBrowser.loadFailed': '无法加载模板',
+ 'workflowActivityVNext.new.templateBrowser.unavailable':
+ '当前环境暂不提供模板。',
+ 'workflowActivityVNext.new.templateBrowser.unavailableDescription':
+ '当前环境尚未部署模板目录服务。',
+ 'workflowActivityVNext.new.templateBrowser.retry': '重试',
+ 'workflowActivityVNext.new.templateBrowser.signIn':
+ '登录后才能浏览公开工作流模板。',
+ 'workflowActivityVNext.new.templateBrowser.noSearchResults':
+ '没有匹配搜索条件的模板。',
+ 'workflowActivityVNext.new.templateBrowser.noTemplates':
+ '暂时没有可用的公开工作流模板。',
+ 'workflowActivityVNext.new.templateBrowser.view': '查看',
+ 'workflowActivityVNext.new.templateBrowser.viewNamed': '查看 {name}',
+ 'workflowActivityVNext.new.templateBrowser.use': '使用模板',
+ 'workflowActivityVNext.new.templateBrowser.useNamed': '使用模板 {name}',
+ 'workflowActivityVNext.new.templateBrowser.catalogue': '工作流模板目录',
+ 'workflowActivityVNext.new.templateBrowser.template': '模板',
+ 'workflowActivityVNext.new.templateBrowser.connection': '连接',
+ 'workflowActivityVNext.new.templateBrowser.does': '执行',
+ 'workflowActivityVNext.new.templateBrowser.updatedColumn': '更新时间',
+ 'workflowActivityVNext.new.templateBrowser.actions': '操作',
+ 'workflowActivityVNext.new.templateBrowser.none': '无',
+ 'workflowActivityVNext.new.templateBrowser.llmProvider': 'LLM Provider',
+ 'workflowActivityVNext.new.templateBrowser.runsSteps':
+ '运行 {count} 个{unit}',
+ 'workflowActivityVNext.new.templateBrowser.stepsTooltipTitle':
+ '工作流步骤({count})',
+ 'workflowActivityVNext.new.templateBrowser.stepsTooltipLoading':
+ '正在加载步骤详情…',
+ 'workflowActivityVNext.new.templateBrowser.stepsTooltipUnavailable':
+ '暂时无法加载步骤详情,请点击“查看”查看完整工作流。',
+ 'workflowActivityVNext.new.templateBrowser.stepsTooltipEmpty':
+ '该模板未提供可展示的工作流步骤。',
+ 'workflowActivityVNext.new.templateBrowser.step': '步骤',
+ 'workflowActivityVNext.new.templateBrowser.steps': '步骤',
+ 'workflowActivityVNext.new.templateBrowser.fallbackDescription':
+ '适用于当前工作区的现成工作流。',
+ 'workflowActivityVNext.new.templateBrowser.contract': '{name} 契约',
+ 'workflowActivityVNext.new.templateBrowser.detailLoading':
+ '正在加载模板详情…',
+ 'workflowActivityVNext.new.templateBrowser.detailFailed':
+ '无法加载模板详情',
+ 'workflowActivityVNext.new.templateBrowser.noLongerAvailable':
+ '该模板已不可用。',
+ 'workflowActivityVNext.new.templateBrowser.preview': '工作流预览',
+ 'workflowActivityVNext.new.templateBrowser.previewDescription':
+ '共 {count} 个{unit}及其执行路径。',
+ 'workflowActivityVNext.new.templateBrowser.noPreviewSteps':
+ '该模板未提供可预览的工作流步骤。',
+ 'workflowActivityVNext.new.templateBrowser.source':
+ '来源:公开工作流模板 · 版本 {version}',
+ 'workflowActivityVNext.new.templateBrowser.cancel': '取消',
+ 'workflowActivityVNext.new.templateBrowser.useThis': '使用此模板',
+ 'workflowActivityVNext.new.templateBrowser.templateOutOfDate': '模板已更新',
+ 'workflowActivityVNext.new.templateBrowser.templateOutOfDateDescription':
+ '你查看模板期间它发生了变化,请刷新目录后重试。',
+ 'workflowActivityVNext.new.templateBrowser.refreshCatalog': '刷新目录',
+ 'workflowActivityVNext.new.templateBrowser.creationFailed': '模板创建失败',
+ 'workflowActivityVNext.new.templateBrowser.couldNotUse':
+ '无法使用工作流模板',
+ 'workflowActivityVNext.new.templateBrowser.creating': '正在创建工作流…',
+ 'workflowActivityVNext.new.templateBrowser.projectionDelayed':
+ '所需时间比预期更长',
+ 'workflowActivityVNext.new.templateBrowser.observationFailed':
+ '无法打开工作流',
+ 'workflowActivityVNext.new.templateBrowser.safeToRetry':
+ '你的内容已保留,请重试以完成打开工作流。',
+ 'workflowActivityVNext.new.templateBrowser.creatingDescription':
+ '通常只需要片刻时间。',
+ 'workflowActivityVNext.new.templateBrowser.tryAgain': '重试',
+ 'workflowActivityVNext.new.title': '新建工作流',
+ 'workflowActivityVNext.new.validateCreate': '导入并打开',
+ 'workflowActivityVNext.new.workspaceUnavailable': '保存位置不可用',
+ 'workflowActivityVNext.new.workspaceUnavailableDescription':
+ '现在可以先选择创建方式;恢复访问权限期间,你的输入会保留在此页面。',
+ 'workflowActivityVNext.new.yaml': '工作流 YAML',
+ 'workflowActivityVNext.run.backAria': '返回活动列表',
+ 'workflowActivityVNext.run.commandId': '请求 ID',
+ 'workflowActivityVNext.run.code': '代码',
+ 'workflowActivityVNext.run.completedSteps': '已完成步骤',
+ 'workflowActivityVNext.run.completionTokens': '完成 Token',
+ 'workflowActivityVNext.run.confirmRetry': '确认重试',
+ 'workflowActivityVNext.run.confirmRunAgain': '确认再次运行',
+ 'workflowActivityVNext.run.confirmTitle': '确认新运行',
+ 'workflowActivityVNext.run.costWarning':
+ '此操作可能再次产生模型或工具费用。',
+ 'workflowActivityVNext.run.correlationId': '跟踪 ID',
+ 'workflowActivityVNext.run.cost': '费用',
+ 'workflowActivityVNext.run.diagnostics': '诊断',
+ 'workflowActivityVNext.run.error': '最终错误',
+ 'workflowActivityVNext.run.failedSummary': '此次运行未完成。',
+ 'workflowActivityVNext.run.description': '查看此次运行的结果、步骤和历史。',
+ 'workflowActivityVNext.run.title': '运行详情',
+ 'workflowActivityVNext.run.forkAccepted': '新运行请求已接受',
+ 'workflowActivityVNext.run.forkAcceptedDescription':
+ '请求已被接受。打开新运行可继续查看进度。',
+ 'workflowActivityVNext.run.fromStep': '来自步骤 {step}',
+ 'workflowActivityVNext.run.graph': '图',
+ 'workflowActivityVNext.run.graphEmpty': '暂时没有可用的运行图。',
+ 'workflowActivityVNext.run.graphEmptyDescription':
+ '此次运行尚未物化图视图。',
+ 'workflowActivityVNext.run.graphLoading': '正在加载运行图…',
+ 'workflowActivityVNext.run.graphSummary': '{nodes} 个节点 · {edges} 条边',
+ 'workflowActivityVNext.run.graphUnavailable': '运行图不可用',
+ 'workflowActivityVNext.run.historyLoading': '正在加载运行历史…',
+ 'workflowActivityVNext.run.historyUnavailable': '运行历史不可用。',
+ 'workflowActivityVNext.run.input': '输入',
+ 'workflowActivityVNext.run.loading': '正在加载运行…',
+ 'workflowActivityVNext.run.loadingDescription': '正在加载运行详情…',
+ 'workflowActivityVNext.run.logs': '日志',
+ 'workflowActivityVNext.run.newActorId': '运行地址',
+ 'workflowActivityVNext.run.noHistory': '暂无已发布运行。',
+ 'workflowActivityVNext.run.noHistoryDescription':
+ '此工作流在当前历史上下文中没有其他运行。',
+ 'workflowActivityVNext.run.noSteps': '暂时没有可用步骤。',
+ 'workflowActivityVNext.run.noDiagnostics': '暂无诊断信息。',
+ 'workflowActivityVNext.run.noTimeline': '尚无可见的时间线事件。',
+ 'workflowActivityVNext.run.output': '最终输出',
+ 'workflowActivityVNext.run.message': '消息',
+ 'workflowActivityVNext.run.openNewRun': '打开新运行',
+ 'workflowActivityVNext.run.originalRun': '原始运行',
+ 'workflowActivityVNext.run.parentRun': '父运行',
+ 'workflowActivityVNext.run.parentStep': '父步骤',
+ 'workflowActivityVNext.run.priorOutputs': '之前的步骤输出',
+ 'workflowActivityVNext.run.priorOutputsNotReused':
+ '不会复用之前的步骤输出。',
+ 'workflowActivityVNext.run.priorOutputsReused': '将复用之前的步骤输出。',
+ 'workflowActivityVNext.run.promptTokens': '输入 Token',
+ 'workflowActivityVNext.run.requestedSteps': '已请求步骤',
+ 'workflowActivityVNext.run.requestParameters': '请求参数',
+ 'workflowActivityVNext.run.refreshFailed': '部分运行详情刷新失败',
+ 'workflowActivityVNext.run.refreshing': '正在刷新…',
+ 'workflowActivityVNext.run.refreshingDetail': '正在刷新运行详情…',
+ 'workflowActivityVNext.run.refreshSucceeded': '运行详情已刷新',
+ 'workflowActivityVNext.run.relatedRuns': '关联运行',
+ 'workflowActivityVNext.run.lineageUnavailable': '关联运行历史不可用。',
+ 'workflowActivityVNext.run.detailFallbackDescription':
+ '运行详情请求不可用,但仍可查看已选中的 Activity 记录。',
+ 'workflowActivityVNext.run.detailUnavailableTitle': '运行详情暂时不可用。',
+ 'workflowActivityVNext.run.detailUnavailableDescription':
+ '已选中的运行会继续在所属工作流历史中高亮。请重试加载它的不可变详情、图、时间线和输出。',
+ 'workflowActivityVNext.run.noRelatedRuns': '没有关联运行。',
+ 'workflowActivityVNext.run.openRunAria': '打开 {runId}',
+ 'workflowActivityVNext.run.retryHistory': '重试历史',
+ 'workflowActivityVNext.run.retry': '重试失败步骤',
+ 'workflowActivityVNext.run.retryUnavailable':
+ '存在一个失败步骤时才能重试。',
+ 'workflowActivityVNext.run.runAgain': '再次运行',
+ 'workflowActivityVNext.run.runAgainUnavailable':
+ '此次运行暂时无法重新开始。',
+ 'workflowActivityVNext.run.rootRun': '根运行',
+ 'workflowActivityVNext.run.sourceRun': '来源运行',
+ 'workflowActivityVNext.run.sourceImmutable':
+ '这会创建一个独立的新运行,来源运行不会被更改。',
+ 'workflowActivityVNext.run.subWorkflows': '子工作流',
+ 'workflowActivityVNext.run.childRuns': '子运行',
+ 'workflowActivityVNext.run.details': '详情',
+ 'workflowActivityVNext.run.attempt': '尝试次数',
+ 'workflowActivityVNext.run.depth': '层级深度',
+ 'workflowActivityVNext.run.definitionRevision': '定义修订 ID',
+ 'workflowActivityVNext.run.definitionVersion': '定义版本',
+ 'workflowActivityVNext.run.editWorkflow': '编辑工作流',
+ 'workflowActivityVNext.run.reviewInput': '检查输入',
+ 'workflowActivityVNext.run.reviewSettings': '检查设置',
+ 'workflowActivityVNext.run.recommendedNextSteps': '建议:{actions}',
+ 'workflowActivityVNext.run.roleReplies': '角色回复数',
+ 'workflowActivityVNext.run.severity': '严重级别',
+ 'workflowActivityVNext.run.startFailed': '无法开始新运行',
+ 'workflowActivityVNext.run.startingStep': '起始步骤',
+ 'workflowActivityVNext.run.statusUrl': '状态 URL',
+ 'workflowActivityVNext.run.statisticsUsage': '统计与用量',
+ 'workflowActivityVNext.run.step': '步骤',
+ 'workflowActivityVNext.run.stepFailedSummary': '此步骤未完成。',
+ 'workflowActivityVNext.run.steps': '步骤',
+ 'workflowActivityVNext.run.timeline': '时间线',
+ 'workflowActivityVNext.run.totalSteps': '步骤总数',
+ 'workflowActivityVNext.run.totalTokens': 'Token 总数',
+ 'workflowActivityVNext.run.type': '类型',
+ 'workflowActivityVNext.run.unavailable': '运行记录不可用',
+ 'workflowActivityVNext.run.unavailableDescription': '无法加载此次运行。',
+ 'workflowActivityVNext.run.unavailableGuidance': '请重试加载此次运行。',
+ 'workflowActivityVNext.settings.account': '账户',
+ 'workflowActivityVNext.settings.accountDescription':
+ '你的个人资料与访问权限。',
+ 'workflowActivityVNext.settings.accountLoading': '正在加载账户…',
+ 'workflowActivityVNext.settings.accountUnavailable': '账户信息不可用',
+ 'workflowActivityVNext.settings.advanced': '高级',
+ 'workflowActivityVNext.settings.advancedDescription':
+ '启动工作流时使用的只读执行信息。',
+ 'workflowActivityVNext.settings.ai': 'AI 默认设置',
+ 'workflowActivityVNext.settings.aiDescription':
+ '选择新 AI 会话默认使用的服务与模型。',
+ 'workflowActivityVNext.settings.authenticated': '已登录',
+ 'workflowActivityVNext.settings.catalogUnavailable':
+ '已保存的选择没有改变,请重试加载可用服务。',
+ 'workflowActivityVNext.settings.catalogUnavailableTitle': '服务不可用',
+ 'workflowActivityVNext.settings.defaultModel': '默认模型',
+ 'workflowActivityVNext.settings.defaultModelHelp':
+ '留空时使用服务默认模型。',
+ 'workflowActivityVNext.settings.delayed': '更改显示所需时间比预期更长',
+ 'workflowActivityVNext.settings.description': '个人默认设置与访问权限。',
+ 'workflowActivityVNext.settings.discard': '恢复已保存设置',
+ 'workflowActivityVNext.settings.discardLeave': '放弃并离开',
+ 'workflowActivityVNext.settings.email': '邮箱',
+ 'workflowActivityVNext.settings.emailVerification': '邮箱验证',
+ 'workflowActivityVNext.settings.executionTarget': '执行目标',
+ 'workflowActivityVNext.settings.expires': '过期时间',
+ 'workflowActivityVNext.settings.failed': '无法保存更改',
+ 'workflowActivityVNext.settings.groups': '群组',
+ 'workflowActivityVNext.settings.llmLoading': '正在加载 AI 默认设置…',
+ 'workflowActivityVNext.settings.llmUnavailable': 'AI 默认设置不可用',
+ 'workflowActivityVNext.settings.name': '名称',
+ 'workflowActivityVNext.settings.manageServiceAccess': '管理服务访问权限',
+ 'workflowActivityVNext.settings.localRuntimeLabel': '本地运行时',
+ 'workflowActivityVNext.settings.notLoaded': '未加载',
+ 'workflowActivityVNext.settings.notAccepted': '设置更新未被接受。',
+ 'workflowActivityVNext.settings.observed': '更改已保存',
+ 'workflowActivityVNext.settings.providerDefault': 'Provider 默认值',
+ 'workflowActivityVNext.settings.preferredService': '首选服务',
+ 'workflowActivityVNext.settings.preferredServiceHelp':
+ '选择新会话默认使用的服务。',
+ 'workflowActivityVNext.settings.provider': '登录方式',
+ 'workflowActivityVNext.settings.profile': '个人资料',
+ 'workflowActivityVNext.settings.profileUnavailable': '个人资料详情不可用。',
+ 'workflowActivityVNext.settings.remoteRuntimeLabel': '远端运行时',
+ 'workflowActivityVNext.settings.roles': '角色',
+ 'workflowActivityVNext.settings.runtimeLoading': '正在加载连接信息…',
+ 'workflowActivityVNext.settings.runtimeUnavailable': '工作流执行信息不可用',
+ 'workflowActivityVNext.settings.runtimeUrl': '运行时 URL',
+ 'workflowActivityVNext.settings.scope': 'Scope',
+ 'workflowActivityVNext.settings.serviceAccessFailed':
+ '无法启动服务访问权限检查,请重试。',
+ 'workflowActivityVNext.settings.sessionActive': '有效',
+ 'workflowActivityVNext.settings.sessionExpired': '已过期',
+ 'workflowActivityVNext.settings.sessionExpiringSoon': '即将过期',
+ 'workflowActivityVNext.settings.sessionInvalid': '无效',
+ 'workflowActivityVNext.settings.sessionState': '会话状态',
+ 'workflowActivityVNext.settings.sessionAccess': '会话与访问权限',
+ 'workflowActivityVNext.settings.signInAgain': '重新登录',
+ 'workflowActivityVNext.settings.signOut': '退出登录',
+ 'workflowActivityVNext.settings.retryGuidance': '请重试加载此部分。',
+ 'workflowActivityVNext.settings.save': '保存更改',
+ 'workflowActivityVNext.settings.saveLeave': '保存并离开',
+ 'workflowActivityVNext.settings.saveObserved': '设置已保存',
+ 'workflowActivityVNext.settings.savedModelUnavailable':
+ '已保存的模型当前不可用,保存的值不会被更改。',
+ 'workflowActivityVNext.settings.sectionsAria': '设置分类',
+ 'workflowActivityVNext.settings.serviceDefaultModel': '使用服务默认模型。',
+ 'workflowActivityVNext.settings.stay': '留在此页',
+ 'workflowActivityVNext.settings.systemDefault': '系统默认值',
+ 'workflowActivityVNext.settings.systemDefaultModel':
+ '使用系统选择的服务和模型。',
+ 'workflowActivityVNext.settings.title': '设置',
+ 'workflowActivityVNext.settings.unauthorized': '无访问权限',
+ 'workflowActivityVNext.settings.unauthorizedDescription':
+ '当前会话无权查看账户或能力详情。',
+ 'workflowActivityVNext.settings.unsaved': '未保存的更改',
+ 'workflowActivityVNext.settings.unsavedActionsAria': '未保存设置操作',
+ 'workflowActivityVNext.settings.unsavedDescription':
+ 'AI 默认设置尚未保存。',
+ 'workflowActivityVNext.settings.unsavedLeaveDescription':
+ '保存更改、放弃更改,或留在设置页面。',
+ 'workflowActivityVNext.settings.unsavedLeaveTitle':
+ 'AI 默认设置有未保存的更改',
+ 'workflowActivityVNext.settings.userId': '用户 ID',
+ 'workflowActivityVNext.settings.verified': '已验证',
+ 'workflowActivityVNext.settings.notVerified': '未验证',
+ 'workflowActivityVNext.settings.workflowExecution': '工作流执行',
+ 'workflowActivityVNext.schedule.actionAccepted': '计划任务操作已受理。',
+ 'workflowActivityVNext.schedule.backToSchedules': '返回计划任务列表',
+ 'workflowActivityVNext.schedule.change': '修改计划任务',
+ 'workflowActivityVNext.schedule.completedAt': '完成于 {date}',
+ 'workflowActivityVNext.schedule.detailLoadFailed': '无法加载计划任务',
+ 'workflowActivityVNext.schedule.detailLoading': '正在加载计划任务详情…',
+ 'workflowActivityVNext.schedule.detailTitle': '计划任务详情',
+ 'workflowActivityVNext.schedule.historyLoading': '正在加载历史记录…',
+ 'workflowActivityVNext.schedule.historyLoadFailed': '无法加载历史记录',
+ 'workflowActivityVNext.schedule.selectedContextAria':
+ '工作流 {workflowName} 中的计划任务 {scheduleName}',
+ 'workflowActivityVNext.schedule.scheduleContext': '计划任务:{name}',
+ 'workflowActivityVNext.schedule.workflowContext': '工作流:{name}',
+ 'workflowActivityVNext.schedule.overview': '概览',
+ 'workflowActivityVNext.schedule.history': '历史记录',
+ 'workflowActivityVNext.schedule.recentAttempts': '最近尝试',
+ 'workflowActivityVNext.schedule.historyDescription':
+ '计划任务尝试可能在创建工作流运行之前失败。',
+ 'workflowActivityVNext.schedule.viewRelatedRuns':
+ '在活动记录中查看相关运行',
+ 'workflowActivityVNext.schedule.scheduledTime': '计划时间',
+ 'workflowActivityVNext.schedule.source': '来源',
+ 'workflowActivityVNext.schedule.outcome': '计划任务结果',
+ 'workflowActivityVNext.schedule.completedTime': '完成时间',
+ 'workflowActivityVNext.schedule.technicalDetails': '技术详情',
+ 'workflowActivityVNext.schedule.noAttempts': '暂无尝试记录',
+ 'workflowActivityVNext.schedule.scheduledAttemptFailed':
+ '定时尝试未能启动工作流。',
+ 'workflowActivityVNext.schedule.manualAttemptFailed':
+ '手动尝试未能启动工作流。',
+ 'workflowActivityVNext.schedule.openRunAria':
+ '打开由 {date} 的计划任务尝试创建的运行记录',
+ 'workflowActivityVNext.schedule.runCreated': '已创建运行',
+ 'workflowActivityVNext.schedule.failedToStart': '启动失败',
+ 'workflowActivityVNext.schedule.attemptAccepted': '已接受',
+ 'workflowActivityVNext.schedule.enable': '启用',
+ 'workflowActivityVNext.schedule.failed': '失败',
+ 'workflowActivityVNext.schedule.failedCount': '{count} 次失败',
+ 'workflowActivityVNext.schedule.failedFires': '失败次数',
+ 'workflowActivityVNext.schedule.lastFire': '上次触发',
+ 'workflowActivityVNext.schedule.manual': '手动触发',
+ 'workflowActivityVNext.schedule.nextFireLabel': '下次触发',
+ 'workflowActivityVNext.schedule.noFires': '暂无尝试记录',
+ 'workflowActivityVNext.schedule.pause': '暂停',
+ 'workflowActivityVNext.schedule.recentFires': '最近触发记录',
+ 'workflowActivityVNext.schedule.runNow': '立即运行',
+ 'workflowActivityVNext.schedule.scheduled': '定时触发',
+ 'workflowActivityVNext.schedule.scheduledAt': '计划于 {date}',
+ 'workflowActivityVNext.schedule.totalCount': '共 {count} 次',
+ 'workflowActivityVNext.schedule.totalFires': '触发总数',
+ 'workflowActivityVNext.schedule.viewAria': '查看 {name}',
+ 'workflowActivityVNext.schedule.create': '创建计划任务',
+ 'workflowActivityVNext.schedule.createTitle': '创建计划任务',
+ 'workflowActivityVNext.schedule.created':
+ '计划任务请求已受理,稍后会出现在列表中。',
+ 'workflowActivityVNext.schedule.cron': 'Cron 表达式',
+ 'workflowActivityVNext.schedule.cronSummary': 'Cron:{cron}',
+ 'workflowActivityVNext.schedule.runs': '运行周期',
+ 'workflowActivityVNext.schedule.technicalFormat': '技术格式',
+ 'workflowActivityVNext.schedule.hourly': '每小时',
+ 'workflowActivityVNext.schedule.daily': '每天',
+ 'workflowActivityVNext.schedule.everyWeekdaySummary': '每个工作日 {time}',
+ 'workflowActivityVNext.schedule.everyDaySummary': '每天 {time}',
+ 'workflowActivityVNext.schedule.customRecurrence': '自定义计划',
+ 'workflowActivityVNext.schedule.deleteAccepted': '计划任务删除请求已受理。',
+ 'workflowActivityVNext.schedule.deleteAria': '删除 {name}',
+ 'workflowActivityVNext.schedule.disableAria': '停用 {name}',
+ 'workflowActivityVNext.schedule.disabled': '已停用',
+ 'workflowActivityVNext.schedule.paused': '已暂停',
+ 'workflowActivityVNext.schedule.more': '更多',
+ 'workflowActivityVNext.schedule.moreActionsAria': '更多计划任务操作',
+ 'workflowActivityVNext.schedule.editAction': '编辑计划任务',
+ 'workflowActivityVNext.schedule.deleteConfirmTitle': '删除 {name}?',
+ 'workflowActivityVNext.schedule.deleteConfirmDescription':
+ '{name} 将停止按计划运行。',
+ 'workflowActivityVNext.schedule.deleteAction': '删除计划任务',
+ 'workflowActivityVNext.schedule.keepSchedule': '保留计划任务',
+ 'workflowActivityVNext.schedule.editAria': '编辑 {name}',
+ 'workflowActivityVNext.schedule.editTitle': '编辑计划任务',
+ 'workflowActivityVNext.schedule.empty': '暂无计划任务',
+ 'workflowActivityVNext.schedule.emptyDescription':
+ '为此已发布工作流创建一个周期性计划任务。',
+ 'workflowActivityVNext.schedule.enableAria': '启用 {name}',
+ 'workflowActivityVNext.schedule.enabled': '已启用',
+ 'workflowActivityVNext.schedule.howOften': '频率',
+ 'workflowActivityVNext.schedule.loadFailed': '无法加载计划任务',
+ 'workflowActivityVNext.schedule.loading': '正在加载计划任务…',
+ 'workflowActivityVNext.schedule.name': '名称',
+ 'workflowActivityVNext.schedule.new': '新建计划任务',
+ 'workflowActivityVNext.schedule.noPrompt': '无运行输入',
+ 'workflowActivityVNext.schedule.nextFire': '下次 {date}',
+ 'workflowActivityVNext.schedule.noUpcomingAttempt': '暂无后续尝试',
+ 'workflowActivityVNext.schedule.nextScheduled': '下次计划时间',
+ 'workflowActivityVNext.schedule.lastAttempt': '上次尝试',
+ 'workflowActivityVNext.schedule.totalAttempts': '尝试总数',
+ 'workflowActivityVNext.schedule.failedAttempts': '失败尝试',
+ 'workflowActivityVNext.schedule.advancedDetails': '高级详情',
+ 'workflowActivityVNext.schedule.nextFiveFires': '接下来的五次触发时间',
+ 'workflowActivityVNext.schedule.open': '计划任务',
+ 'workflowActivityVNext.schedule.openAria': '管理 {name} 的计划任务',
+ 'workflowActivityVNext.schedule.preview': '预览下次运行',
+ 'workflowActivityVNext.schedule.previewTitle': '接下来的计划运行',
+ 'workflowActivityVNext.schedule.previewing': '正在预览计划任务…',
+ 'workflowActivityVNext.schedule.published': '已发布',
+ 'workflowActivityVNext.schedule.prompt': '运行输入(可选)',
+ 'workflowActivityVNext.schedule.promptReview': '运行输入',
+ 'workflowActivityVNext.schedule.publishBeforeOpen':
+ '请先发布此工作流,再管理计划任务。',
+ 'workflowActivityVNext.schedule.refreshAria': '刷新计划任务',
+ 'workflowActivityVNext.schedule.refreshing': '正在刷新计划任务…',
+ 'workflowActivityVNext.schedule.refreshingDetail': '正在刷新计划任务详情…',
+ 'workflowActivityVNext.schedule.refreshingHistory': '正在刷新计划任务历史…',
+ 'workflowActivityVNext.schedule.overviewRegionAria': '计划任务概览',
+ 'workflowActivityVNext.schedule.historyRegionAria': '计划任务历史',
+ 'workflowActivityVNext.schedule.runNowAria': '立即运行 {name}',
+ 'workflowActivityVNext.schedule.repeat': '重复',
+ 'workflowActivityVNext.schedule.repeatSummary': '{repeat} 于 {time}',
+ 'workflowActivityVNext.schedule.weekly': '每周',
+ 'workflowActivityVNext.schedule.monthly': '每月',
+ 'workflowActivityVNext.schedule.dayOfWeek': '星期几',
+ 'workflowActivityVNext.schedule.dayOfMonth': '每月几号',
+ 'workflowActivityVNext.schedule.weeklySummary': '每周 {day} 于 {time}',
+ 'workflowActivityVNext.schedule.monthlySummary': '每月 {day} 日于 {time}',
+ 'workflowActivityVNext.schedule.weekday.0': '星期日',
+ 'workflowActivityVNext.schedule.weekday.1': '星期一',
+ 'workflowActivityVNext.schedule.weekday.2': '星期二',
+ 'workflowActivityVNext.schedule.weekday.3': '星期三',
+ 'workflowActivityVNext.schedule.weekday.4': '星期四',
+ 'workflowActivityVNext.schedule.weekday.5': '星期五',
+ 'workflowActivityVNext.schedule.weekday.6': '星期六',
+ 'workflowActivityVNext.schedule.reviewAction': '复核计划任务',
+ 'workflowActivityVNext.schedule.reviewTitle': '复核计划任务',
+ 'workflowActivityVNext.schedule.save': '保存更改',
+ 'workflowActivityVNext.schedule.scheduleName': '计划任务名称',
+ 'workflowActivityVNext.schedule.time': '时间',
+ 'workflowActivityVNext.schedule.timezone': '时区',
+ 'workflowActivityVNext.schedule.title': '计划任务',
+ 'workflowActivityVNext.schedule.titleForWorkflow': '{name} 的计划任务',
+ 'workflowActivityVNext.schedule.updateAccepted': '计划任务更新请求已受理。',
+ 'workflowActivityVNext.schedule.useRepeatBuilder': '使用重复规则构建器',
+ 'workflowActivityVNext.schedule.unavailableDescription':
+ '请先发布可运行的工作流,再创建周期性计划任务。',
+ 'workflowActivityVNext.schedule.unavailableTitle':
+ '工作流发布后才能使用计划任务',
+ 'workflowActivityVNext.schedule.updated': '计划任务更新请求已受理',
+ 'workflowActivityVNext.schedule.weekdays': '工作日',
+ 'workflowActivityVNext.schedule.writeCron': '改用 Cron 表达式',
+ 'workflowActivityVNext.schedule.workflow': '工作流',
+ 'workflowActivityVNext.schedule.enabledAfterCreation': '创建后启用',
+ 'workflowActivityVNext.state.forbidden': '你无权访问此工作区',
+ 'workflowActivityVNext.state.unauthorized': '请登录后继续',
+ 'workflowActivityVNext.unavailable.body': '请检查地址或返回其他页面。',
+ 'workflowActivityVNext.unavailable.description': '你请求的页面不可用。',
+ 'workflowActivityVNext.unavailable.title': '不可用',
+ 'workflowActivityVNext.workflows.activeView': '活跃工作流',
+ 'workflowActivityVNext.workflows.allView': '全部工作流',
+ 'workflowActivityVNext.workflows.archive': '归档',
+ 'workflowActivityVNext.workflows.archiveCheckAgain': '再次检查',
+ 'workflowActivityVNext.workflows.archiveConfirm': '归档工作流',
+ 'workflowActivityVNext.workflows.archiveDescription':
+ '这会停止已发布工作流的新运行。可编辑草稿、已发布修订和活动历史仍会保留。再次发布即可恢复。',
+ 'workflowActivityVNext.workflows.archiveFailed': '无法归档工作流',
+ 'workflowActivityVNext.workflows.archiveSuccess': '工作流已归档',
+ 'workflowActivityVNext.workflows.archiveTitle': '确认归档此工作流?',
+ 'workflowActivityVNext.workflows.archiveTryAgain': '重试',
+ 'workflowActivityVNext.workflows.archivedStatus': '已归档',
+ 'workflowActivityVNext.workflows.archivedView': '显示已归档工作流',
+ 'workflowActivityVNext.workflows.clearFilters': '清除筛选',
+ 'workflowActivityVNext.workflows.columnActions': '操作',
+ 'workflowActivityVNext.workflows.columnStatus': '状态',
+ 'workflowActivityVNext.workflows.columnUpdated': '最近更新',
+ 'workflowActivityVNext.workflows.columnWorkflow': '工作流',
+ 'workflowActivityVNext.workflows.copyReference': '复制工作流引用',
+ 'workflowActivityVNext.workflows.description':
+ '创建、编辑并运行你的工作流。',
+ 'workflowActivityVNext.workflows.descriptionAria': '{name} 的描述',
+ 'workflowActivityVNext.workflows.draftsUnavailable': '草稿工作流不可用',
+ 'workflowActivityVNext.workflows.draftsUnavailableDescription':
+ '请重试加载草稿工作流。',
+ 'workflowActivityVNext.workflows.draftsView': '草稿',
+ 'workflowActivityVNext.workflows.draftStatus': '草稿',
+ 'workflowActivityVNext.workflows.duplicateNameWarning':
+ '另一个工作流已使用此名称。允许使用重复名称。',
+ 'workflowActivityVNext.workflows.deleteDescription':
+ '此操作只删除可编辑草稿,已发布版本和运行历史会继续保留。',
+ 'workflowActivityVNext.workflows.deleteDraft': '删除草稿',
+ 'workflowActivityVNext.workflows.deleteFailed': '无法删除草稿',
+ 'workflowActivityVNext.workflows.deleteTitle': '删除可编辑草稿?',
+ 'workflowActivityVNext.workflows.empty': '暂无工作流',
+ 'workflowActivityVNext.workflows.emptyDescription':
+ '创建一个工作流即可开始。',
+ 'workflowActivityVNext.workflows.loadMore': '加载更多',
+ 'workflowActivityVNext.workflows.loadMoreFailed': '无法加载更多工作流',
+ 'workflowActivityVNext.workflows.loading': '正在加载工作流…',
+ 'workflowActivityVNext.workflows.moreActionsAria':
+ '{owner}中{name}的更多操作',
+ 'workflowActivityVNext.workflows.new': '新建工作流',
+ 'workflowActivityVNext.workflows.noMatch': '没有匹配的工作流',
+ 'workflowActivityVNext.workflows.noMatchDescription':
+ '请尝试其他搜索或筛选条件。',
+ 'workflowActivityVNext.workflows.openAria': '打开{owner}中的{name}',
+ 'workflowActivityVNext.workflows.runAria': '运行 {name}',
+ 'workflowActivityVNext.workflows.refreshAria': '刷新工作流',
+ 'workflowActivityVNext.workflows.retryAria': '重试加载工作流',
+ 'workflowActivityVNext.workflows.runFromEditor':
+ '请打开编辑器验证并运行此工作流。',
+ 'workflowActivityVNext.workflows.search': '搜索工作流',
+ 'workflowActivityVNext.workflows.searchAria': '搜索工作流',
+ 'workflowActivityVNext.workflows.title': '工作流',
+ 'workflowActivityVNext.workflows.unavailable': '工作流不可用',
+ 'workflowActivityVNext.workflows.unavailableDescription':
+ '请重试加载工作流。',
+ 'workflowActivityVNext.workflows.viewFilter': '工作流视图',
+ 'workflowActivityVNext.workflows.partialUnavailable': '部分工作流无法加载',
+ 'workflowActivityVNext.workflows.publishedStatus': '已发布',
+ 'workflowActivityVNext.workflows.referenceCopied': '已复制工作流引用',
+ 'workflowActivityVNext.workflows.referenceCopyFailed': '无法复制工作流引用',
+ 'workflowActivityVNext.workflows.rename': '重命名',
+ 'workflowActivityVNext.workflows.renameFailed': '无法重命名工作流',
+ 'workflowActivityVNext.workflows.renameSave': '保存名称',
+ 'workflowActivityVNext.workflows.renameSuccess': '工作流已重命名',
+ 'workflowActivityVNext.workflows.renameTitle': '重命名工作流',
+ 'workflowActivityVNext.workflows.viewActivity': '查看活动',
+ 'workflowActivityVNext.workflows.viewActivityAria':
+ '查看{owner}中{name}的活动',
+ 'workflowActivityVNext.workflows.workspaceOwner': '工作区',
+ };
+
+export default workflowActivityVNextMessages;
diff --git a/apps/aevatar-console-web/src/locales/zh-CN.ts b/apps/aevatar-console-web/src/locales/zh-CN.ts
index 6a5d8d526c..fa43ee8bd2 100644
--- a/apps/aevatar-console-web/src/locales/zh-CN.ts
+++ b/apps/aevatar-console-web/src/locales/zh-CN.ts
@@ -1,12 +1,20 @@
+import channelMessages from './channelMessages.zh-CN';
import projectMessages from './projectMessages.zh-CN';
+import workflowActivityVNextMessages from './workflowActivityVNextMessages.zh-CN';
const zhCNMessages = {
...projectMessages,
+ ...channelMessages,
+ 'workflowCanvasBenchmark.title': '工作流画布基准测试',
+ ...workflowActivityVNextMessages,
'common.appName': 'Aevatar',
+ 'console.home.unavailable': '暂时无法打开你的工作区',
+ 'console.home.retry': '重试',
'common.language.english': 'English',
'common.language.label': '语言',
'common.language.switch': '切换语言',
'common.language.zhCN': '中文',
+ 'common.user.account': '账户',
'common.user.logout': '退出登录',
'common.user.settings': '设置',
'common.user.signIn': '登录',
@@ -55,7 +63,8 @@ const zhCNMessages = {
'永久删除“{title}”?此会话无法恢复。',
'pages.chat.index.deleteChatFailed': '无法删除会话',
'pages.chat.index.deleteChatTitle': '删除会话?',
- 'pages.chat.index.emptyDescription': '描述你想创建的 Team、Member 或 Workflow。',
+ 'pages.chat.index.emptyDescription':
+ '描述你想创建的 Team、Member 或 Workflow。',
'pages.chat.index.failedToLoadConversation': '无法加载会话',
'pages.chat.index.failedToLoadHistory': '无法加载会话历史',
'pages.chat.index.historyStoredInWorkspace': '历史记录会保存到当前工作区。',
@@ -65,7 +74,8 @@ const zhCNMessages = {
'pages.chat.index.loadingConversation': '正在加载会话',
'pages.chat.index.loadingHistory': '正在加载会话历史',
'pages.chat.index.historySynchronizing': '会话历史仍在同步,请稍后重试。',
- 'pages.chat.index.missingChatHistoryContext': 'Chat 已完成,但未返回会话上下文。',
+ 'pages.chat.index.missingChatHistoryContext':
+ 'Chat 已完成,但未返回会话上下文。',
'pages.chat.index.newChat': '新会话',
'pages.chat.index.newChatAction': '新建会话',
'pages.chat.index.noChatHistory': '暂无会话历史',
@@ -114,8 +124,7 @@ const zhCNMessages = {
'pages.missionwall.latencySeconds': '{latency}s',
'pages.missionwall.liveState.connected':
'已连接到已发布 workflow run read models。',
- 'pages.missionwall.liveState.empty':
- '暂无可见的已发布 workflow runs。',
+ 'pages.missionwall.liveState.empty': '暂无可见的已发布 workflow runs。',
'pages.missionwall.liveState.loading': '正在加载已发布 workflow runs。',
'pages.missionwall.liveState.partialRunError':
'部分已发布 service runs 无法加载。',
@@ -167,8 +176,7 @@ const zhCNMessages = {
'pages.missionwall.status.unknown': '未知',
'pages.missionwall.status.waiting': '等待',
'pages.missionwall.state.auditLoading': '正在加载选中 run',
- 'pages.missionwall.state.auditPendingTitle':
- '这个 run 暂无步骤流',
+ 'pages.missionwall.state.auditPendingTitle': '这个 run 暂无步骤流',
'pages.missionwall.state.emptyKicker': '等待运行记录',
'pages.missionwall.state.emptyTitle': '暂无可见的已发布 workflow',
'pages.missionwall.state.loadingKicker': '加载运行态',
@@ -176,6 +184,7 @@ const zhCNMessages = {
'pages.missionwall.state.publishedWorkflowTitle': '暂无可见 run',
'pages.missionwall.stageSubtitle': 'Team {teamName} · {memberName}',
'pages.missionwall.stageTitle': '{workflowName} · Step Flow',
+ 'pages.missionwall.stepFlow': 'Step Flow',
'pages.missionwall.stepStatus.active': '运行中',
'pages.missionwall.stepStatus.completed': '已完成',
'pages.missionwall.stepStatus.failed': '失败',
@@ -676,7 +685,14 @@ const zhCNMessages = {
'用于匹配 bug、feature 或 _default 等分支 key 的值。',
'shared.studio.nodeConfiguration.switch.on.label': '分支输入',
'shared.studio.nodeConfiguration.switch.on.placeholder': '$input',
- 'shared.studio.nodeConfiguration.toolCall.tool.label': '工具',
+ 'shared.studio.nodeConfiguration.toolCall.arguments.description':
+ '属性名和值类型必须与工具要求的输入一致。',
+ 'shared.studio.nodeConfiguration.toolCall.arguments.label': '参数',
+ 'shared.studio.nodeConfiguration.toolCall.arguments.placeholder':
+ '\'{\'"query":"$input"\'}\'',
+ 'shared.studio.nodeConfiguration.toolCall.tool.description':
+ '使用工作流模板或目标角色工具设置中的准确名称,例如 web_search。',
+ 'shared.studio.nodeConfiguration.toolCall.tool.label': '工具名称',
'shared.studio.nodeConfiguration.toolCall.tool.placeholder': 'web_search',
'shared.studio.nodeConfiguration.transform.operation.label': '操作',
'shared.studio.nodeConfiguration.transform.operation.option.count':
@@ -717,6 +733,9 @@ const zhCNMessages = {
'shared.studio.nodeConfiguration.workflowCall.workflow.label': 'Workflow',
'shared.studio.nodeConfiguration.workflowCall.workflow.placeholder':
'child_workflow',
+ 'shared.workflowExecutionLogs.collapse': '收起工作流日志',
+ 'shared.workflowExecutionLogs.consoleAria': '工作流运行控制台',
+ 'shared.workflowExecutionLogs.tokenUsage': '令牌用量',
'teams.members.actions.build': '构建',
'teams.members.actions.clearEntry': '清除入口成员',
'teams.members.actions.create': '创建成员',
@@ -769,8 +788,10 @@ const zhCNMessages = {
'teams.automations.authorization.error': '无法继续授权',
'teams.automations.authorization.exactAccess': '精确权限',
'teams.automations.authorization.expiry': '凭证有效期',
- 'teams.automations.authorization.noExternalGrants': '无需外部 NyxID 服务或所有者 LLM 模型授权。',
- 'teams.automations.authorization.noOwnerLLMGrant': '此工作流无需所有者 LLM 模型授权。',
+ 'teams.automations.authorization.noExternalGrants':
+ '无需外部 NyxID 服务或所有者 LLM 模型授权。',
+ 'teams.automations.authorization.noOwnerLLMGrant':
+ '此工作流无需所有者 LLM 模型授权。',
'teams.automations.authorization.preparing': '正在准备授权检查',
'teams.automations.authorization.review': '检查授权',
'teams.automations.authorization.reviewAgain': '重新检查',
@@ -886,12 +907,14 @@ const zhCNMessages = {
'teams.automations.messages.createFailed': '自动化未创建:{message}',
'teams.automations.messages.createSuccess': '自动化已创建。',
'teams.automations.messages.authorizationAccepted': '授权请求已受理',
+ 'teams.automations.messages.actionFailed': '无法完成自动化操作。',
'teams.automations.messages.deleteAccepted': '删除请求已受理',
'teams.automations.messages.cronRequired': '请先填写 Cron 表达式。',
'teams.automations.messages.deleteSuccess': '自动化已删除。',
'teams.automations.messages.disableSuccess': '自动化已暂停。',
'teams.automations.messages.enableSuccess': '自动化已恢复。',
'teams.automations.messages.previewFailed': '预览失败:{message}',
+ 'teams.automations.messages.previewFailedGeneric': '无法预览后续计划运行。',
'teams.automations.messages.promptTooLong':
'周期 Prompt 最多 {maxLength} 个字符。',
'teams.automations.messages.runNowFailed': '立即运行请求失败:{message}',
@@ -905,6 +928,7 @@ const zhCNMessages = {
'teams.automations.messages.serviceIdentityMissing':
'所选成员还没有服务身份。',
'teams.automations.messages.updateFailed': '自动化未更新:{message}',
+ 'teams.automations.messages.updateFailedGeneric': '无法更新自动化。',
'teams.automations.messages.updateSuccess': '自动化已更新。',
'teams.automations.noPublishedMember.description':
'自动化需要成员拥有已发布的服务身份后才能运行。',
@@ -1079,6 +1103,15 @@ const zhCNMessages = {
'Studio resolved draft workflow {workflowId}, but loading it failed: {reason}',
'teamMemberWorkflowStudio.alerts.linkedWorkflowLoadFailed.title':
'Workflow draft could not be loaded.',
+ 'teamMemberWorkflowStudio.alerts.actionError.title': 'Workflow 修改需要处理',
+ 'teamMemberWorkflowStudio.alerts.draftSaveFailed.description':
+ '无法保存 Workflow 草稿。请检查修改后重试。',
+ 'teamMemberWorkflowStudio.alerts.memberCreateFailed.description':
+ '无法创建 Workflow 成员。请检查修改后重试。',
+ 'teamMemberWorkflowStudio.alerts.memberLinkFailed.description':
+ '暂时无法完成 Workflow 成员关联。草稿仍已保留,请再次保存以重试。',
+ 'teamMemberWorkflowStudio.alerts.saveAndPublishFailed.description':
+ '无法验证并发布该 Workflow。请检查 Workflow 后重试。',
'teamMemberWorkflowStudio.common.close': '关闭',
'teamMemberWorkflowStudio.executionPanel.consoleAria': '草稿运行控制台',
'teamMemberWorkflowStudio.explicitRequest.approval': '审批要求',
@@ -1101,6 +1134,7 @@ const zhCNMessages = {
'teamMemberWorkflowStudio.executionPanel.clear': '清空日志',
'teamMemberWorkflowStudio.executionPanel.copyAll': '复制全部日志',
'teamMemberWorkflowStudio.executionPanel.copyAllDone': '已复制全部日志。',
+ 'teamMemberWorkflowStudio.executionPanel.copyFailed': '无法复制日志。',
'teamMemberWorkflowStudio.executionPanel.copySelected': '复制选中日志',
'teamMemberWorkflowStudio.executionPanel.copySelectedDone':
'已复制选中日志。',
@@ -1136,13 +1170,14 @@ const zhCNMessages = {
'teamMemberWorkflowStudio.executionPanel.output': '输出',
'teamMemberWorkflowStudio.executionPanel.overview': '概览',
'teamMemberWorkflowStudio.executionPanel.rawFrames':
- '已收到 {count} 个运行事件,但暂时还没有节点输出。',
+ '已收到 {count} 个运行事件,正在等待第一个节点开始。',
'teamMemberWorkflowStudio.executionPanel.resultFirst': '结果',
'teamMemberWorkflowStudio.executionPanel.runLog': '运行日志',
'teamMemberWorkflowStudio.executionPanel.selectLog':
'选择一条日志查看它的输入、输出和原始事件数据。',
'teamMemberWorkflowStudio.executionPanel.steps': '步骤',
'teamMemberWorkflowStudio.executionPanel.status.error': '错误',
+ 'teamMemberWorkflowStudio.executionPanel.status.notRun': '未运行',
'teamMemberWorkflowStudio.executionPanel.status.pending': '待运行',
'teamMemberWorkflowStudio.executionPanel.status.recorded': '已记录',
'teamMemberWorkflowStudio.executionPanel.status.running': '运行中',
@@ -1157,10 +1192,8 @@ const zhCNMessages = {
'当前 Workflow 成员还没有可安全归属的执行记录。',
'teamMemberWorkflowStudio.executionsPanel.fallbackName': 'Workflow 执行',
'teamMemberWorkflowStudio.executionsPanel.inspect': '查看',
- 'teamMemberWorkflowStudio.executionsPanel.sectionAria':
- 'Workflow 执行记录',
- 'teamMemberWorkflowStudio.executionsPanel.serviceMeta':
- '服务已就绪',
+ 'teamMemberWorkflowStudio.executionsPanel.sectionAria': 'Workflow 执行记录',
+ 'teamMemberWorkflowStudio.executionsPanel.serviceMeta': '服务已就绪',
'teamMemberWorkflowStudio.executionsPanel.title': '执行记录',
'teamMemberWorkflowStudio.executionsPanel.unknownStatus': '未知',
'teamMemberWorkflowStudio.graph.branchCount.one': '1 个分支',
@@ -1191,8 +1224,7 @@ const zhCNMessages = {
'teamMemberWorkflowStudio.header.identityAria': 'Workflow 身份信息',
'teamMemberWorkflowStudio.header.inputSet': '已设置输入',
'teamMemberWorkflowStudio.header.invoke': '调用',
- 'teamMemberWorkflowStudio.header.invoke.open':
- '打开已发布成员的调用工作台。',
+ 'teamMemberWorkflowStudio.header.invoke.open': '打开已发布成员的调用工作台。',
'teamMemberWorkflowStudio.header.invoke.publishFirst':
'先发布这个成员,再调用它。',
'teamMemberWorkflowStudio.header.invoke.saveFirst':
@@ -1314,6 +1346,30 @@ const zhCNMessages = {
'teamMemberWorkflowStudio.runsPanel.sectionAria': '成员运行记录',
'teamMemberWorkflowStudio.runsPanel.title': '成员运行记录',
'teamMemberWorkflowStudio.runsPanel.unknownStatus': '未知',
+ 'teamMemberWorkflowStudio.toast.draftRunCompleted': '草稿运行已完成。',
+ 'teamMemberWorkflowStudio.toast.draftRunFailed':
+ '草稿运行失败。请查看结果后重试。',
+ 'teamMemberWorkflowStudio.toast.draftSaveFailed':
+ '无法保存 Workflow 草稿。请查看详细信息后重试。',
+ 'teamMemberWorkflowStudio.toast.draftSaved': 'Workflow 草稿已保存。',
+ 'teamMemberWorkflowStudio.toast.memberCreateFailed':
+ '无法创建 Workflow 成员。请查看详细信息后重试。',
+ 'teamMemberWorkflowStudio.toast.memberLinkFailed':
+ '暂时无法完成 Workflow 成员关联。请再次保存以重试。',
+ 'teamMemberWorkflowStudio.toast.memberCreated': 'Workflow 成员已创建。',
+ 'teamMemberWorkflowStudio.toast.memberPublished': 'Workflow 成员已发布。',
+ 'teamMemberWorkflowStudio.toast.publishFailed':
+ '无法发布 Workflow 成员。请查看详细信息后重试。',
+ 'teamMemberWorkflowStudio.toast.publishedMemberStatusRefreshed':
+ '已刷新已发布成员的状态。',
+ 'teamMemberWorkflowStudio.toast.publishedWorkflowSaved':
+ '已保存发布后的 Workflow。',
+ 'teamMemberWorkflowStudio.toast.publishStatusFailed':
+ '无法刷新发布状态。请查看详细信息后重试。',
+ 'teamMemberWorkflowStudio.toast.saveAndPublishFailed':
+ '无法保存并发布 Workflow。请查看详细信息后重试。',
+ 'teamMemberWorkflowStudio.toast.yamlApplyFailed':
+ '无法应用 YAML 修改。请查看详细信息后重试。',
'teamMemberWorkflowStudio.yamlPanel.apply': '应用到草稿',
'teamMemberWorkflowStudio.yamlPanel.cancel': '取消',
'teamMemberWorkflowStudio.yamlPanel.closeAria': '关闭 YAML 编辑器',
@@ -1393,27 +1449,53 @@ const zhCNMessages = {
'pages.governance.columns.service.endpoint.target': 'Service endpoint target',
'pages.governance.columns.service.target': 'Service target',
'pages.governance.governanceinspectordrawer.version.ready': 'Version ready',
- 'pages.governance.governanceworkbench.binding.accepted.for.creation': 'Binding was accepted for governance creation.',
- 'pages.governance.governanceworkbench.binding.accepted.for.retirement': 'Binding was accepted for retirement.',
- 'pages.governance.governanceworkbench.binding.accepted.for.update': 'Binding was accepted for update.',
- 'pages.governance.governanceworkbench.endpoint.accepted.for.creation': 'Endpoint was accepted for governance creation.',
- 'pages.governance.governanceworkbench.endpoint.accepted.for.exposure': 'Endpoint was accepted for {value1} exposure.',
- 'pages.governance.governanceworkbench.endpoint.accepted.for.update': 'Endpoint was accepted for update.',
+ 'pages.governance.governanceworkbench.binding.accepted.for.creation':
+ 'Binding was accepted for governance creation.',
+ 'pages.governance.governanceworkbench.binding.accepted.for.retirement':
+ 'Binding was accepted for retirement.',
+ 'pages.governance.governanceworkbench.binding.accepted.for.update':
+ 'Binding was accepted for update.',
+ 'pages.governance.governanceworkbench.endpoint.accepted.for.creation':
+ 'Endpoint was accepted for governance creation.',
+ 'pages.governance.governanceworkbench.endpoint.accepted.for.exposure':
+ 'Endpoint was accepted for {value1} exposure.',
+ 'pages.governance.governanceworkbench.endpoint.accepted.for.update':
+ 'Endpoint was accepted for update.',
'pages.governance.governanceworkbench.policy': 'Policy',
- 'pages.governance.governanceworkbench.policy.accepted.for.creation': 'Policy was accepted for governance creation.',
- 'pages.governance.governanceworkbench.policy.accepted.for.retirement': 'Policy was accepted for retirement.',
- 'pages.governance.governanceworkbench.policy.accepted.for.update': 'Policy was accepted for update.',
+ 'pages.governance.governanceworkbench.policy.accepted.for.creation':
+ 'Policy was accepted for governance creation.',
+ 'pages.governance.governanceworkbench.policy.accepted.for.retirement':
+ 'Policy was accepted for retirement.',
+ 'pages.governance.governanceworkbench.policy.accepted.for.update':
+ 'Policy was accepted for update.',
'pages.governance.governanceworkbench.service': 'Service',
- 'pages.governance.governanceworkbench.service.endpoint.target': 'Service endpoint target',
+ 'pages.governance.governanceworkbench.service.endpoint.target':
+ 'Service endpoint target',
'pages.governance.governanceworkbench.service.target': 'Service target',
'pages.governance.governanceworkbench.version.pending': 'Version pending',
'pages.governance.governanceworkbench.version.ready': 'Version ready',
- 'pages.governance.governanceworkbench.revision.was.published.for.governance.evaluation': 'Revision was published for governance evaluation.',
- 'pages.governance.governanceworkbench.revision.is.prepared.and.waiting.for': 'Revision is prepared and waiting for promotion decisions.',
- 'pages.governance.governanceworkbench.revision.missing.policies': 'Revision is missing {value1} required policies.',
+ 'pages.governance.governanceworkbench.revision.was.published.for.governance.evaluation':
+ 'Revision was published for governance evaluation.',
+ 'pages.governance.governanceworkbench.revision.is.prepared.and.waiting.for':
+ 'Revision is prepared and waiting for promotion decisions.',
+ 'pages.governance.governanceworkbench.revision.missing.policies':
+ 'Revision is missing {value1} required policies.',
'pages.runs.memberPublishedRuns.event': 'Event',
'pages.runs.memberPublishedRuns.member': 'Team member',
'pages.runs.memberPublishedRuns.step': 'Step',
+ 'pages.runs.index.invocation.accepted.waiting.for.activity':
+ '调用请求已受理,正在等待活动。',
+ 'pages.runs.index.prompt.required.before.sending': '请输入提示后再发送。',
+ 'pages.runs.index.resume.request.accepted.waiting.for.run':
+ '恢复请求已受理,正在等待运行继续。',
+ 'pages.runs.index.resume.request.not.accepted': '恢复请求未被受理,请重试。',
+ 'pages.runs.index.run.request.failed': '无法启动运行。',
+ 'pages.runs.index.selected.service.unavailable':
+ '所选服务当前不可用,正在使用工作区默认服务重试。',
+ 'pages.runs.index.signal.accepted.waiting.for.run':
+ '信号已受理,正在等待运行继续。',
+ 'pages.runs.index.signal.not.accepted': '信号未被受理,请重试。',
+ 'pages.runs.index.stop.failed': '无法停止运行。',
'pages.runs.runsinspectorpane.actor': 'Actor',
'pages.runs.runsinspectorpane.command': 'Command',
'pages.runs.runsinspectorpane.command.accepted': 'Command accepted',
@@ -1422,7 +1504,8 @@ const zhCNMessages = {
'pages.runs.runsinspectorpane.current.run.2': 'Current run',
'pages.runs.runsinspectorpane.current.run.ready': 'Current run ready',
'pages.runs.runsinspectorpane.event.recorded': 'Event recorded',
- 'pages.runs.runsinspectorpane.no.user.visible.payload': 'No user-visible payload fields.',
+ 'pages.runs.runsinspectorpane.no.user.visible.payload':
+ 'No user-visible payload fields.',
'pages.runs.runsinspectorpane.runtime.actor.ready': 'Runtime actor ready',
'pages.runs.runsinspectorpane.runtime.actor.ready.2': 'Runtime actor ready',
'pages.runs.runsinspectorpane.runtime.actor.ready.3': 'Runtime actor ready',
@@ -1446,19 +1529,93 @@ const zhCNMessages = {
'pages.scopes.invoke.selected.service': 'the selected service',
'pages.scopes.invoke.service': 'Service',
'pages.scopes.invoke.version.ready': 'Version ready',
- 'pages.scopes.scopeserviceruntimeworkbench.actor.available': 'Actor available',
+ 'pages.scopes.scopeserviceruntimeworkbench.actor.available':
+ 'Actor available',
'pages.scopes.scopeserviceruntimeworkbench.binding': 'Binding',
- 'pages.scopes.scopeserviceruntimeworkbench.binding.accepted.for.retirement': 'Binding was accepted for retirement.',
- 'pages.scopes.scopeserviceruntimeworkbench.binding.created': 'Binding was created.',
- 'pages.scopes.scopeserviceruntimeworkbench.binding.updated': 'Binding was updated.',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.accepted.for.retirement':
+ 'Binding was accepted for retirement.',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.change.accepted':
+ '绑定变更已受理。请查看列表中的当前状态。',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.created':
+ 'Binding was created.',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.id.label': '绑定 ID',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.id.required':
+ '请输入绑定 ID。',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.kind.connector': '连接器',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.kind.label': '绑定类型',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.kind.secret': '密钥',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.kind.service': '服务',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.retirement.failed':
+ '暂时无法提交绑定退役请求,请重试。',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.save.failed':
+ '暂时无法保存此绑定,请重试。',
+ 'pages.scopes.scopeserviceruntimeworkbench.binding.updated':
+ 'Binding was updated.',
+ 'pages.scopes.scopeserviceruntimeworkbench.bindings.preparing.description':
+ '刷新以查看绑定何时可用。',
+ 'pages.scopes.scopeserviceruntimeworkbench.bindings.preparing.title':
+ '绑定仍在准备中',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.accepted.description':
+ '正在检查最新状态。',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.accepted.title':
+ '请求已受理',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.delayed.description':
+ '最新列表尚未反映此请求。请刷新后再次检查。',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.delayed.title':
+ '更新仍在等待中。',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.failed.description':
+ '请先刷新最新状态,再决定是否重试。',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.failed.title':
+ '暂时无法确认更新状态',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.observed.description':
+ '最新列表已反映此项变更。',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.observed.title':
+ '更新已确认。',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.observing.description':
+ '正在等待最新列表更新。',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.observing.title':
+ '正在检查当前状态',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.dismiss': '关闭',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.refresh': '刷新',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.refreshing.description':
+ '正在检查最新列表。',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.refreshing.title':
+ '正在刷新当前状态',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.submitting.description':
+ '正在提交更新。',
+ 'pages.scopes.scopeserviceruntimeworkbench.action.submitting.title':
+ '正在保存更新',
+ 'pages.scopes.scopeserviceruntimeworkbench.connector.id.label': '连接器 ID',
+ 'pages.scopes.scopeserviceruntimeworkbench.connector.id.required':
+ '请输入连接器 ID。',
+ 'pages.scopes.scopeserviceruntimeworkbench.connector.type.label':
+ '连接器类型',
+ 'pages.scopes.scopeserviceruntimeworkbench.connector.type.required':
+ '请输入连接器类型。',
+ 'pages.scopes.scopeserviceruntimeworkbench.create.binding': '创建绑定',
+ 'pages.scopes.scopeserviceruntimeworkbench.display.name.label': '显示名称',
'pages.scopes.scopeserviceruntimeworkbench.endpoint': 'Endpoint',
'pages.scopes.scopeserviceruntimeworkbench.endpoints': 'endpoints',
'pages.scopes.scopeserviceruntimeworkbench.policy.count': '{value1} policies',
+ 'pages.scopes.scopeserviceruntimeworkbench.policy.ids.label': '策略 ID',
'pages.scopes.scopeserviceruntimeworkbench.ready': 'Ready',
- 'pages.scopes.scopeserviceruntimeworkbench.revision.accepted.for.retirement': 'Revision was accepted for retirement.',
+ 'pages.scopes.scopeserviceruntimeworkbench.revision.accepted.for.retirement':
+ 'Revision was accepted for retirement.',
+ 'pages.scopes.scopeserviceruntimeworkbench.revision.retirement.failed':
+ '暂时无法提交版本退役请求,请重试。',
'pages.scopes.scopeserviceruntimeworkbench.service': 'Service',
'pages.scopes.scopeserviceruntimeworkbench.service.target': 'Service target',
- 'pages.scopes.scopeserviceruntimeworkbench.serving.version.ready': 'serving version ready',
+ 'pages.scopes.scopeserviceruntimeworkbench.save.binding': '保存绑定',
+ 'pages.scopes.scopeserviceruntimeworkbench.secret.name.label': '密钥名称',
+ 'pages.scopes.scopeserviceruntimeworkbench.secret.name.required':
+ '请输入密钥名称。',
+ 'pages.scopes.scopeserviceruntimeworkbench.serving.version.ready':
+ 'serving version ready',
+ 'pages.scopes.scopeserviceruntimeworkbench.target.endpoint.optional.label':
+ '目标端点(可选)',
+ 'pages.scopes.scopeserviceruntimeworkbench.target.service.label': '目标服务',
+ 'pages.scopes.scopeserviceruntimeworkbench.target.service.required':
+ '请选择目标服务。',
'pages.scopes.scopeserviceruntimeworkbench.version': 'Version',
'pages.services.index.artifact.attached': 'Artifact attached',
'pages.services.index.endpoint': 'Endpoint',
@@ -1475,18 +1632,25 @@ const zhCNMessages = {
'pages.studio.bind.studiomemberbindpanel.revision.ready': 'Revision ready',
'pages.studio.bind.studiomemberbindpanel.run.completed': 'Run completed',
'pages.studio.bind.studiomemberbindpanel.service.ready': 'Service ready',
+ 'pages.studio.bind.studiomemberbindpanel.smoke.test.request.failed':
+ '无法完成冒烟测试,请重试。',
'pages.studio.studiobuildpanels.command.accepted': 'command: accepted',
'pages.studio.studiobuildpanels.current.run.ready': 'current run: ready',
'pages.studio.studiobuildpanels.events.count': 'events: {count}',
'pages.studio.studiobuildpanels.runtime.actor.ready': 'runtime actor: ready',
- 'pages.studio.studiobuildpanels.save.accepted.checking.again': 'Save accepted. Waiting for catalog; checking again in {value1}s.',
- 'pages.studio.studiobuildpanels.save.accepted.waiting.for.catalog': 'Save accepted. Still waiting for catalog; use Refresh catalog to check again.',
+ 'pages.studio.studiobuildpanels.save.accepted.checking.again':
+ 'Save accepted. Waiting for catalog; checking again in {value1}s.',
+ 'pages.studio.studiobuildpanels.save.accepted.waiting.for.catalog':
+ 'Save accepted. Still waiting for catalog; use Refresh catalog to check again.',
'pages.studio.studiobuildpanels.save.applied': 'Save applied.',
'pages.studio.studiobuildpanels.save.rejected': 'Save rejected.',
'pages.studio.studiobuildpanels.script': 'Script',
'pages.studio.studiobuildpanels.script.applied': 'Applied script',
'pages.studio.studiobuildpanels.script.draft': 'Script draft',
- 'pages.studio.studiobuildpanels.script.promotion.version.summary': 'Script promotion version summary',
+ 'pages.studio.studiobuildpanels.workflowActionFailed':
+ '无法完成工作流操作。请查看详细信息后重试。',
+ 'pages.studio.studiobuildpanels.script.promotion.version.summary':
+ 'Script promotion version summary',
'pages.studio.studiobuildpanels.version.ready': 'version ready',
'pages.studio.studiofilesdetailpane.chat.author.assistant': '助手',
'pages.studio.studiofilesdetailpane.chat.author.unknown': '未知作者',
@@ -1520,10 +1684,39 @@ const zhCNMessages = {
'pages.studio.studiomemberinvokeinspector.service.ready': 'Service ready',
'pages.studio.studiomemberinvokeinspector.version.ready': 'Version ready',
'pages.studio.studioworkbenchsections.runtime.available': 'Runtime available',
- 'pages.studio.studioworkbenchsections.runtime.available.2': 'Runtime available',
- 'pages.studio.studioworkbenchsections.runtime.available.3': 'Runtime available',
- 'pages.studio.studioworkbenchsections.runtime.available.4': 'Runtime available',
- 'pages.studio.studioworkbenchsections.runtime.facts.available': 'Runtime facts available',
+ 'pages.studio.studioworkbenchsections.runtime.available.2':
+ 'Runtime available',
+ 'pages.studio.studioworkbenchsections.runtime.available.3':
+ 'Runtime available',
+ 'pages.studio.studioworkbenchsections.runtime.available.4':
+ 'Runtime available',
+ 'pages.studio.studioworkbenchsections.runtime.facts.available':
+ 'Runtime facts available',
+ 'pages.studio.studiomemberinvokepanel.copyFailed': '无法复制该内容。',
+ 'pages.workflows.workflowyamlviewer.copy.success': 'YAML 已复制到剪贴板。',
+ 'pages.workflows.workflowyamlviewer.failed.to.copy.yaml': '复制 YAML 失败。',
+ 'pages.chat.chatadvancedconsole.timelineActionFailed':
+ '无法完成运行操作,请重试。',
+ 'pages.deployments.index.actionFailed': '无法完成部署操作,请重试。',
+ 'pages.gagents.index.bindingActionFailed': '无法完成绑定操作,请重试。',
+ 'pages.gagents.index.registryActionFailed':
+ '无法完成 Actor 注册表操作,请重试。',
+ 'pages.governance.governanceworkbench.actionFailed':
+ '无法完成治理操作,请重试。',
+ 'pages.studio.bind.studiomemberbindpanel.bindingActionFailed':
+ '无法完成绑定操作,请重试。',
+ 'pages.studio.studiobuildpanels.workflowSaveFailed':
+ '无法保存工作流,请重试。',
+ 'pages.studio.studiofilesdetailpane.connectorSaveFailed':
+ '无法保存连接器目录,请重试。',
+ 'pages.studio.studiofilesdetailpane.conversationDeleteFailed':
+ '无法删除会话,请重试。',
+ 'pages.studio.studiofilesdetailpane.roleSaveFailed':
+ '无法保存角色目录,请重试。',
+ 'pages.studio.studioinspectorpane.nodeActionFailed':
+ '无法应用节点更改,请重试。',
+ 'pages.studio.studioworkbenchsections.executionActionFailed':
+ '无法完成运行操作,请重试。',
'menu.WorkOrder Details': '请求详情',
'teams.detail.tabs.workOrders': '请求',
'workOrders.actions.backToRequests': '返回请求列表',
diff --git a/apps/aevatar-console-web/src/pages/Deployments/index.test.tsx b/apps/aevatar-console-web/src/pages/Deployments/index.test.tsx
index dfd7337a5a..b800c5ab68 100644
--- a/apps/aevatar-console-web/src/pages/Deployments/index.test.tsx
+++ b/apps/aevatar-console-web/src/pages/Deployments/index.test.tsx
@@ -324,10 +324,16 @@ describe('DeploymentsPage', () => {
renderDeploymentsPage();
- expect(await screen.findByText('正在加载发布服务')).toBeInTheDocument();
+ expect(await screen.findByRole('status')).toHaveAttribute(
+ 'data-variant',
+ 'table',
+ );
+ expect(screen.getByText('正在加载发布服务')).toHaveClass(
+ 'aevatar-loading-visually-hidden',
+ );
expect(
- screen.getByText('发布对象清单仍在加载,返回前不会把当前范围误判为空。'),
- ).toBeInTheDocument();
+ screen.queryByText('发布对象清单仍在加载,返回前不会把当前范围误判为空。'),
+ ).toBeNull();
expect(screen.getAllByText('—').length).toBeGreaterThanOrEqual(4);
expect(screen.queryByText('当前范围没有服务')).toBeNull();
diff --git a/apps/aevatar-console-web/src/pages/Deployments/index.tsx b/apps/aevatar-console-web/src/pages/Deployments/index.tsx
index dc649dfba7..40cb7d14a7 100644
--- a/apps/aevatar-console-web/src/pages/Deployments/index.tsx
+++ b/apps/aevatar-console-web/src/pages/Deployments/index.tsx
@@ -19,12 +19,12 @@ import {
Table,
Tabs,
Tag,
- Tooltip,
Typography,
theme,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import AevatarTooltip from '@/shared/ui/AevatarTooltip';
import {
readServiceQueryDraft,
trimServiceQuery,
@@ -75,6 +75,7 @@ import {
type AevatarThemeSurfaceToken,
} from '@/shared/ui/aevatarWorkbench';
import ConsoleMenuPageShell from '@/shared/ui/ConsoleMenuPageShell';
+import ConsoleOperationNotice from '@/shared/ui/ConsoleOperationNotice';
import {
cardStackStyle,
summaryFieldLabelStyle,
@@ -713,7 +714,7 @@ const DeploymentsScopeCard: React.FC<{
>
{t("pages.deployments.index.team.application.namespace.2", "team/Application/Namespace")}
-
+