Commit Graph

104 Commits

Author SHA1 Message Date
Lucio Lelii a426a2c5e7 Summarise an array row by the branch it is on
An upload row taking its file from a global listed as "GLOBAL · false".
The summary takes the first two values a row holds, and the false was a
"several files" box nobody had touched - while the global the row names,
the one thing that tells it apart from the next row, never got a look in.

Two kinds of value are skipped now, both for the same reason: they
identify nothing. A false flag is an absence dressed as a fact, and a
field the row's own choice hides is irrelevant by construction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 14:55:29 +02:00
Lucio Lelii f41962993c Show step preview placeholders as expandable values, not a flat string replace
A node's Condition/prompt preview resolved ${{name}} placeholders with a
single-pass string replace, so a runtime value got duplicated wherever the
same placeholder repeated in the source text (e.g. a Conditional's
${{x}} != null && ${{x}}.contains(...) pattern) and long/verbose values were
dumped inline unbounded. Reuse the existing template-placeholder machinery
(already used for HumanDecisionBlock/HumanInteractionBlock text) instead: a
new "template" field type on the settings dialog renders each placeholder as
its own expandable segment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 13:26:58 +02:00
Lucio Lelii 86e61690bb Open a step's failure instead of squeezing it into a tooltip
A step's error is regularly a provider payload or a stack, and a hover
tooltip could only ever clip it: there was no way to read past the first
few lines, let alone paste it into a bug report.

The badge now teases the failure - "Error executing" plus its first
line, clamped - and opens the whole text in a dialog where it keeps its
own line breaks, scrolls, stays selectable, and copies in one click.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 16:49:40 +02:00
Lucio Lelii 8a57d74fb9 Say when a flow has no global input to take a field from
"From global inputs" was offered on every bindable field, so on a flow that declares none it led to
an empty picker and a form that could not be saved - a dead end with nothing explaining it. The
choice stays visible, because it is what tells you the possibility exists, but it is disabled and
says "(none declared)", and the value field it replaces is hidden while it is selected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 12:23:25 +02:00
Lucio Lelii eef5dfb3ed Stop offering a default on a field the state has made required
A schema-driven dialog read only the schema's own `required` list, so a field made required by
`x-ui-required-when` - an MCP server's `url` once the catalog is off, its `name` - was drawn with
"Use default" and could be saved empty. The inline editor already accounted for the conditional
rule; this brings the dialog to the same answer, and the same rule now gates both the button and
saving.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 12:23:19 +02:00
Lucio Lelii c298c352b3 Let a configurable field take a global input, and show its own tip
The small parameter editor offered two sources for a configurable-as-input
field: a static value, or a workflow input. A global input was reachable only
by typing ${{global.<name>}} by hand into the value box - and on a provider
whose model list is closed there is no value box to type into, which left the
one flow-level way of deciding such a field unreachable from the editor.

There is now a third source that lists the flow's declared globals and writes
the placeholder for you, with a plain sentence when the flow declares none
rather than an empty select.

The modal also never rendered a field's description, so every tip written on
one of these fields was invisible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 10:53:01 +02:00
Lucio Lelii a8cad0d3cd Let the placeholder flag alone decide, without requiring a textarea
buildTemplatedRichContentParts asked for x-ui-widget: textarea as well as
the flag, so a one-line field could not have its ${{...}} marked even when
its value really is interpolated. Whether a value is interpolated is a
property of the value, not of the control used to type it.

A no-op for everything that exists today: every field carrying the flag
gets it from LongText, whose widget defaults to textarea. It is what lets
LLMDescriptor.model declare the same thing without becoming a textarea.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 14:31:20 +02:00
Lucio Lelii 3ba14244c3 Stop the HumanInteractive config type claiming fields the server drops
The type declared llmDescriptor, inputAsList and outputAsList. None of the
three exists on the Java class, which has only actionDescription, and
BlockConfiguration carries @JsonIgnoreProperties(ignoreUnknown = true), so
everything the fakes were sending was discarded in silence. They are
leftovers from when simulation was configured on the node, before it moved
to the execution - the descriptor a human task's executor uses is the
simulator's, passed in when the run is launched.

