Commit Graph

47 Commits

Author SHA1 Message Date
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 96c3679f2b Report a failed global save once, at the top of the panel
The globals go in a single request, so a failure belongs to the batch and not to
any field in it. Repeating the same text on every input said one thing N times
and implied N separate problems.

One message now sits above the fields, and it can say what the per-field version
could not: the request was all-or-nothing, so none of the values were saved. It
clears on the next edit, so a stale notice cannot outlive the state it described.

Node inputs keep their per-input errors: each is its own request, so a failure
there is attributable and partial success is real. File uploads likewise.

489 frontend tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 16:11:41 +02:00
Lucio Lelii fee24f174b Save the edited global inputs in one request
The Save bar fired a request per edited input, all at once, and they all mutated
the same execution. That is how two typed values went missing: the bulk global
endpoint replaced the whole set of globals, so the list input's save landed last
and took its neighbours down with it. The endpoint now merges (service-side fix),
but a save should not depend on request ordering to be correct.

Every edited global now goes in a single PUT /executions/{id}/globals, and the
node inputs follow one at a time - there is no bulk endpoint per step, so the
best available is not to have them in flight together. The single-input save
uses the same two requests, a global batch of exactly one, so there is one code
path and one value normalisation instead of a second copy that could drift.

planInputSaves and preparedInputValue are pure and live with the other viewer
utils, which is what made them testable: the component has no spec harness (14
injected services), and the parts worth pinning are which endpoint gets called
and what shape the value takes.

The fake now writes globals to context.globalInputs and the descriptors, where
the viewer actually reads them. The older single-key fakes only ever touched
context.inputs, so a saved global never showed up in development at all.

A failed batch reports the same error on every input in it: it failed as a
batch, and guessing a culprit would be worse than saying so.

487 frontend tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 16:07:33 +02:00
Lucio Lelii 253e8ae483 Count node inputs too, and name the groups after the domain
The tally said "0 of 3" while ignoring the manual inputs below it, which are just
as required: the backend only reaches READY when every step is ready, so a step
missing its manual input blocks the start exactly as an unsatisfied global does.
It now counts the whole panel.

"Provided" is decided per input and passed down, rather than inferred in the
panel from a globals-only list. A global uses the backend's own
missingGlobalInputKeys; a node input is judged on its stored value, ignoring
unsaved edits - otherwise typing would make an input look satisfied before it was
sent. An empty list, or a list of blanks, does not count as supplied.

"Flow inputs" and "Manual inputs" are now "Global inputs" and "Node inputs",
which is what they are called everywhere else in the codebase and in the API.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 14:17:31 +02:00
Lucio Lelii dcf3af5551 Make filling in execution inputs one form, not a stack of cards
Each input opened with a centred gradient badge whose largest text read "Flow" -
the same word on every global input, so the most visual weight carried the least
information - followed by a "Type:" line and a four-row textarea. A one-word
positionTitle got the same box as a CV, and three short answers filled the panel.

An input is now one line of chrome - name, a small type chip, and a dot saying
whether it is still required - over a field that starts small. Removing an item
from a list is an icon rather than a full-width "Remove" button, which at this
width used to push the field out of the panel.

Saving is one action for the panel. Per-input buttons meant a click and a round
trip each, with nothing to say how much was still unsaved; a sticky bar now
reports the pending count and saves them together. Each one still goes through
the same single-input request, so a failure is still reported against its own
input.

The panel also stops ignoring missingGlobalInputKeys, which the backend has been
sending all along: it now shows "2 of 5 provided" and marks exactly the inputs
that block the start.

The editor is one template instead of four near-identical copies - global and
node, each single and multiple. That was not the goal here, but writing the same
change four times is how those four drifted apart in the first place. First tests
for this component.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 14:13:09 +02:00
Lucio Lelii 0fe5a0109c Read the bias direction from the fields the API actually sends
The direction badge always said "Bias". It derived the direction from
biasExecutionContext.activeBiasProbes, and that field is not on the bias context
at all - probes belong to a step view - so the lookup was always undefined and
every variant fell through to the bias branch. My own change one commit ago.

A persisted snapshot settles what the payload really is: bias and mitigation are
tracked in four separate collections - annotations per node and activated
subflows per container, one pair for each direction. The direction is which of
them are non-empty, so a variant can now correctly read as Bias, Mitigation, or
Bias + mitigation, including when an intervention was switched on for a whole
container subflow rather than per annotation. A variant with nothing recorded
says "unspecified" instead of guessing.

