Commit Graph

258 Commits

Author SHA1 Message Date
Lucio Lelii 79ae012faa Keep all of docs/ local
The user guide files already committed stay tracked; anything new in
docs/ has to be added on purpose.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 19:04:04 +02:00
Lucio Lelii da269130f6 Ask a secure retriever whether a field is open beside its items
A secure retriever's values are at .../{parameter}/items and its
questions - is the field required, does it take a typed value - at
.../{parameter}/required and /open beside them. The question URL was
made by suffixing the values URL, so for every secure retriever it
pointed under items, where nothing answers: choosing a global input as
a file's source showed an error contacting the server.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 19:04:04 +02:00
Lucio Lelii f2ade91000 Draw a loop's way back above every node between its ends
It cleared the two nodes it connects and nothing else, so when the
loop's own steps sat higher - the usual case - its top ran hidden behind
them, limit label included. It now clears every node between its ends,
and follows any of them being moved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:57:56 +02:00
Lucio Lelii 32b62d10a2 guides moved in his own folder 2026-09-25 14:46:16 +02:00
Lucio Lelii 7459b1e70b Publish the user guide as PDF and DOCX, and keep the rest of docs/ local
docs/ now holds only the user guide in the repository - Markdown, PDF and
the new DOCX. The working notes, the scripts that build the guide and its
screenshots are ignored; render-user-guide.py, tracked until now, leaves
the repository but stays on disk.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:44:53 +02:00
Lucio Lelii 7c30ba1a38 Give the Rete plugins one rete in tests, and stop an abandoned editor load
Five unhandled rejections came with every test run, from the pages that
render a real graph. In tests, rete-angular-plugin was loaded by Node and
got rete's CommonJS build while everything else got its ES module, so
the renderer was not a Scope of the rete it was plugged into. Vitest now
processes the rete packages itself, and all of them share one rete.

With the graph actually built, a real bug showed: an editor still loading
when its component was destroyed went on mounting nodes with the
component's destroyed injector. A load now stops once its component is
destroyed or a newer load begins - between nodes and connections, and at
every render, which is dropped for an abandoned editor.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:42:26 +02:00
Lucio Lelii 7477027319 Put a copy button next to every value, not only in the full view
The full view opens only when a preview leaves something out, so a short
output or input had no way to be copied. Every value in the Output,
Intermediate and Rounds tabs now has its own copy button, beside the
view button when there is one. The full view's button just says Copy:
it copies any value, JSON or not.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:35:08 +02:00
Lucio Lelii 90a04dc480 Let a value be copied from its full view and from the outcome
The full view of an output or input gains a Copy button - Copy JSON for
a JSON value - and the outcome payload a copy button in its corner, which
stays put while a long payload scrolls. JSON is copied indented, whether
it came as an object or as a model's text in a fence, so it pastes as
clean JSON elsewhere; text is copied as shown.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:32:19 +02:00
Lucio Lelii 6134590bd8 Keep the JSON full view still while its tree is opened and closed
Sized to its content and centred on the page, the modal grew and shrank
with every node opened or closed, its top edge jumping each time. Showing
a JSON tree it now has a fixed height, and only its content scrolls.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:29:31 +02:00
Lucio Lelii 3ffaf6e596 Show JSON values as trees, and keep a read-only graph's connections unselectable
Outputs, intermediate inputs, loop rounds and outcome payloads that are
JSON - objects, arrays, or JSON a model wrote as text, fenced or not -
are shown with the JSON tree instead of their text. A preview opens the
first level and a few entries, summing up the rest; the full view opens
the whole tree. The JSON viewer gains the depth and entry limits this
takes.

In a read-only graph, such as an execution's, connections can no longer
be picked or shown as selected: there is nothing to do with one there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:14:20 +02:00
Lucio Lelii 022df1909a Add a Rounds tab showing each loop of an execution round by round
For an execution with a loop, the side panel gains a Rounds tab: each
loop, which round it is on out of how many, and every round with what
its steps were given - the draft sent for review, the verdict handed on
- and how it ended: went round again, left the loop, or stopped at the
limit. Earlier rounds come from the execution's step history, the last
from its steps as they stand.

