diff --git a/README.md b/README.md index 5a26f8e..fe507c5 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,13 @@ ownership boundary. See [the module concept](docs/CONCEPT.md) and [BPMN interoperability contract](docs/BPMN_INTEROPERABILITY.md) for the shared model retained by Workflow Engine. +The editor route, graph, decision, state, and accessibility mapping is recorded +in [the interface pattern audit](docs/INTERFACE_PATTERN_MIGRATION.md). ## Checks ```bash /mnt/DATA/git/govoplan/.venv/bin/python -m unittest discover -s tests cd webui && npm run typecheck +cd webui && npm run test:interface-pattern ``` diff --git a/docs/INTERFACE_PATTERN_MIGRATION.md b/docs/INTERFACE_PATTERN_MIGRATION.md new file mode 100644 index 0000000..d666b7d --- /dev/null +++ b/docs/INTERFACE_PATTERN_MIGRATION.md @@ -0,0 +1,30 @@ +# Workflow Interface Pattern Migration + +Workflow is the optional visual authoring and inspection surface for the +headless Workflow Engine. It composes native BPMN graph editing, immutable +revision inspection, governed activation, and instance evidence without owning +runtime persistence or importing module-private action implementations. + +| Surface | Task and archetype | Consequence and state contract | +| --- | --- | --- | +| `/workflow` definition catalogue | List-detail workspace | Search, loading, empty, selected, current/historical revision, baseline, override, and update-available states retain definition context. | +| Native BPMN graph | Specialized create/edit workspace | Palette nodes can be dragged or added with keyboard activation. Nodes and edges can be selected, edited, reconnected, or removed through explicit controls; drag is not the only path. | +| Definition settings and derivation | Governed create/edit dialogs | Scope, kind, execution mode, inherited visibility, reuse, automation, and immutable View revision expose field help and policy provenance. | +| Validate/save/activate/archive/delete/reset | Review and consequential actions | Validation diagnostics identify graph elements. Save creates a revision; activation changes the runnable revision; archive/delete/reset use explicit state gates and Core confirmation dialogs. | +| Runs dialog and open-work widget | Monitoring, progress, and human decision | Instance/step status, handoff instructions, evidence references, transitions, retries, cancellation, reconciliation, failures, and partial outcomes remain durable Workflow Engine evidence. | + +Core owns buttons, icon buttons, dialog/focus behavior, alerts, status, form +help, toggles, selectors, unsaved-navigation protection, and documentation help. +The graph itself is the authorized domain-specific editor. Module standards and +optional Views/Policy integrations are consumed through public capabilities. +Responsive layouts move catalogue, palette, graph, and inspector into task +order; reduced-motion preferences disable editor animation as a source of +meaning. + +Verification: + +- `npm run typecheck` +- `npm run test:interface-pattern` +- Workflow editor and Workflow Engine backend suites +- manifest shape, optional-module permutations, structural localization, theme, + and full-product bundle-budget checks diff --git a/src/govoplan_workflow/backend/manifest.py b/src/govoplan_workflow/backend/manifest.py index 96a87a1..cc3adab 100644 --- a/src/govoplan_workflow/backend/manifest.py +++ b/src/govoplan_workflow/backend/manifest.py @@ -1,6 +1,7 @@ from __future__ import annotations from govoplan_core.core.modules import ( + DocumentationLink, DocumentationTopic, FrontendModule, FrontendRoute, @@ -109,6 +110,13 @@ manifest = ModuleManifest( documentation_types=("admin", "user"), audience=("operator", "module_admin", "power_user", "product_owner"), related_modules=("workflow_engine", "views", "policy", "audit"), + links=( + DocumentationLink( + label="Workflow interface pattern audit", + href="govoplan-workflow/docs/INTERFACE_PATTERN_MIGRATION.md", + kind="repository", + ), + ), order=76, ), ), diff --git a/webui/package.json b/webui/package.json index e981089..4b4c06c 100644 --- a/webui/package.json +++ b/webui/package.json @@ -13,7 +13,8 @@ } }, "scripts": { - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "test:interface-pattern": "node scripts/test-interface-pattern.mjs" }, "peerDependencies": { "@govoplan/core-webui": "^0.1.14", diff --git a/webui/scripts/test-interface-pattern.mjs b/webui/scripts/test-interface-pattern.mjs new file mode 100644 index 0000000..7e1211b --- /dev/null +++ b/webui/scripts/test-interface-pattern.mjs @@ -0,0 +1,21 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; + +const page = fs.readFileSync("src/features/workflow/WorkflowPage.tsx", "utf8"); +const runs = fs.readFileSync("src/features/workflow/WorkflowRunsDialog.tsx", "utf8"); +const inspector = fs.readFileSync("src/features/workflow/WorkflowInspector.tsx", "utf8"); +const styles = fs.readFileSync("src/styles/workflow.css", "utf8"); + +assert.ok(page.includes("DocumentationHelpLink"), "Workflow exposes configured-system help"); +assert.ok(page.includes("useUnsavedDraftGuard"), "Workflow protects dirty graph revisions during navigation"); +assert.ok(page.includes(" addNodeFromPalette(nodeType.type)}"), "Palette nodes have a keyboard/pointer alternative to drag and drop"); +assert.ok(inspector.includes("onEdgeChange"), "Edges can be edited without reconnect dragging"); +assert.ok(runs.includes("StatusBadge"), "Run and handoff states are not conveyed by color alone"); +assert.ok(runs.includes("DismissibleAlert"), "Run failures use the shared alert contract"); +assert.ok(!page.includes("window.alert("), "Workflow must not use browser alerts"); +assert.ok(styles.includes("@media (max-width: 680px)"), "Workflow retains a narrow-viewport layout"); +assert.ok(styles.includes("@media (prefers-reduced-motion: reduce)"), "Workflow honors reduced motion"); + +console.log("Workflow interface pattern contract passed."); diff --git a/webui/src/features/workflow/WorkflowPage.tsx b/webui/src/features/workflow/WorkflowPage.tsx index 7d20ef8..7b9204e 100644 --- a/webui/src/features/workflow/WorkflowPage.tsx +++ b/webui/src/features/workflow/WorkflowPage.tsx @@ -27,6 +27,7 @@ import { Button, ConfirmDialog, Dialog, + DocumentationHelpLink, DismissibleAlert, FormField, IconButton, @@ -74,6 +75,7 @@ import WorkflowRunsDialog from "./WorkflowRunsDialog"; import { FALLBACK_WORKFLOW_LIBRARY, draftFromDefinition, + newWorkflowNode, sampleWorkflowDraft, workflowFingerprint, workflowPayload, @@ -564,6 +566,21 @@ export default function WorkflowPage({ setSelectedEdgeId(null); }; + const addNodeFromPalette = (nodeType: string) => { + if (!draft || graphReadOnly) return; + const index = draft.graph.nodes.length; + const node = newWorkflowNode( + nodeType, + { + x: 80 + (index % 4) * 220, + y: 80 + Math.floor(index / 4) * 150 + }, + nodeLibrary + ); + updateGraph({ ...draft.graph, nodes: [...draft.graph.nodes, node] }); + setSelectedNodeId(node.id); + }; + return (
@@ -571,6 +588,10 @@ export default function WorkflowPage({
Workflows + } @@ -593,6 +614,7 @@ export default function WorkflowPage({ variant="primary" onClick={createNew} disabled={!canWrite} + disabledReason={!canWrite ? "Workflow definition write permission is required." : undefined} />
@@ -870,6 +892,7 @@ export default function WorkflowPage({ onDragStart={(event) => startPaletteDrag(event, nodeType.type) } + onClick={() => addNodeFromPalette(nodeType.type)} title={nodeType.description} > @@ -1195,6 +1218,7 @@ function WorkflowDefinitionSettingsDialog({