Worse than dead: the fake block-type schema declared simulateWith as a
*required* property, so in dev mode the editor rendered a field the real
server has no idea about. That schema also carried an LLMDescriptor
definition nothing referenced once simulateWith was gone.

A type that lies costs more than the fields it saves. This one sent me
planning work for a block that has no LLM.

Also pins what the node editor does with a nested field bound to an input,
which needed no change to support llmDescriptor.model: a blank value at a
dotted path plus a matching port reads as provided by that input, a set
value does not, and neither does a blank one before the server has created
the port.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 14:24:13 +02:00
Lucio Lelii a6a811549a Enforce schema bounds in the editors, and say what an empty field does
Every bound was already in the schema and already enforced by the server,
but nothing passed it to the control: a temperature of 5 was typeable and
only failed on save. The settings dialog and the inline node editor now
share one validator, so a bound declared once reads the same wherever a
value can be typed. Numeric properties finally get a numeric control.

Arrow increment and required granularity are kept apart: step says what
the value must be a multiple of - 1 on an integer, nothing on a decimal -
while stepIncrement only moves the spinner. Arrows on a 0-to-1 field used
to jump by 1, reaching only the two ends of the range; they now move by a
tenth without making 0.35 wrong. FieldValueConstraints omits stepIncrement
so the increment cannot reach the validator to try.

An empty optional field now states that it is using the default, with a
reset beside the control that stays in place and greys out rather than
appearing once a value is typed. Going back to unset is the one thing a
filled box cannot express: clearing it by hand looks identical to never
having decided. Generic - it follows from the schema not requiring the
field, on all three editing surfaces, container included.

Also fixes the dialog reading as broken: descriptions were rendered twice,
once as a mat-hint and once below in error red, and the wrapping hint
overflowed the fixed-height subscript area onto the button beside it.

An optional group now sits in the fieldset of the object that owns it, so
a node holding two LLM descriptors cannot show two identical "Model
parameters" controls with nothing to tell them apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 11:53:40 +02:00
Lucio Lelii 43bcc38b9e Honour optional groups in the container node too
Same annotation, same control, same write-back as in a block: one button that
opens the whole object, a badge saying how many settings are set, and a group
that disappears when everything is cleared.

The one container-specific piece is how a select resolves its options, which the
shared round trip takes as a callback.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 14:08:17 +02:00
Lucio Lelii 0c0fac3643 Move the object dialog round trip into the shared schema module
An annotation honoured by one node type is worse than no annotation, so the
container needs the same round trip the block editor has. It moves as it is -
the characterisation tests written before the extraction still pass unchanged -
with only the two genuinely component-specific parts left as callbacks: how
select options are resolved, and how a dynamic sub-schema is expanded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 14:04:49 +02:00
Lucio Lelii c6060540f9 Render an optional group as one button that opens a modal
Five empty chips for parameters nobody sets on most nodes took more room than
the prompt. The group now shows as a single control saying how many of its
settings are set, and opens the whole object in one dialog.

The write-back reuses the object round trip rather than the array one, so an
optional numeric cleared in the modal removes the key instead of persisting 0 -
otherwise the provider default would be unreachable, which is the bug fixed
yesterday for the inline editor. Clearing everything drops the object entirely,
so a saved flow never carries an empty husk that implies a choice was made.

A temperature of 0 counts towards the badge: it is the repeatable setting, not
an absence, and a collapsed control must never hide a value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 12:39:55 +02:00
Lucio Lelii b278f7f9c7 Let the schema-driven form carry a whole object as one field
Two changes to the core of the form, both needed before anything can render an
optional group as a single control.

collectSchemaLeafFields stops unfolding an object marked x-ui-optional-group and
emits the object itself instead. Until now no code path produced a leaf of type
object at all: `hasChildren && !isArray` always recursed, which is why five
settings most nodes never touch took more room on the card than the prompt.

Emitting it is opt-in, for the same reason arrays are: most callers want editable
scalars and would choke on an object. An unmarked nested object still unfolds
exactly as before, and a test pins that - the change has to be per object, not a
new rule for nested objects in general.