From the second round of a loop on, a step's inputs are shown as they
are now rather than as prepared at the start, which is only the first
round's value. This also corrects the Intermediate tab and the inputs
shown on a node in a loop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 14:03:28 +02:00
Lucio Lelii 46f40fe9be Put a loop's delete button in its label, not between its ends
Every connection puts its × at the midpoint of its two ends, which is on
the curve for an ordinary one. A way back is drawn above the nodes, so
that midpoint landed on whatever connection ran between them - it looked
like that connection's ×, still showing after another was selected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 13:04:54 +02:00
Lucio Lelii 0c129a5f8d Draw a loop's way back above its nodes, clear of how tall they grow
Below the ports, the way back ran through any node taller than a fixed
drop - most of them, once expanded - and its limit label sat hidden
behind the node. It now runs above the tops of the nodes at both ends,
which move only when a node is dragged, and follows them when one is.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 12:58:58 +02:00
Lucio Lelii f2378216ee Show which round a loop step is on, and answer for the round seen
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>
2026-09-25 11:41:13 +02:00
Lucio Lelii fbd27c4344 Draw a loop's way back, and let its limit be set on it
A connection from a router back to where a loop starts is picked out
with the same rule the server uses, drawn underneath the nodes in violet
with its iteration limit, and edited in place when selected. The limit
travels on the connection as its loop settings.

An input used to hold one connection, and Rete dropped the old one before
asking. The entry of a loop takes two - from before the loop, and back
from the router - so inputs now accept several and the editor itself
replaces the old one, except when exactly one of the two leads back.

Connections keep their saved id across loads instead of getting a new one
each time, so settings and selections stay attached. Node capabilities
now carry routesExclusively through to the client.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 11:38:39 +02:00
Lucio Lelii f9491e0904 Add package license to lockfile 2026-09-23 12:12:00 +02:00
Lucio Lelii 1270bf7ea7 Improve execution group management 2026-09-23 12:09:57 +02:00
Lucio Lelii def50c229b Merge branch feature/human-evaluation-node into main 2026-09-22 16:08:33 +02:00
Lucio Lelii 3755081e0f Add a live activity panel to the admin area
Answers one question - is it safe to restart the server - so it answers that
first: a single verdict line, then the two lists behind it. A run in flight
outranks a connected user however many there are, because a person loses an
unsaved edit and comes back while a run is simply gone.

Refreshes itself every fifteen seconds, the page being worthless the moment it
is stale. A failed refresh keeps the last known answer rather than emptying the
page: blank would read as "nobody is connected", the one wrong conclusion
somebody could restart the server on.

PresenceService sends a heartbeat a minute, and only while the tab is visible.
Every authenticated request already counts as a sign of life, but the editor is
quiet - someone reading a flow or typing a prompt makes none for minutes, and
they are the person a restart interrupts worst. A backgrounded tab is not
someone using the application, and counting one would turn the panel into a list
of forgotten tabs, which is the failure the server's own session column already
has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 10:11:39 +02:00
Lucio Lelii 3d4b49e030 Merge branch 'main' into feature/human-evaluation-node 2026-09-21 15:06:52 +02:00
Lucio Lelii fd30de2b1d Point the web interface at its home on the ISTI Gitea
Seven references followed the move: the licence addendum, the NOTICE, the README
attribution, the citation metadata, the package repository field and the two links
the About dialog shows a user - one of which names the backend service, so it moves
to the service's new home too.

The working notes that used to sit in docs/ are gone from the repository and named
in .gitignore, together with .claude. What remains under docs/ is the user guide,
which is written for whoever runs the application rather than for us.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 15:03:11 +02:00
Lucio Lelii 44be453cbb Summarise a plain-value array row by its value
A task script is a list of strings, and every row read back as "Item 1", "Item 2":
the summary only knew how to read object rows, so the very text being configured was
hidden behind its position. A string, number or boolean row now reads as itself, and
falls back to the position only when it is blank.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 12:15:58 +02:00
Lucio Lelii ed4dae1a39 Let the log be read backwards while it is still running
The panel scrolled itself to the newest line on every poll, so a reader
who scrolled up to follow something got yanked back five seconds later -
exactly when a log is worth reading, and exactly the moment it became
unusable.

Following now stops as soon as the reader leaves the end, and resumes on
its own when they come back to it, so the common case needs no button.
The button is there to say which of the two is happening and to override
it: a log that stops moving on its own otherwise looks like a log that
stopped.

The threshold lives in the utils with the rule, not in the component: not
zero, because a list that grew by a line between the scroll and the
handler would otherwise read as the reader having walked away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 11:55:51 +02:00
Lucio Lelii 469099dffc Read an event's details as an object, under a title that fits
The first cut put the event's own sentence in the heading and its details
in a textarea of raw JSON. A pruning warning runs to a full paragraph, so
the title wrapped five lines and read as an error page, and finding one
number in the body meant counting braces.

