Every list parameter of a node now reads as its name and a count, and
opens on demand to its items; with none there is nothing to open, the
add button is the way in, and the "No items" line is gone. An item
added opens the list, so it is there to see.
The list is one shared component, app-node-list-field, rather than two
near-identical blocks in the block node and a third in the execution
view: the same list reads and behaves the same everywhere, and a
container that gains a list parameter takes the component as it is.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
In an execution, a step on its second round or later carries a Round N
badge. Answers to a person's question - decisions, evaluations, evidence,
revealing the reference - send the round of its loop the question was
opened on, fixed when the dialog opened rather than read at submit time,
so an answer to an old round's question is refused by the server instead
of being applied to a draft the person never saw.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The interaction dialog knew three shapes: a chat, a free-text answer and
a decision. A HumanEvaluation node needs a fourth - the target to open,
the steps to follow, and one control per criterion - so it gets its own
component beside the others rather than another branch inside one.
The verdict goes to the server whole. Sent a criterion at a time, a
failure partway through would leave the step holding a judgement nobody
made.
While a node hides its reference verdict, that input is dropped from the
inputs the form shows: hiding it in the component alone would leave it
sitting in the panel above. Asking to see it is a deliberate, recorded
act, and it does not come back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The control was derived from the field being optional, which put it on nearly
every field in every dialog. It now comes from the schema's
x-ui-defaults-when-empty, which the server sets per field: "you may leave this
blank" and "leaving this blank means something specific" are different claims,
and only the second is worth a control.
Two rules still sit above the declaration. A field the current state requires
offers nothing - clearing it would go towards a default that does not exist - and
a checkbox is excluded, because false is a value, not an absence.
Sampling parameters a provider does not apply are no longer offered either. All
five were shown to every provider and the unsupported ones were dropped at run
time, reported in a warning on an execution that had already happened. The filter
is shared by the two editing surfaces so they cannot drift, and it keeps every
knob whenever the answer is not knowable - no provider chosen, one the server does
not list, a capability call that failed - because a knob wrongly hidden cannot be
set at all, which is worse than one that does nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mechanical: three comment lines at the top of every .ts, .html and .css
under src, and nothing else. Split out from the licence commit so the
files that carry an actual change stay readable in the history, and kept
to its own commit because it moves the blame line on 390 files.
The short SPDX form rather than the full GNU notice - it is
machine-readable under REUSE, it satisfies the requirement to keep the
licence notice intact, and it points at LICENSE-ADDENDUM instead of
restating the attribution term in every file.
The template and stylesheet headers do not reach the bundle: Angular
discards template comments and the production build strips CSS ones. The
one in index.html survives, since that file is served as written, which is
no loss.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>