SchemaDisplayItem gains a fourth slot beside field / richContent / array, with
its lookup map in buildOrderedSchemaDisplay. Nothing fills it yet.

588 frontend tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 12:29:54 +02:00
Lucio Lelii 1e381aa0ef Cover the array item modal before refactoring it
This round trip - item schema to dialog fields and back to an object - is the
machinery an optional-group modal wants to reuse, and nothing covered it: the
dialog mock in this spec resolved null, so no test ever reached the builder or
the parser.

Six characterisation tests pin what it does today: which fields it builds and
with what labels and types, that it writes the parsed item into the array,
that it edits in place rather than appending, and that a cancelled dialog
changes nothing.

One of them pins behaviour I intend to change and deliberately does not endorse:
an emptied required number becomes 0. Writing it down is the point - the optional
case has to differ, and the difference should be visible as a changed assertion
rather than as a silent shift.

584 frontend tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 12:22:00 +02:00
Lucio Lelii b53a22dcc4 Name a nested fieldset readably instead of by its dotted path
A field inside a nested object whose object declares no name of its own took the
raw parent path as its fieldset legend, which the uppercasing CSS then rendered
as "LLMDESCRIPTOR.PARAMETERS".

The expression `?? parentPath(path)` appeared in four places - twice in
generic-node, twice in container-node - plus a fifth in task-step-node and a
sixth as the default in schema-driven-fields. That is how one mistake came to be
wrong in six places at once, so this is one exported helper rather than six
corrected copies.

Independent of the optional-group work it was found during, and worth having on
its own.

579 frontend tests green; the assertion fails when the helper returns the raw
path again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 12:20:50 +02:00
Lucio Lelii 7ac7d08c98 Let the model parameters be set from the editor and the simulation dialog
Numeric fields inside a nested object were already collected, grouped and saved
by the schema-driven form, so most of this is what that form could not yet do.

A cleared numeric field now removes its key instead of saving 0. Number('') is 0,
so an emptied box used to persist a real zero - and on a temperature that is the
worst confusion available, because 0 is a valid and useful setting, which meant
that once a value had been given there was no way back to the provider default.
The container node carried its own copy of the same parsing and the same defect;
both now agree, and its maxIterations can no longer be cleared into a 0 its own
constraint forbids.

minimum and maximum are read from the schema and bound on the input, and the
placeholder says the range and that empty is allowed - otherwise the only way to
learn either is to save and be refused.

The simulation dialog gains the same five fields, which needed NodeSettingField
to grow a number type and NodeSettingsValues to admit numbers. That widening
rippled into three signatures that assumed string | boolean; the Angular compiler
found them, tsc --noEmit did not.

readSimulatorParameters is pure and tested rather than buried in the viewer: it
is where "the user left this empty" has to survive contact with Number('').

571 frontend tests green, and the cleared-field assertion fails when the parsing
is put back. Initial bundle 4.26 kB over budget, up from 2.88 - reported, not
raised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 11:29:32 +02:00
Lucio Lelii b2d744fe42 Make the bias experiment reachable, and say why when it is not
The Bias impact tab is empty because no experiment has ever run - zero rows in
bias_impact_report_entity - and nothing in the UI said how one is started.

The empty state now explains what a report is, says how many nodes carry a probe
that can be activated on this run, and offers the action. When something is in
the way it states that instead: a subflow cannot be a baseline, an unfinished run
cannot be compared against a rerun, a flow with no activatable probe has nothing
to measure.

"Create biased rerun" no longer returns silently when no node qualifies - a
button that does nothing and explains nothing is indistinguishable from a broken
one. It now names the reason.

The per-node measure control is shown on interactive blocks instead of hidden.
ChatInteraction cannot be replayed in isolation, so the isolated experiment is
genuinely unavailable there - but hiding the control made an annotated node look
identical to an unannotated one. It is rendered disabled, and the tooltip points
at the full-flow route that does work. That is a rule of the domain, worth
stating rather than concealing.

Splitting the node scan out of biasRerunCandidates keeps this free: the count is
computed from the flow snapshot, while only the capability check needs the
network.