The event now opens in its own dialog: a two-word name from the event
type, the node and time beneath it, the sentence as prose, and the
details as the JSON tree the app already had. The name is derived, not
tabulated - a type nobody has seen yet still reads as words - with the
initialisms that would otherwise shout or look like typos spelled out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 11:24:17 +02:00
Lucio Lelii 8c3d9c6ed3 Let an event's own numbers be opened from the log
The log showed each event's sentence and threw away everything behind it.
A pruning warning saying "6332 characters over budget" could not tell you
what the total actually was, a tool call could not say which tool: those
numbers reached the browser and were dropped, leaving the API as the only
way to read them.

Rows that carry details now offer an eye, which opens them read-only.
Rows that carry none offer nothing - a button opening an empty box is
worse than no button - so the decision of what is worth opening lives in
the view model, where it can be tested, rather than in the template.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 11:07:38 +02:00
Lucio Lelii 974589aeee Render the evaluation form, and submit a judgement as one act
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>
2026-09-20 23:24:43 +02:00
Lucio Lelii b1497ab3c4 Make a project a container of flows, with no run of its own
Running every flow in a project put two different things in one list: the
sort control decided what you saw, while the up/down arrows set the order
the run would actually use. Pressing an arrow therefore rewrote the run
order from whatever the list happened to be sorted by - usually the
alphabetical one - and the row often did not move, so the arrows read as
broken.

Rather than reconcile the two, a project is now just a grouping: the play
button, the "Run project" menu entry and the reorder arrows are gone, with
the client calls behind them. The server keeps its endpoints, so bringing
the feature back is a frontend change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 21:15:58 +02:00
Lucio Lelii 82396e8b8f Drive the view-content button from the schema, not from a skills rule
The button was wired to the literal retriever name "Skills" and to a
hand-built /retriever/Skills/definitions URL, so the node knew about one
particular binding. It now reads x-retriever-definition-url off the item
property: any binding that declares where its value can be read gets the
view, and the node knows nothing about which one it is showing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 20:58:14 +02:00
Lucio Lelii 9802f12203 Show the view-skill button on ungrouped array rows too
The node draws array rows from two near-identical template blocks - one
inside a group fieldset, one for ungrouped fields - and the button was
added to only the first. An LLM node renders its Skills list through the
second, so the button never appeared where it was actually wanted.

The test that missed this only exercised the component class, so it adds
a rendering test covering both blocks, and clears the stored user between
service tests: left behind, it made a sign-in a no-op transition and the
cache-reset tests failed intermittently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 16:08:54 +02:00
Lucio Lelii 87f6e2564b Drop cached projects/flows on login or logout, not just app reload
ProjectsService and FlowsService are root singletons that load their
list once and cache it for the app's lifetime. Logout never cleared
that cache, so a second person signing in on the same tab without a
hard reload still saw whoever's projects and flows were loaded before
them - no backend request was even made.

Authorization now emits userChanged whenever the signed-in identity
actually changes (never on the initial hydration from storage), and
both services reset their cache on it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 11:29:58 +02:00
Lucio Lelii d09ec47be6 Let a skill binding's content be viewed from the node editor
Skills were selectable only by bare id, with no way to see the
instructions a SKILL.md actually carries. Adds a "view content" button
next to each skill row, backed by the already-existing
/retriever/Skills/definitions/{id} endpoint and the previewOnly dialog
mode, so no backend change or new dialog infrastructure was needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 11:29:49 +02:00
Lucio Lelii 0a2f7606d9 Merge feature/declared-field-defaults into master 2026-09-18 21:33:01 +02:00
Lucio Lelii fc541c12ef Offer "Use default" only where a field declares one
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>
2026-09-18 11:52:55 +02:00
Lucio Lelii bb5621f559 Let the simulator and bias judge dialogs supply a credential
The backend now accepts a credentialId for both the interaction simulator
and the bias judge, but nothing in the UI could provide one - a
credential-requiring provider chosen either way would always fail, short of
coincidentally reusing a credential from elsewhere in the same execution.

openLLMDescriptorSettings (the one dialog shared by both the simulator and
the bias judge picker) stays as it is, tests included. A new
openLLMDescriptorSettingsWithCredential wraps it: once a provider and model
are chosen, if LlmProviderService.listCapabilities() says that provider
requiresCredential, a second dialog lists the user's saved credentials for
it (ExecutionVaultCredentialsService.listForProvider). Two separate modals
rather than one field folded into the first, since NodeSettingsDialogService
only ever holds one dialog open at a time - stacking a "create credential"
flow inside this one was not attempted, matching the pre-existing, already
documented gap that there is nowhere in the app to manage credentials
outside of creating them.