The model was wrong in a second way, with a second victim:
activeAnnotationIdsByNode is not sent either, so the bias highlighting on graph
nodes read an absent field and never lit anything up. It now combines both
directions.

The model is corrected to the real shape and the two absent fields are gone from
it, so nothing can quietly read undefined again. The derivation lives in pure,
tested helpers rather than inline, and the dev fake now splits activations by
direction like the real payload - flattening them hid this very difference in
development.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 11:40:33 +02:00
Lucio Lelii ad8a65fb18 Make a bias variant recognisable, and quieten the run history
Nothing in the run history said whether a run was a bias variant. Worse, the
viewer header claimed "Bias variant" for *every* execution: it tested for the
presence of biasExecutionContext, which the backend sets on all of them,
defaulting to NORMAL. Only the mode distinguishes them. The same faulty test also
offered the bias comparison action on any plain rerun.

Each history row now carries a kind badge - Run, Rerun, Bias, Mitigation, or Bias
+ mitigation - coloured and with a tooltip saying what it means for the result. A
bias variant reads as a variant even when it is also a rerun, because carrying
probes is what changes how its output should be read.

The rows also stop showing raw uuids. A rerun names the run it came from by
number ("from #1") instead of repeating a 36-character id, the execution id is
gone from the row entirely - the viewer header owns it - and the group header no
longer prints the source flow id. "1 runs" reads "1 run".

The viewer header keeps only what changes how a result should be read: Simulated,
the bias variant, Subflow. Execution id, simulator descriptor, experiment id,
baseline and probe internals moved behind a Details toggle, closed by default.
Two Italian strings in that block are now English, like the rest of the app.

Cards are flat here too, matching the flows list: no gradients, no lift on hover,
a left accent for the selected run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 11:34:36 +02:00
Lucio Lelii f086064e61 Make the outcomes section collapsible
An outcome payload is often a generated document - the local example is a full
rejection email - which pushed the execution graph down the page. The section now
collapses.

It starts open, because this is the flow's answer and it was invisible until a
moment ago. Collapsed, the header keeps the outcome codes, so the conclusion is
never hidden entirely: only the payload folds away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 11:29:35 +02:00
Lucio Lelii 0dcdc6d135 Show a run's outcomes, where an End node puts the flow's answer
A flow result is built only from *unconnected* outputs, so wiring the last block
into an End node moves its value out of the result and into the outcome payload -
which nothing in the UI read. The run then looked like it produced nothing at all.
There is already an execution in a local database whose outcome payload is a full
generated rejection email that was invisible for exactly this reason.

The run view now has an Outcomes section above the graph, listing each End the run
passed through: its code, its label, the step it came from, and its payload. An
End reached with no value says so, rather than showing an empty box - the two
cases mean different things.

A text payload renders as text rather than through the JSON tree. The tree would
have kept the line breaks but wraps strings in quotes, and the common case here is
a generated document, not a data structure.

This is the smallest of the options for the underlying gap: the data was already
persisted and already in the API payload, so nothing in the engine changed. The
gap itself remains - End cannot be attached as a pure ordering dependency
(canDependOnOtherNodes is false), so a single-exit flow still has to choose
between a labelled end and a value in `result`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 11:21: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 abf21b1c24 Add fullscreen mode, wider zoom-out, and draggable execution graph positions
- Lower rete editor min zoom (0.35 -> 0.1) so wide flows fit on screen.
- Remove the Export action from the read-only subflow preview dialog,
  since it's only ever opened from the execution view.
- Add a fullscreen toggle to the flow editor and task execution viewer,
  placed next to the title; disables the "flows" sidebar tab and hides
  the right assistant/validation panel while the flow editor is
  fullscreen, and collapses the left sidebar if it was open.
- Allow dragging nodes in the read-only execution graph and persist
  custom positions client-side in sessionStorage, keyed by execution id,
  restored on reload/refresh.
2026-09-02 20:00:34 +02:00
Lucio Lelii c7cefaca96 feat(execution): gate execution start on vault credentials
An execution cannot start until every requiredAuthorizations entry is
satisfied. A key of the form LLMProvider::<provider>::authorization is
answered with a vault secret id chosen from a picker filtered by that
provider, or from a credential created on the spot; anything else keeps
the literal-value panel it had.

