Adopt the shared interface pattern language
This commit is contained in:
@@ -23,10 +23,13 @@ ownership boundary.
|
|||||||
See [the module concept](docs/CONCEPT.md) and
|
See [the module concept](docs/CONCEPT.md) and
|
||||||
[BPMN interoperability contract](docs/BPMN_INTEROPERABILITY.md) for the shared
|
[BPMN interoperability contract](docs/BPMN_INTEROPERABILITY.md) for the shared
|
||||||
model retained by Workflow Engine.
|
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
|
## Checks
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/mnt/DATA/git/govoplan/.venv/bin/python -m unittest discover -s tests
|
/mnt/DATA/git/govoplan/.venv/bin/python -m unittest discover -s tests
|
||||||
cd webui && npm run typecheck
|
cd webui && npm run typecheck
|
||||||
|
cd webui && npm run test:interface-pattern
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from govoplan_core.core.modules import (
|
from govoplan_core.core.modules import (
|
||||||
|
DocumentationLink,
|
||||||
DocumentationTopic,
|
DocumentationTopic,
|
||||||
FrontendModule,
|
FrontendModule,
|
||||||
FrontendRoute,
|
FrontendRoute,
|
||||||
@@ -109,6 +110,13 @@ manifest = ModuleManifest(
|
|||||||
documentation_types=("admin", "user"),
|
documentation_types=("admin", "user"),
|
||||||
audience=("operator", "module_admin", "power_user", "product_owner"),
|
audience=("operator", "module_admin", "power_user", "product_owner"),
|
||||||
related_modules=("workflow_engine", "views", "policy", "audit"),
|
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,
|
order=76,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
+2
-1
@@ -13,7 +13,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit",
|
||||||
|
"test:interface-pattern": "node scripts/test-interface-pattern.mjs"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.14",
|
"@govoplan/core-webui": "^0.1.14",
|
||||||
|
|||||||
@@ -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("<ConfirmDialog"), "Destructive and corrective definition actions use shared confirmation");
|
||||||
|
assert.ok(page.includes("<Dialog"), "Definition settings and derivation use shared focus-contained dialogs");
|
||||||
|
assert.ok(page.includes("onClick={() => 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.");
|
||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
ConfirmDialog,
|
ConfirmDialog,
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DocumentationHelpLink,
|
||||||
DismissibleAlert,
|
DismissibleAlert,
|
||||||
FormField,
|
FormField,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -74,6 +75,7 @@ import WorkflowRunsDialog from "./WorkflowRunsDialog";
|
|||||||
import {
|
import {
|
||||||
FALLBACK_WORKFLOW_LIBRARY,
|
FALLBACK_WORKFLOW_LIBRARY,
|
||||||
draftFromDefinition,
|
draftFromDefinition,
|
||||||
|
newWorkflowNode,
|
||||||
sampleWorkflowDraft,
|
sampleWorkflowDraft,
|
||||||
workflowFingerprint,
|
workflowFingerprint,
|
||||||
workflowPayload,
|
workflowPayload,
|
||||||
@@ -564,6 +566,21 @@ export default function WorkflowPage({
|
|||||||
setSelectedEdgeId(null);
|
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 (
|
return (
|
||||||
<main className="workflow-page">
|
<main className="workflow-page">
|
||||||
<div className="workflow-shell">
|
<div className="workflow-shell">
|
||||||
@@ -571,6 +588,10 @@ export default function WorkflowPage({
|
|||||||
<div className="workflow-panel-toolbar">
|
<div className="workflow-panel-toolbar">
|
||||||
<strong>Workflows</strong>
|
<strong>Workflows</strong>
|
||||||
<span className="workflow-toolbar-actions">
|
<span className="workflow-toolbar-actions">
|
||||||
|
<DocumentationHelpLink
|
||||||
|
reference={{ topicId: "workflow.editor", documentationType: "user" }}
|
||||||
|
label="Open Workflow documentation"
|
||||||
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
label="Refresh"
|
label="Refresh"
|
||||||
icon={<RefreshCw size={16} />}
|
icon={<RefreshCw size={16} />}
|
||||||
@@ -593,6 +614,7 @@ export default function WorkflowPage({
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={createNew}
|
onClick={createNew}
|
||||||
disabled={!canWrite}
|
disabled={!canWrite}
|
||||||
|
disabledReason={!canWrite ? "Workflow definition write permission is required." : undefined}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -870,6 +892,7 @@ export default function WorkflowPage({
|
|||||||
onDragStart={(event) =>
|
onDragStart={(event) =>
|
||||||
startPaletteDrag(event, nodeType.type)
|
startPaletteDrag(event, nodeType.type)
|
||||||
}
|
}
|
||||||
|
onClick={() => addNodeFromPalette(nodeType.type)}
|
||||||
title={nodeType.description}
|
title={nodeType.description}
|
||||||
>
|
>
|
||||||
<GitFork size={16} />
|
<GitFork size={16} />
|
||||||
@@ -1195,6 +1218,7 @@ function WorkflowDefinitionSettingsDialog({
|
|||||||
<FormField
|
<FormField
|
||||||
label="Scope"
|
label="Scope"
|
||||||
help="The scope determines ownership, visibility, and the Policy inheritance path."
|
help="The scope determines ownership, visibility, and the Policy inheritance path."
|
||||||
|
documentation={{ topicId: "workflow.editor", documentationType: "admin" }}
|
||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
value={draft.scopeType}
|
value={draft.scopeType}
|
||||||
@@ -1241,6 +1265,7 @@ function WorkflowDefinitionSettingsDialog({
|
|||||||
<FormField
|
<FormField
|
||||||
label="Definition kind"
|
label="Definition kind"
|
||||||
help="Templates can be reused or derived, but cannot be activated or started."
|
help="Templates can be reused or derived, but cannot be activated or started."
|
||||||
|
documentation={{ topicId: "workflow.editor", documentationType: "user" }}
|
||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
value={draft.definitionKind}
|
value={draft.definitionKind}
|
||||||
@@ -1257,6 +1282,7 @@ function WorkflowDefinitionSettingsDialog({
|
|||||||
<FormField
|
<FormField
|
||||||
label="Execution mode"
|
label="Execution mode"
|
||||||
help="Guided flows require a user; automated flows cannot contain human handoffs; hybrid flows can combine both."
|
help="Guided flows require a user; automated flows cannot contain human handoffs; hybrid flows can combine both."
|
||||||
|
documentation={{ topicId: "workflow.editor", documentationType: "user" }}
|
||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
value={draft.executionMode}
|
value={draft.executionMode}
|
||||||
@@ -1278,6 +1304,7 @@ function WorkflowDefinitionSettingsDialog({
|
|||||||
<FormField
|
<FormField
|
||||||
label="Workflow View"
|
label="Workflow View"
|
||||||
help="The selected immutable View revision is applied for active runs. Individual steps may narrow it further."
|
help="The selected immutable View revision is applied for active runs. Individual steps may narrow it further."
|
||||||
|
documentation={{ topicId: "workflow.editor", documentationType: "user" }}
|
||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
value={draft.viewId}
|
value={draft.viewId}
|
||||||
|
|||||||
@@ -1226,3 +1226,14 @@
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.workflow-page *,
|
||||||
|
.workflow-page *::before,
|
||||||
|
.workflow-page *::after {
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user