simulateExecution() in task-execution-viewer.ts also now surfaces a
rejected simulation's error message instead of failing silently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 14:51:16 +02:00
Lucio Lelii b13c2019eb Let a retriever-backed field fall back to free text in two more places
The backend already publishes /retriever/LLM/models/open, which says
whether a provider's model field is a fixed list or free text - Gemini
has used it in production for a while, through generic-node.ts's own
leaf-field editor. Two other renderers never asked that question, and a
provider that cannot list (any hosted one with no listing endpoint) got
stuck there with an empty, unusable select instead:

  - container-node.ts's own field editor decided select-versus-text from
    retrieverKey alone, synchronously, before the free-text question could
    even be asked. toDialogFieldType now takes the answer as an argument,
    resolved first through a new fetchRetrieverFreeText.
  - Nested objects - an LLMDescriptor inside a container's configuration
    or an array item, reached through the buildSchemaObjectDialog both
    generic-node.ts and container-node.ts share - had no way to ask at
    all. SchemaObjectDialogHooks grows an optional loadFreeText hook; when
    it says yes, loadOptions is skipped entirely (the same fetch a
    listable retriever would otherwise make and throw away) and the field
    is forced to text. One fix in the shared function covers both
    components that call it.

Deliberately not "an empty options list means free text" - that heuristic
would be wrong for a retriever whose empty list is a real answer, a
project's global inputs among them, where nothing to choose from is not
the same question as nothing to type. A property with no loadFreeText
hook, or one that answers no, keeps rendering as a select, empty or not,
exactly as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 12:30:43 +02:00
Lucio Lelii e1708f95b8 Read the real provider catalog instead of hardcoding InternalOllama
Two things in the copilot both used to hinge on the same wrong check,
isInternalProvider(provider) - a plain string comparison - instead of
what the backend's /llm/providers catalog already publishes:

  - providerNeedsCredential decided whether to show the credential picker
    by name, so a credential-free provider under any name other than
    "InternalOllama" - a remote Ollama with no key, say - was told it
    needed a credential anyway. It now reads requiresCredential from the
    catalog.
  - The "Add credential" dialog had no endpoint field at all. It now
    shows one, built the same way llm-descriptor-settings.ts already
    rebuilds its model field when the provider changes: onValuesChange
    recomputes the field list from the current draft, so choosing a
    provider that requiresEndpoint adds the field, and choosing one back
    away removes it, without closing the dialog.

Both read the same LlmProviderService the execution viewer already uses,
loaded once and cached in llmProviderCapabilities. It has to be loaded
eagerly now, not only when the dialog opens: providerNeedsCredential is a
computed() signal that gates whether the configuration is valid to save,
so it cannot wait for a promise nothing has awaited yet. Bootstrap now
kicks that load off in the background; the dialog's own lazy load stays
too, in case bootstrap has not resolved by the time it opens.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 12:30:28 +02:00
Lucio Lelii a0fdfcda35 Show an endpoint field on the execution's "Add credential" dialog
The backend catalog (/llm/providers) now publishes a requiresEndpoint
flag per provider. LlmProviderCapability grows the matching field, and
task-execution-viewer.ts's credential-requirement dialog shows an
"Endpoint URL" field only when the provider being satisfied needs one -
every existing provider still gets the same three fields it always had.

VaultSecret, VaultSecretCreateRequest and VaultSecretUpdateRequest grow
an optional endpoint too, so it round-trips through createSecret and back
out of the credential list unchanged for a provider that has none.

The gate in execution-viewer.utils.ts (buildAuthorizationGate) reads the
same flag onto each VaultAuthorizationEntry, generalising the single
resolveRequiresCredential lookup it already had into
resolveProviderCapability, shared between requiresCredential and the new
requiresEndpoint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 12:30:13 +02:00
Lucio Lelii 5787a6fe44 Add the SPDX licence header to every source file
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>
2026-09-16 12:10:18 +02:00
Lucio Lelii 85e6c12372 Complete the user guide in English and render it to PDF
The guide was Italian, 531 lines, and stopped at the features that existed
when it was written: it described the editor and executions and said
nothing about bias annotations, probes, experiments or reports - the part
of the platform that most needs explaining, because none of it is
discoverable by clicking around.

Bias is now six chapters, including the thing the interface does not say
anywhere: the isolated experiment and the full-flow rerun are chosen by
which control you click, not by a toggle. Also added: projects, credentials
and the vault gate, simulated runs, reruns and comparison, the execution
tree, file uploads and the admin area.

