Implements the frontend side of the control-flow engine's Fase 4-5
backend extension: presentation-only swimlanes for grouping nodes by
actor/responsibility, and a new IOType.JSON for structured input/
output descriptors. Both are additive/optional fields, backward
compatible with existing flows.
Swimlanes:
- Model: FlowLane (id, name, description, order, color), FlowData.lanes,
and an optional laneId on every block/container.
- Lane management (add/rename/reorder/color/delete) in a new "Lanes"
section of the title toolbar, mirroring the existing Global Inputs
editor. Deleting a lane clears laneId on any node that referenced it,
avoiding an immediate NODE_LANE_NOT_FOUND validation error.
- Real visual swimlanes on the rete.js canvas: horizontal color-coded
bands with labels that pan/zoom together with the nodes, backed by a
transform layer kept in sync with the area's live transform.
- Drag-to-reassign: moving a node updates its laneId based on the drop
Y position, but only for genuine pointer drags — programmatic moves
(initial load, clone, server-side node regeneration) are excluded via
the existing programmatic-translation tracking, so loading a flow
never silently reassigns lanes.
- A small lane badge on the node header confirms the current
assignment after a drag.
- The readonly/execution-view diff and patch logic now accounts for
laneId and lanes so live updates are detected correctly.
Also fixes a data-loss bug found while wiring this up: exportGraph()
rebuilt each node and the top-level FlowData as explicit object
literals that never carried laneId/lanes through, and flowFromApi()
never normalized lanes coming back from the backend — both would have
silently dropped the field on save/reload, the same class of bug fixed
earlier for biasAnnotations.
JSON IO type:
- Global input type picker now offers JSON alongside TEXT/FILE.
- Behavioral probe editor: JSON-typed mock outputs default to {} and
use the JSON textarea editor instead of a plain text input.
- portSelectableKinds (generic-node) includes JSON among the concrete
types offered wherever a port's kind is ANY.
Verified with the full suite (253/253, 21 new tests) and a live
end-to-end browser check: created two lanes, dragged a node from the
unassigned area into a lane (confirmed via the node's lane badge),
saved, and reopened the saved flow with the lane assignment intact.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mirrors the existing block-side bias impact feature onto containers,
reusing the same descriptor, form and error handling as the backend
integration doc specifies, rather than duplicating them:
- FlowContainer now carries biasAnnotations, matching FlowBlock.
- ContainersService/ContainersCallService gained
retrieveBiasCapabilities/retrieveBiasCapabilitiesForInstance,
backed by GET/POST /containers/types/{type}/bias-capabilities.
- bias-annotations and behavioral-probe-editor now accept FlowNode
instead of FlowBlock only; the probe editor dispatches capability
lookups to BlocksService or ContainersService based on nodeFamily,
so container annotations reuse the exact same shared components
and UI instead of a second, parallel implementation.
- container-node gained the bias badge, the bias-annotations panel,
and preserves annotations across recreateContainer() the same way
generic-node already does across block regeneration.
- task-execution-viewer's bias-rerun candidate list now includes
containers with executable probes, not just blocks.
Also fixes a real data-loss bug found while wiring this up:
rete-editor's exportGraph explicitly stripped bias annotations from
containers on every flow save, silently discarding anything entered
through the new panel.
Verified with the full suite (238/238, 6 new tests) and a live
end-to-end browser check: an annotation added to a container only
offered INPUT_TRANSFORMATION/OUTPUT_TRANSFORMATION (per the fake
container capabilities), and survived a save + reopen of the saved
flow, proving the exportGraph fix actually round-trips the data.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
generic-node's "Delete node?" popup used its own slightly different
red shades (border #fca5a5, gradient background, text #881337)
instead of the #7f1d1d/#fecaca/#fff1f2 combination that its own
warning tooltip and container-node's identical delete-confirm popup
already use consistently. Aligned it to that shared combination.
Investigated the header color difference between generic-node (blue)
and container-node (teal) that an earlier audit flagged as an
inconsistency: task-step-node.css shows the header gradient is a
deliberate per-node-type identity color (blue = automatic, orange =
human-interaction, per its .llm-node--human .llm-header rule), so
container-node's teal is consistent with that scheme, not a bug — left
unchanged. The port badge colors (input/output/dependency) were
already identical between generic-node and container-node.
Verified with the full suite (232/232) and a live browser check of
the delete-confirm popup.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
While verifying the dialog style convergence, found that generic-node's
"Edit <field>" popup and container-node's "Edit Name" popup have the
same bug just fixed in bias-annotations: they render inside the node
card, which rete.js positions with a CSS transform for pan/zoom, so
their fixed-position .llm-modal-backdrop was trapped inside the node's
own box instead of covering the page — confirmed live, the backdrop's
bounding rect matched the node's box (280x533) rather than the
viewport.
This popup is the primary way to edit any short parameter on any node
in the editor, so the bug was hit far more often than the bias one.
Same fix: promote the backdrop to a native <dialog> shown via
showModal(), move the dimming to ::backdrop, and guard showModal()
since jsdom doesn't implement it. Verified live: the backdrop's rect
now spans the full viewport for both generic-node and container-node,
and closing via a backdrop click still works.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each of these four dialogs hardcoded its own border-radius, border
color, shadow and z-index instead of sharing the tokens introduced
for modal-shell. Two of the mismatches were more than cosmetic:
- confirm-dialog sat at z-index 10000, below node-settings-dialog
(10010) and below any modal-shell-based dialog (10021). Both
bias-rerun-dialog and bias-impact-experiment-dialog call
ConfirmDialogService while staying open themselves, so a confirm
triggered from either would render completely hidden behind them
and be unclickable, silently stalling the flow. confirm-dialog is
now the top of the z-index ladder (--z-confirm: 10031) since it is
the one dialog that can legitimately nest inside any other.
- human-interaction-dialog used a 12px corner radius while every
other dialog uses 16px; aligned it to --radius-modal.
subflow-preview-dialog keeps its larger radius, gradient background
and node-card border color, since it deliberately mirrors the canvas
aesthetic of the subflow it previews rather than a form dialog — only
its z-index was aligned to the shared ladder.
Verified with the full suite (232/232) and a live browser check of
the token-consuming modal-shell dialogs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The dialog/modal CSS across the app hardcodes the same handful of
values (border-radius, box-shadow, surface/border/muted-text colors,
z-index) independently in each component, with several components
already drifting to slightly different values for the same intent.
Add a small set of :root custom properties in styles.css (radius,
shadow, surface/border/text colors, spacing scale, modal z-index
ladder) and migrate modal-shell.css — the shared shell already used
by three dialogs — onto them first, as the lowest-risk starting
point. Values are unchanged, only their source moved to tokens, so
this is a no-op visually: verified via the existing test suite
(232/232) and a live browser check confirming computed values are
unchanged and the editor/dashboard render identically.
Follow-up: retrofit confirm-dialog, node-settings-dialog,
human-interaction-dialog and subflow-preview-dialog onto the same
tokens, then reconcile node CSS (generic-node/container-node).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The bias annotation editor rendered its dimming backdrop as a plain
position:fixed div. Since it sits inside a node card that rete.js
positions with a CSS transform for pan/zoom, that transform becomes
the containing block for fixed descendants, so the backdrop was
confined to the node's own box instead of covering the page.
Switch to a native <dialog> shown via showModal(), which promotes it
to the browser's top layer regardless of ancestor transforms, and use
::backdrop for the dimming overlay. showModal() is guarded because
jsdom does not implement it, so unit tests keep working.
Confirmed both by unit tests and a live browser check: the backdrop's
bounding rect now spans the full viewport instead of the node's box.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bugs:
- blocks.ts/containers.ts: reset toInit on a failed initial catalog load so
the next call retries instead of leaving the catalog permanently empty.
- task-step-node.ts: align isEmptyDisplayValue with generic-node's
isMissingValue so a field renders consistently between the editor and the
execution view for the same schema.
- flows-call.fake.ts/assistant-call.fake.ts: wrap synchronous throws in
defer() so catchError() on the caller side actually intercepts them.
- utilities/rete-editor.ts: remove a no-op ternary and a leftover debug log.
- stores/flow-editor.ts: save() now returns a handled error instead of
crashing on a non-null assertion when no flow is loaded.
- Removed leftover console.log statements (title-toolbar, flow-item,
editor-sidebar).
Dead code removed: the unused admin-users page, editor-sidebar's unused
createNewBlock(), rete-editor's unused flowChanged output, and the broken,
uncalled ListStateViewHolder.create().
Refactors (duplication called out by the same review):
- New session-guard.ts factory backing authGuard/adminGuard.
- tasks-executor's formatDuration now reuses the shared util.
- New services/shared/http-error.util.ts replacing the duplicated
extractHttpErrorMessage/toHttpError in admin-call.ts and authorization-call.ts.
- New services/bias/bias-error.util.ts unifying the three different ad hoc
error-message extractions across the bias-* dialogs.
- New pages/admin/admin-access.util.ts and utilities/temporary-signal.ts
replacing the duplicated redirectOnAdminAccessDenied and auto-dismiss-toast
patterns.
- New shared ModalShellComponent adopted by the three bias-* dialogs (their
backdrop/header/footer CSS was already byte-identical); new
password-form-validators.ts and a shared password-dialog-chrome.css
collapsing admin-reset-password-dialog and change-password-dialog, which
duplicated their entire validation logic and CSS.
- New services/shared/{catalog-store,empty-node-cache,pending-sync-counter,
deep-clone,flow-node-mapping}.ts: BlocksService/ContainersService and their
*-call.ts mappers were near line-for-line duplicates (which is exactly how
the toInit bug ended up in both).
- New shared/nodes/node-focus-modal-controller.ts: generic-node.ts and
container-node.ts had ~150 identical lines of focus-modal/body-scroll-lock
plumbing.
Confirm-dialog, node-settings-dialog, human-interaction-dialog and
subflow-preview-dialog were deliberately left out of the modal-shell
extraction: each has a meaningfully different structure and no way to verify
visually here, so forcing them into a shared shell was judged higher risk
than the cosmetic-only bias-*/password dialogs, whose CSS was already
byte-identical.
61 test files / 230 tests passing; `ng build` and `ng test` green throughout.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Finishes the bias impact experiments plan (docs/bias-impact-experiments-plan.md
steps 7-12):
- Side-effect policy selector: reuse the .llm-warning visual language for the
external side-effects banner, add a REQUIRE_CONFIRMATION note.
- Full-flow compare ("Compare with baseline"): new bias-compare-dialog
(service + host) triggering compareBiasExecutions and opening the shared
report viewer; inline errors read from errors[].message/detail.
- Persisted reports: new bias-impact-report-list (list + detail in one view)
wired into a new "Bias impact reports" tab in task-execution-viewer;
404/403 on report detail show the same inline message on purpose.
- Canvas: annotation badge on generic-node (count, executable-probe
indicator, severity from the backend catalog); new
BiasComparisonViewStateService driving bias-active / downstream-changed /
routing-change highlighting on task-step-node and custom-connection, fed by
a highlightOnCanvas event from bias-impact-report-viewer wired in all three
places that render it; legend + "back to normal view" action in the canvas
toolbar.
- Fixed a bug where the bias variant context badge only rendered for
simulated executions.
- Fixed "Measure bias impact" to stay visible-but-disabled with an
explanatory tooltip while the baseline hasn't reached a final state,
instead of being hidden outright, per the §12 checklist.
- Added a Retry action to the compare dialog's error state for parity with
the report list.
- Added the end-to-end facade flow test (annotation -> capability -> isolated
experiment -> report) plus coverage for all new components/services.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Added assistant error message state with retry capability
- Introduced assistantErrorMessage, lastFailedPrompt, and lastSubmittedPrompt signals
- Created progressOnlyMode computed signal for unified progress display
- Implemented handleAssistantErrorWithRetry method for consistent error handling
- Added retry button with conditional rendering in template
- Enhanced error states with improved styling for error cards
- Extracted sendPrompt logic to support both new prompts and retries
- Improved session reload with failure state handling
- Fix memory leak in task-executions polling (DestroyRef)
- Fix race condition in flows service (error recovery)
- Add centralized NotificationService + GlobalNotificationComponent
- Convert main routes to lazy loading (loadComponent)
- Remove static state from auth interceptor
- Write real unit tests (guards, stores, services)
- Strengthen HFNodeData types (explicit properties)
- Extract pure functions from TaskExecutionViewer into utils
- Add console.warn logging to guards on access denied
- Expand runtime config (apiUrl, assistantEnabled, tourModeAlwaysOn, turnstileEnabled)
- Document FormUtility and ListStateViewHolder base classes
- Remove unused lucide-angular dependency
- Replace ngModel with signal bindings in flows-list/blocks-list
- Use :where() for global mat-form-field override
- Document design system tokens in styles.css