506 frontend tests green; each new assertion fails when its behaviour is undone.
Initial bundle now 2.86 kB over budget, up from 2.21 kB - reported, not raised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 22:10:16 +02:00
Lucio Lelii 8659b798d5 Pulse a container while its subflow is working
In the parent's execution graph a container's own status is WAITING_FOR_SUBFLOW,
so it took none of the in-progress styling and sat there inert for minutes while
its child ran. Combined with a tree that was not refreshing, a working run was
indistinguishable from a stuck one.

The card now pulses on the same rhythm as a running node, in indigo rather than
yellow because the work is a level down: the node is not computing, it is
waiting on a child, and that child is the thing worth opening. A badge names the
iteration in flight - __containerIterationIndex is new on the graph payload - so
the signal says what is happening and not merely that something is.

An errored container does not pulse: there the error is what matters.

Motion is now suppressed under prefers-reduced-motion, with the pulses settling
on their emphasised frame so the state survives without the animation. That
covers the two pre-existing pulses as well - adding a third blinking element is
the point where honouring the setting stops being optional.

Budgets: task-step-node.css went 122 bytes over, so the badge icon lost its spin
(the border pulse already carries "in progress"). The initial bundle is still
2.21 kB over its 1.75 MB budget, up from being just inside it - reported, not
raised.

497 frontend tests green; the new assertions fail with the class binding removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 16:35:33 +02:00
Lucio Lelii 0b706ea95c Group the flows list by project, with shared context and project runs
Projects group the flow list in place: no new page or tab, the existing sidebar
list gains collapsible per-project sections plus a project filter. Everything is
behind PROJECTS_ENABLED in shared/feature-flags, and with no projects the list
renders exactly as it did before - flows-list.spec guards that.

FlowsList stays the single owner of the load, filter, sort and list state; the
grouping is extracted into a pure flow-grouping.ts and a presentational
flows-group component. A wrapper rendering N flows lists would have re-registered
the same list state and re-triggered the load N times. The group chrome mirrors
tasks-executions-list, which already implements collapsible groups, so the two
sidebars read as one product.

toFlowCreateRequest deliberately still carries no project. It builds the
full-replace PUT the editor issues on every save, so a project sent there would
be silently dropped each time; membership changes only through
assignFlowToProject. flow-mapper.spec guards it.

Deleting a project destroys its flows, so it gets its own dialog rather than a
wider ConfirmDialogService: it names the count, lists the flows, says that
finalized flows go too - which the flow list otherwise forbids - and requires the
project name to be typed. Widening the shared confirm service for one destructive
caller would have rippled through every other call site.

Moving a flow between projects is a menu item, not drag-and-drop: the sidebar is
320px with its own scroll, and the whole card is already a click target, so a
drag gesture would fight the open-flow gesture. Flow order inside a project uses
up/down arrows for the same reason.

Shared context is edited in a dialog modelled on the Global Inputs panel, which
is the mental model users already have for ${{global.x}}; the title toolbar shows
the inherited values read-only, because that is where prompts are written.