Features that exist in the model but that nothing produces - an automated
bias analyser, a vault management page, a project-run history - are
deliberately absent. A guide that promises a screen the user cannot find is
worse than one that omits it.

The PDF is generated rather than committed by hand: no pandoc here, so
render-user-guide.py emits print-styled HTML that Playwright, already a
dependency, turns into the PDF. Its header carries the two commands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 12:10:09 +02:00
Lucio Lelii 4e1926588b Show the licence, attribution and source offer in the interface
Two obligations the licence creates had nowhere to land. The attribution
term requires the notice to stay visible in the Appropriate Legal Notices
a work displays, and section 13 owes the Corresponding Source to anyone
interacting with the program over a network - neither is discharged by a
file sitting in a repository the user of a deployment never sees.

So the About dialog is not decoration: it is the only place either
obligation is met, which is why the component says so in a comment. A
deployment that drops it stops complying with the licence it ships under,
and the attribution term would be asking of others something this
interface does not do itself.

The login page sits outside this layout, so an unauthenticated visitor
still sees none of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 12:09:58 +02:00
Lucio Lelii 33150978aa Release under the AGPL with an attribution term
The repository carried no licence at all, which meant nobody could legally
build on it and the question of what a derivative owes back had no answer.

The AGPL rather than the GPL because this is a web application: under the
GPL, running a modified copy as a hosted service obliges the operator to
publish nothing, and a workflow platform is exactly the kind of thing
someone hosts. Section 13 closes that.

The additional term is the part that carries the intent - section 7(b)
permits requiring that author attributions be preserved in the Appropriate
Legal Notices a work displays, so the attribution has to survive into a
derivative's interface and not merely into a file nobody opens. It is a
permitted additional requirement, not a further restriction, so downstream
cannot strip it.

Academic citation is a request in CITATION.cff, not a licence condition:
no OSI licence can compel a citation, and pretending otherwise would put an
unenforceable clause in a document that needs to be enforceable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 12:09:49 +02:00
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 f1305c16e7 Say that a file is stored where it is chosen, not on Save
Replacing a file uploads it straight away, while every other input waits
for the Save at the foot of the panel. Nothing said so, so a replacement
left the footer reading "All changes saved" with Save greyed out - which
is exactly what an edit that failed to register looks like.

The row now says it, and a test covers the path it describes: Replace
reopens the picker and the chosen file uploads on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 14:51:41 +02:00
Lucio Lelii b4302bf7db Mark the node inside a container that carries the error
A container's errors are reported against the container, with the node
that actually carries one in relatedNodeIds - so opening the subflow to
look for it found a canvas with nothing marked, and an error naming a
container the view no longer showed. Entering a subflow also cleared the
marks outright, and the set was only ever computed at the root, where the
errors grouped under a container are not among the flow-level ones.

The marks now follow whatever the current view reports: the container at
the root, the block that carries the error once inside. Focusing one
error from the panel still narrows them, and dropping that focus goes
back to all of them rather than to none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 14:28:54 +02:00
Lucio Lelii 3af6f14bb7 Send a global file input to the endpoint that accepts files
The upload went to /globals/{key}, which takes JSON, and came back as an
unsupported content type; the array variant additionally named its parts
after the input, which nothing binds on. Both now use the multipart routes
that exist for this, with the part names those routes read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 12:31:43 +02:00
Lucio Lelii 84f528203c Give a file input a picker that says what it takes and what went wrong
The native file control said "Choose file / No file chosen": nothing about
what the input accepts, nothing about the file once chosen, and on failure
a bare "Failed to upload file" that threw away the server's explanation -
the wrong type, a size, a name it could not build a file from.

Replace it with a drop zone that states the accepted types and size up
front, then gives way to the file itself while it uploads and once it
lands, with Replace to change it. A failed upload now names the file that
failed next to what the server actually said, and the zone invites another
try - which also needed the native control cleared, since picking the same
file twice fires no change event and picking the same file again is
exactly what a retry is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 12:27:52 +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 81f2a890f2 Validate MCP file uploads in execution UI 2026-09-11 14:50:21 +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 0aec372d73 Connect the rerun button to the handler that was waiting for it
The list emitted executionRerunRequested and the executor had a handler
for it, but the template never bound the two: pressing rerun on a
finished run emitted into nothing, not even an HTTP call. The button
stayed enabled because it only looks at the status.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 16:30:33 +02:00