A provider key never falls through to the literal-value panel, whatever
the provider catalog says or fails to say, so the API key itself can no
longer be pasted as the authorization value. The catalog is therefore no
longer part of the gate: an outstanding requirement blocks the start on
its own, and a failed catalog read is a notice with a retry.

The authorizations PUT answers with the recomputed execution, so its
response replaces the execution in the store rather than triggering a
list refresh, and the backend's 400 message reaches the user. A settled
requirement stays on screen with the credential label and a change
action, and a banner above the graph names the missing providers when
the context aside is collapsed.

The gate itself is now pure - buildAuthorizationGate and
isExecutionStartable in execution-viewer.utils.ts - and covered by
tests, since the component cannot be instantiated under the test
environment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 23:23:14 +02:00
Lucio Lelii d05a661473 feat: update flow editor enhancements 2026-09-01 15:53:39 +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 8faee0fbaf feat: support interactive container subflows 2026-07-24 07:22:38 +02:00
Lucio Lelii 4886bf828a feat(task-execution): support human decision contracts 2026-07-23 11:30:32 +02:00
Lucio Lelii cc1f773c2d fix(task-execution): render execution graph topology 2026-07-23 10:48:06 +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 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 877844fca0 feat(execution): add intermediate inputs tab and preview modal 2026-05-13 12:18:26 +02:00
Lucio Lelii 5f405b69a0 solved problem on container import 2026-04-10 10:08:17 +02:00
Lucio Lelii de6e86d53a refactor: code quality improvements (tasks 1-15)
- 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
2026-04-09 14:53:02 +02:00
Lucio Lelii 7028ac0c53 feat: improve auth flow and task execution UX 2026-04-09 14:01:56 +02:00
Lucio Lelii b10e9c59e0 Expand array outputs in execution viewer 2026-03-27 16:14:38 +01:00
Lucio Lelii 27f9bc1285 Add dependency flow validation and execution input UX updates 2026-03-27 15:18:03 +01:00
Lucio Lelii 167e301a21 Refine task previews and add node clone actions 2026-03-25 16:53:05 +01:00
Lucio Lelii a2a4f699c7 look and feel modified in node outputs 2026-03-24 14:48:21 +01:00
Lucio Lelii 3bf669180d look and feel in the longtext parmeter changed 2026-03-24 14:05:21 +01:00
Lucio Lelii 55418d2226 Refine execution viewer and schema-driven node handling 2026-03-24 13:45:32 +01:00
Lucio Lelii fad93a3eca Add suspended execution resume support and production Docker setup 2026-03-22 10:35:30 +01:00
Lucio Lelii 447b01786d Refine execution chat modal and add cancel support 2026-03-20 12:54:05 +01:00
Lucio Lelii 19b0dadacc Improve schema-driven nodes and interactive execution chat 2026-03-19 15:18:09 +01:00
Lucio Lelii 2a42817ef3 Support containers in task execution views 2026-03-16 16:50:44 +01:00
Lucio Lelii 25e133ac04 Add first-class container nodes and subflow preview 2026-03-14 15:50:55 +01:00
Lucio Lelii c0d2f75285 Support typed execution inputs and task node IO labels 2026-03-13 16:51:47 +01:00
Lucio Lelii b24b8177a9 Support dynamic array schemas in node editors 2026-03-12 17:22:23 +01:00
Lucio Lelii d6e3a2a534 Migrate core UI areas to Angular Material 2026-03-12 15:30:31 +01:00
Lucio Lelii dc45a5581b Improve execution input and output presentation 2026-03-11 17:11:37 +01:00
Lucio Lelii f9a2504273 Add execution authorization input flow and inline node deletion UI 2026-03-11 16:57:14 +01:00
Lucio Lelii 514339599b Extend execution graph and schema-driven node rendering 2026-03-10 16:59:35 +01:00
Lucio Lelii d0ca908b02 Format execution durations with adaptive units 2026-03-09 17:06:48 +01:00
Lucio Lelii 1fef10ab17 task input section added 2026-03-06 09:41:30 +01:00
Lucio Lelii b639133401 updated 2026-03-05 16:03:09 +01:00
Lucio Lelii 1c5184892d moved task in a service 2026-03-05 14:58:24 +01:00
Lucio Lelii 36167f13ac added task step part and make nodes generic 2026-03-04 16:55:14 +01:00
Lucio Lelii 4504ec0087 added task layout 2026-03-03 20:01:03 +01:00