"Run project" creates and starts a run - creating alone would look like nothing
happened. Runs come back BLOCKED or STOPPED when a step needs inputs or failed,
and the UI says so instead of claiming progress. Project runs surface in /tasks
as sibling groups labelled with a project chip, derived in tasks-executor: no
second nesting level and no backend change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 07:53:42 +02:00
Lucio Lelii d05a661473 feat: update flow editor enhancements 2026-09-01 15:53:39 +02:00
Lucio Lelii 4d92cbc51c feat(execution): refine tree and subflow layouts 2026-08-03 17:21:52 +02:00
Lucio Lelii 14f78b79ce fix(editor): render container subflows and long port labels 2026-08-03 17:21:28 +02:00
Lucio Lelii 31e2700c5a feat: refine subflow editing navigation and zoom 2026-07-27 17:48:51 +02:00
Lucio Lelii 760202090d feat: edit container subflows from flow editor 2026-07-27 17:07:46 +02:00
Lucio Lelii 1e5a6e78ca feat(task-execution): resolve ${{...}} placeholders in human interaction text
question/actionDescription arrive with literal ${{name}} / ${{global.name}}
placeholders since no block type ever persists a "resolved" prompt. Resolves
them client-side in a single pass (not sequential replaces, to avoid
re-resolving a value that itself contains ${{...}}), then renders each
value as its own expandable card or collapsed accordion instead of
concatenating long/array values inline into the surrounding text.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 21:07:54 +02:00
Lucio Lelii 4886bf828a feat(task-execution): support human decision contracts 2026-07-23 11:30:32 +02:00
Lucio Lelii a6fef8c207 style(task-execution): highlight clickable bias badge 2026-07-23 11:22:02 +02:00
Lucio Lelii c67163faaa feat(task-execution): show bias annotation details 2026-07-23 11:21:34 +02:00
Lucio Lelii f633493bd6 fix(task-execution): resolve bias badge from catalog 2026-07-23 11:09:58 +02:00
Lucio Lelii cc1f773c2d fix(task-execution): render execution graph topology 2026-07-23 10:48:06 +02:00
Lucio Lelii 318531111d feat(flow-editor): support node capabilities 2026-07-23 10:37:28 +02:00
Lucio Lelii c031eb8535 fix(flow-editor): render dynamic ports and park swimlanes 2026-07-22 17:39:03 +02:00
Lucio Lelii 0f155e2040 feat(flow-editor): add swimlanes and JSON IO type support
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>
2026-07-22 14:54:43 +02:00
Lucio Lelii 9f54896545 feat(bias-impact): extend bias annotation support to containers
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>
2026-07-22 09:59:46 +02:00
Lucio Lelii 761535acdc fix(generic-node): align delete-confirm colors with the shared danger palette
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>
2026-07-22 09:25:03 +02:00
Lucio Lelii 46976b1f92 fix(nodes): fix the same backdrop containing-block bug in the shared .llm-modal popup
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>
2026-07-22 09:20:08 +02:00
Lucio Lelii af246dab65 fix: correct real bugs and remove dead code found in a full-project quality review
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>
2026-07-21 14:27:34 +02:00
Lucio Lelii 94ce814a6f feat(bias-impact): complete side-effect policy, compare, report list and canvas highlighting
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>
2026-07-21 11:20:57 +02:00
Lucio Lelii 7bfef72f79 feat: add bias impact experiments and biased reruns 2026-07-21 10:34:22 +02:00
Lucio Lelii 0d60da1874 feat(flow-editor): support bias annotations 2026-07-20 16:22:55 +02:00
Lucio Lelii a258064eb5 feat: add execution groups and schema-driven flow fields 2026-07-20 16:22:50 +02:00
Lucio Lelii 4c4b57781f feat: Add subflow export functionality with source name tracking
- Implement export button in subflow preview dialog
- Add sourceName parameter to track flow origin
- Add deriveSourceName() utility to normalize source names
- Improve dialog layout with flexbox for action buttons
- Fix confirm dialog z-index (10000)
- Add exporting state signal for export button UI
2026-05-20 17:13:18 +02:00
Lucio Lelii 0c64850b7d fix: preserve expanded node mode on boolean toggles 2026-05-18 12:49:31 +02:00
Lucio Lelii 877844fca0 feat(execution): add intermediate inputs tab and preview modal 2026-05-13 12:18:26 +02:00
Lucio Lelii 4bcb9f5d3e feat(editor): add focus mode for container and generic nodes 2026-05-13 12:18:13 +02:00
Lucio Lelii 9b0e2bbd89 Update nodes: schema-driven fields, node utility, generic/container/task-step nodes 2026-04-24 12:33:21 +02:00
Lucio Lelii 6611bbe752 Hide empty task-node parameter sections 2026-04-24 12:18:55 +02:00
Lucio Lelii c87596fea1 Strengthen frontend spec coverage 2026-04-21 12:39:33 +02:00
Lucio Lelii a090285bf7 Support reduced block and container catalogs 2026-04-21 12:38:35 +02:00
Lucio Lelii 5220cee49b Consolidate schema-driven node logic and align tests with Vitest 2026-04-21 11:57:01 +02:00