Skip to content

Release 2.3.0 - #74

Merged
piotrblaszczyk merged 14 commits into
releasefrom
main
Aug 12, 2026
Merged

Release 2.3.0#74
piotrblaszczyk merged 14 commits into
releasefrom
main

Conversation

@piotrblaszczyk

Copy link
Copy Markdown
Contributor

No description provided.

librowski and others added 14 commits July 2, 2026 10:31
…#32)

* fix(execution-worker): connect to TEMPORAL_ADDRESS instead of implicit localhost

Worker.create without an explicit connection dials 127.0.0.1:7233,
ignoring the TEMPORAL_ADDRESS env var — correct in local dev, broken in
any deployment where Temporal is not on loopback.

* fix(execution-worker): treat empty-conditions decision branch as catch-all

The no_branch_matched error message and the Sales Inquiry reference
template both treat a branch with no conditions as the explicit
catch-all, but branchMatches returned false for it — any input
classified outside the keyword branches failed the whole run.

Supersedes the empty-conditions bullet of
packages/execution-core/decision-no-match.decision-log.md (the strict
fail-fast core of that decision is unchanged); see
apps/execution-worker/decision-catch-all.decision-log.md.

* feat(backend): add per-ip rate limit on the execute route

Fixed-window, in-memory limiter (WB-229 abuse gate). Disabled unless
RATE_LIMIT_EXECUTE_PER_MINUTE / RATE_LIMIT_EXECUTE_PER_DAY are set, so
local dev is unaffected. TRUST_PROXY=true reads the client IP from
X-Forwarded-For — only enable behind a proxy that sets it.

* feat(deploy): containerize ai studio stack for production deployment

deploy/ai-studio/: multi-target Dockerfile (runtime/migrate/web),
production docker-compose (only nginx public, pinned images, automatic
migrations), nginx SPA+API proxy with SSE tuning and per-request DNS
re-resolution, .env.example with Mistral Small 3.2 default, DevOps
README, and a decision log covering the architecture choices.

tsx becomes a real dependency of backend and worker (start:prod runs
without an .env file); .dockerignore now keeps **/.env out of build
contexts.

Verified end-to-end: Sales Inquiry Pipeline to execution_completed with
live SSE through nginx; rate limiter returns 429 past the budget.

* feat(deploy): add swarm overlay aligned with workflow-builder infra

tools/deployment/ mirrors the workflow-builder repo's deployment path
(build-docker.sh, deploy.sh, ansible deploy-application playbook) and
consumes the same three images from deploy/ai-studio/Dockerfile — only
the orchestration layer differs. Deviations forced by AI Studio being
stateful: node-pinned volumes for Postgres/Temporal, post-deploy
migration step (Swarm ignores depends_on), attachable internal network
with short DNS aliases, and an AUTH_ENABLED-gated gatekeeper so the
public demo stays login-free.

Stack template render-verified in both auth modes; status 'Proposed'
pending the DevOps conversation.

* feat(backend): apply drizzle migrations on boot

drizzle-orm's programmatic migrator runs the SQL files from
apps/backend/drizzle/ before the server accepts traffic. A failure
(database still starting) exits the process; container restart policies
retry until it converges. drizzle-kit stays a devDependency — db:migrate
remains available for out-of-band use.

* refactor(deploy): drop the migrate service and image

The backend migrates itself at boot, so the migrate Dockerfile target,
compose service, and the Swarm playbook's post-deploy migration task
(plus its attachable-network requirement) all go away. Two images
remain: runtime and web. The worker now waits for the backend
healthcheck so it never touches a pre-migration schema.

Verified on a wiped stack: virgin database boots, backend logs
'database migrations applied' before listening, Sales Inquiry Pipeline
runs to execution_completed over live SSE, rate limiter returns 429
past the budget.

* style: trim comments to the non-obvious

Keep only what the code cannot say itself: traps (Worker.create's
implicit localhost, corepack/pnpm 10 failure, offline mode leaking into
lifecycle scripts), constraints (single-replica migrator, X-Forwarded-For
trust), and magic values. Drop the narration.

* revert(execution-worker): move the decision catch-all fix to its own pr

Reverts the empty-conditions-as-catch-all change (ccf7375) and its
decision log. It changes execution semantics and supersedes a clause of
decision-no-match.decision-log.md — that deserves a focused review, not
a ride-along in a deployment PR.

* docs(deploy): drop the deployment decision log

AI Studio is a POC — the README and the comments on the non-obvious
pieces carry what operators need; full architecture rationale is
premature at this stage.

* fix(deploy): add healthcheck, restart policies, and overlay driver to swarm stack

Backend healthcheck (fetch /api/health) lets Swarm detect when
migrations are done — without it the worker can hit a pre-migration
schema. Explicit restart_policy on every service replaces the implicit
Swarm default; crash-looping services (worker, temporal) get
max_attempts. Internal network gets driver: overlay for clarity.

* Add actions code

* Remove old deployment code

---------

Co-authored-by: Jan Librowski <jan.librowski@synergycodes.com>
Co-authored-by: Jakub Kubacki <jakub.kubacki@synergycodes.com>
…ocales (#58)

The selector matched the current option against the raw i18n.language, so a
regional locale (e.g. pl-PL) - which equals neither 'en' nor 'pl' - fell back
to the first option and displayed 'EN' while the UI rendered Polish. Match on
i18n.resolvedLanguage (base language) instead. Adds a regression spec.

Co-authored-by: Jan Librowski <jan.librowski@synergycodes.com>
* fix(sdk): drop the decision node's outer output port when branches exist

Each branch renders its own source handle, so the node-level source port was
a redundant, disconnected port on any decision node with branches. Branchless
decision nodes keep it (existing diagrams route edges through it).

* feat(sdk): forward maxRows on the TextArea uischema control

overflow-ui's TextArea already supports it; the uischema type and control
only exposed minRows, so consumers had to cap growth with CSS max-height.

* fix(sdk): space out branch cards in the decision properties panel

DecisionBranchesControl wrapped its cards and the add-branch button in an
unstyled div, so they rendered glued together. Style the wrapper as a column
with a gap, following the component-token pattern.

* refactor(sdk): drop the accordion's classless-div layout hack

The accordion content styled bare 'div:not([class])' children to compensate
for controls with unstyled roots. DecisionBranches - the only such control -
now styles its own wrapper with the same 0.75rem gap, so the selector had no
remaining in-repo target and only masked missing control styling.

* fix(sdk): remove the decision node's source handle entirely

Execution routes only through branch handles - the decision executor sets
nextPort to the matched branch or fails with no_branch_matched, so an edge
from the bare source handle can never fire. Keeping it for branchless nodes
only invited connections that silently dead-end; the demo's canned decision
nodes are unaffected (they render as plain nodes, not this template).

* docs: changesets are 1-2 sentence changelog entries

Codify the rule in CLAUDE.md and tighten this branch's changesets (plus the
verbose language-selector one already on main) to match: the consumer-visible
change and any migration note only - rationale lives in commits and PRs.

---------

Co-authored-by: Jan Librowski <jan.librowski@synergycodes.com>
The app bar hardcoded the Workflow Builder logo with no way to replace or
link it, forcing consumers into hash-prefix CSS hides and fixed-position
overlays. The props follow the existing render-body config-holder pattern.

Co-authored-by: Jan Librowski <jan.librowski@synergycodes.com>
…search, richer templates (#48)

* feat(ai-studio): auto-load flagship template and add first-visit disclaimer

Replace the internal Synergy sales-inquiry template with a generic, relatable
Customer Support Triage flagship (classify -> route by type -> specialist draft
-> QA), built on the existing trigger/agent/decision nodes.

Auto-load it on first visit via initialNodes/initialEdges on WorkflowBuilder.Root
so visitors land on a runnable workflow instead of a blank canvas; a returning
visitor's saved diagram still wins. Add a dismissible first-visit disclaimer
modal stating the workflows run real OpenRouter calls, that the demo is not a
model benchmark, and that its purpose is to showcase Workflow Builder.

* chore(execution-worker): default to gemini-2.5-flash-lite for the demo

Cheaper, fast default for the public demo (roughly an order of magnitude cheaper
than claude-3.5-haiku). Quality-per-cost is what matters here - the model is the
engine, not the product. Override with the AI_MODEL env var.

* feat(backend): gate workflow execution with rate limit and Turnstile

POST /api/workflows/:id/execute is the only endpoint that spends real LLM
budget, so it gets a per-IP fixed-window rate limit plus an optional Cloudflare
Turnstile check, applied right after authorization via guardExecution(). Both
controls are no-ops when unconfigured (TURNSTILE_SECRET_KEY empty), so local dev
runs unprotected; the rate limit is active out of the box as a budget backstop.
Turnstile verification fails closed on a verifier error.

* feat(ai-studio): attach Turnstile token to workflow execution

Before the execute request, fetch a Turnstile token from an invisible widget
and send it as the cf-turnstile-token header. Degrades gracefully: with no
VITE_TURNSTILE_SITE_KEY the token is undefined, no header is sent, and the
backend skips verification - so local dev needs no keys.

* feat(ai-studio): add AI Debate, Content Repurposer and Meeting Notes templates

Grow the picker into a small gallery of relatable, runnable examples beyond the
flagship: AI Debate (parallel personas that fan out then converge on a verdict),
Content Repurposer (one post fanned out to three channel variants), and Meeting
Notes to Action Items (a linear summarize -> extract -> format chain). All use
the existing trigger/agent/decision nodes.

* refactor(ai-studio): rename markdown-preview node to visualize

Generalize the on-canvas preview node from markdown-only to a future generic
Visualizer: type ai-studio/markdown-preview -> ai-studio/visualize, palette label
Visualize (Eye icon), worker domain VisualizeNode + executeVisualize + registry,
flagship node preview-1 -> visualize-1. Rendering is still markdown for now;
renderer set, format detection, charts, diagrams, export and expand land in the
following commits. Includes the visualize plan and long-work backlog.

* feat(ai-studio): detect output format for visualize

Heuristic detectFormat(text) -> {renderer, data?, chartable?} mapping an output
string to a renderer for the Visualize node's auto mode: mermaid->diagram,
JSON->{chart for {label,value}/{type,data}, table for object arrays, stat-cards
for flat scalar objects, json for nested}, CSV->table, else markdown. Conservative
(chart/diagram only on clear signals), prose falls back to markdown. 11 unit tests.

* feat(ai-studio): add mode param to visualize node

Adds a 'Render as' select to the Visualize node: VISUALIZE_MODES = auto +
markdown/text/json/table/stat-cards/chart/diagram, default auto. Auto detects the
format; the rest force a specific renderer.

* feat(ai-studio): json/table/text/stat-card renderers for visualize

Add a renderer registry (getRenderer) with markdown, text, JSON tree (collapsible,
hand-rolled), table (JSON array / CSV rows), and stat-cards (flat object) renderers.
The visualize card now runs detectFormat (or the node's mode), picks the renderer,
shows an 'Auto > X' badge, and renders into a framed body. chart and diagram fall
back to table/text for now (real renderers land next).

* feat(ai-studio): always-on visualize card with empty state

The visualize card now renders for the node at all times with a fixed minimum
size: an empty-state placeholder ('The visualization appears here after you run
the workflow') before a run, a generating indicator while running, and the
rendered output on completion. The reveal animation moved from the card frame to
the content so it plays when the result arrives, not on every render.

* feat(ai-studio): chart renderer (recharts) for visualize

Add a lazy-loaded recharts renderer (bar/line/area/pie) for the chart mode and
chart-spec envelopes ({type, data}) or {label,value}/{x,y} arrays. recharts only
loads when a chart is shown (React.lazy + Suspense). The card shows a 'Try as
chart' chip when auto-detected data is chartable but rendered as a table.

* feat(ai-studio): mermaid diagram renderer for visualize

Add a lazy-loaded mermaid renderer for the diagram mode: it renders a mermaid
source string to SVG in the browser (securityLevel strict), and falls back to
the raw text on a syntax error. mermaid (~150KB) only loads when a diagram is
shown (React.lazy).

* feat(ai-studio): export visualize output (png/svg/copy)

Add export-visualization util (html-to-image): download PNG, copy image to
clipboard (with a download fallback on browsers like Firefox that cannot write
image blobs), copy source text, and download SVG (native serialization fast-path
for chart/diagram). The card header now offers Copy image / Download PNG / Copy
source actions over the rendered content.

* feat(ai-studio): expand visualize to fullscreen modal

Add an Expand action that opens the visualization full-size in a modal (rendered
through a portal to document.body so its fixed overlay escapes the React Flow
viewport transform). The modal renders the same renderer larger and offers
PNG / SVG / copy-image / copy-source export.

* docs(ai-studio): finalize visualize backlog and refresh getRenderer comment

All visualize work items complete and verified (test/typecheck/lint/build).
chart/diagram visual smoke deferred (needs a structured upstream); covered by
unit tests + build (lazy chunks confirmed).

* feat(ai-studio): render visualize output inside the node body

Move the visualization from a detached floating panel into the node itself: the
content renders in-flow inside the node body (via OptionalNodeContent), so the
node grows vertically to contain it and reads as one cohesive card. Drops the
redundant card title (the node header already shows it) in favor of a slim
badge + export/expand toolbar; the body scrolls when the output is long. Empty
state and the expand modal are unchanged.

* fix(ai-studio): stop mermaid error graphics on non-diagram input

Two causes of the giant 'Syntax error in text / mermaid version' graphics:
- The diagram renderer called mermaid.render on invalid input, and mermaid
  injects its error graphic into the DOM on failure. Now it validates with
  mermaid.parse({ suppressErrors: true }) first and falls back to raw text
  (with a note) without ever calling render, so nothing is injected.
- Auto-detection matched a bare leading word (graph/pie/timeline/journey), so
  prose starting with those was mis-detected as a diagram. Detection is now
  strict: a fenced ```mermaid block, or flowchart/graph WITH a direction, or a
  distinctive declaration keyword. Prose stays markdown. (+3 tests, 13 total)

* feat(ai-studio): render embedded mermaid/json blocks in visualize

Adapt mixed output to the right format without an LLM: the markdown renderer now
renders a fenced ```mermaid block as a real diagram and a ```json block via the
detected renderer (chart/table/...), so a markdown response with an embedded
diagram renders it inline instead of as raw code. The diagram and chart renderers
also extract a fenced block from a larger response when their mode is forced.

* feat(ai-studio): AI adapt - LLM-convert output to the chosen format

Adds an opt-in 'AI adapt' action on the Visualize node: it sends the upstream
output to a new backend endpoint (POST /api/visualize/adapt) that uses an LLM to
convert it into the active format (clean Mermaid / chart JSON / table / ...), then
renders the result. The endpoint reuses the execution abuse gate (per-IP rate
limit + optional Turnstile) and is disabled (501) when the backend has no
OPENROUTER_API_KEY. Verified end-to-end: action-items prose -> clean mermaid.

* feat(ai-studio): add aiAdapt toggle to visualize node config

Adds an 'AI: adapt output to this format' Switch to the Visualize node's
properties panel. When on, the node automatically LLM-converts the upstream
output into the active render format on each run (no need to click the on-card
button, which is hidden while the toggle is on). Off keeps the manual on-card
adapt button for structured formats.

* feat(backend): stronger visualize adapt prompts for better conversion

The format-adapt prompts were terse and gave the model little guidance, so
conversions were mediocre on a small model. Each format now has detailed,
role-framed instructions (pick the fitting diagram type and quote labels safely;
find a category + numeric measure and aggregate for charts; extract 2-8 KPIs for
stat-cards; use only facts, no invention) plus temperature 0.2 and output trim.
JSON renderers also strip a fenced block in case the model wraps the output.

* feat(ai-studio): render markdown in table cells and stat-card values

LLM-produced field values often contain markdown (bold, lists, links, inline
code). A new RichText helper renders such strings as markdown in table cells and
stat-card values, while leaving plain strings (e.g. 'user_id') untouched so they
are not mangled. Raw HTML stays escaped (no rehype-raw) to avoid XSS from
untrusted model output.

* fix(ai-studio): execution-panel dark-mode contrast, modal heading, visualize label

- Set explicit text color (theme token) on the Execution Log and node-detail
  panels so their text stays readable in dark mode instead of inheriting a dark color.
- Align the welcome modal's icon and title on one horizontal row.
- Shorten the Visualize node's palette description so its subtitle fits.

* feat(ai-studio): linked CDN logo with theme-aware variants

Hide the SDK app-bar logo (no prop to replace or link it yet) and render
the Workflow Builder CDN logo over the slot, linked to workflowbuilder.io.
Swap the transparent dark-text / white-text variants by the SDK theme so
the logo reads on both light and dark app bars (the -solid asset bakes in
a white background). Remove once the SDK exposes a logo/logoHref prop.

* fix(ai-studio): keep app-bar Save button clear of the logo overlay

display:none on the SDK logo collapsed its flex box, sliding the Save
button left under the fixed logo overlay. Hide the logo with visibility
and reserve its width instead so the nav-segment keeps its place.

* feat(ai-studio): add Visualize node to AI Debate template

End the debate flow in a Visualize node and shape the verdict as markdown
(verdict line + For/Against table + reasoning) so it renders richly.

* feat(ai-studio): add Visualize node to Meeting Notes template

End the flow in a Visualize node and shape the recap as markdown with an
action-items table (owner/task/due) so it renders as a structured artifact.

* feat(ai-studio): converge Content Repurposer into a Visualize node

Add a Content Pack agent that merges the three channel drafts into one
sectioned markdown document, then a Visualize node to render it. Keeps the
fan-out and gives the flow a single final artifact.

* refactor(ai-studio): drop dead code and unused exports

Remove the unused isTurnstileEnabled helper and de-export symbols that are
only used within their own module (renderers, VISUALIZE_MODES/VisualizeMode,
DetectResult, VisualizeNode). knip-clean across ai-studio, backend, worker.

* chore(ai-studio): drop internal planning docs from the PR

Remove the long-work backlog and crystallize plan; agent-process scratch,
not product docs for the public repo. Net diff vs main no longer includes them.

* fix(ai-studio): declare ai-agent output so node mentions resolve

The ai-agent palette item had no outputSchema, so {{ nodes.<id>.response }}
references (e.g. in decision conditions) fell through to the SDK's unresolved
'missing mention' label. Declaring the response output renders a clean
'Classify Ticket · Response' pill instead.

* feat(ai-studio): web search tool for AI Agent nodes (Tavily)

Add an opt-in 'Web search' toggle to the AI Agent node. When enabled and
TAVILY_API_KEY is set, the executor gives the agent a Tavily-backed web_search
tool via the AI SDK tool-calling loop (capped at a few steps). The loop runs
inside the activity, so it needs no graph cycles and stays DAG-compatible.
Without the key the node runs unchanged (tool simply not exposed).

* feat(ai-studio): add Market Research template using web search

A Trigger -> Research Agent (web search enabled) -> Visualize flow that
showcases the AI Agent web-search tool: the agent searches and writes a
sourced markdown brief, rendered in the Visualize node. Needs TAVILY_API_KEY
to actually search; runs without it, just answers from the model.

* feat(ai-studio): always AI-adapt visualize output, drop the toggle

Remove the aiAdapt switch from the Visualize node. Structured formats
(chart/diagram/table/json/stat-cards) are now always LLM-adapted to fit the
selected format; markdown/text stay passthrough. The manual adapt button is
gone too since adaptation is automatic.

* refactor(ai-studio): remove the Error Policy feature entirely

Drop the per-node Error Policy control (schema/uischema/defaults across all
nodes + errorPolicy from every template) and the now-dead ErrorHandle
decorator/component. Nodes rely on the engine default (fail on error).
Simplifies the demo UI - the feature was underdeveloped and not worth keeping.

* feat(ai-studio): default the demo to English

The SDK i18n auto-detects the browser locale, so the public demo came up in
Polish on PL machines. An inline script in index.html pins 'en' before the
module (and the SDK i18n it loads) boots, unless the visitor made a clean
en/pl choice via the selector. Placed in HTML because a bundler import can't
be guaranteed to run before the SDK's i18n init. Pairs with the SDK selector
fix (regional locales) tracked separately.

* refactor(ai-studio): trim narration comments to a self-explanatory minimum

Remove comments that restate code or justify changes across ai-studio, the
execution worker and the backend; keep only terse notes for genuine traps and
non-obvious constraints (single-instance rate limit, fail-closed verification,
mermaid pre-validation, script ordering, browser quirks). Rationale lives in
commit messages, not inline. No code changes.

* feat(ai-studio): tidy node + form UI via SDK CSS overrides

Widen decision-branch spacing, hide the decision node's unused outer output
port (branches carry their own), and give prompt inputs a monospace font and a
capped height. Consumer-side overrides on SDK tokens/classes, no SDK change.

* feat(ai-studio): add undo/redo

Port the undo-redo plugin from demo (app-bar buttons + Ctrl+Z / Ctrl+Shift+Z),
register it on WorkflowBuilder.Root, and add its react-i18next dependency.

* fix(ai-studio): make execution node highlight solid across all statuses

Running/completed/failed rings mixed opacities (40/90/50%), so some looked
half-transparent and others solid. Drop the transparency so every status ring
is a consistent solid color.

* feat(ai-studio): center execution log, drop the node-detail popup

Move the execution log to the bottom center. Clicking a node's flag marker now
reveals and flashes that node's entry in the log instead of opening a separate
detail panel (that panel is removed).

* fix(ai-studio): flag opens the log; node selection highlights its entry

The flag marker now explicitly opens the log (store-owned collapsed state) so it
works even when the log is already showing that node. Selecting a node on the
canvas highlights its log entry without opening the log. Drops the select-driven
auto-expand effect that could fight a manual collapse.

* fix(ai-studio): center the icon inside node execution markers

* refactor(backend): drop the in-house execute rate limit

Rate limiting now comes solely from the shared execute rate-limit middleware
(per-minute/per-day, proxy-aware, tested), which also covers the Visualize
adapt endpoint via a shared instance so both LLM entry points draw from one
budget. guardExecution keeps only the Turnstile check.

* fix(ai-studio): drop the English pin, let the locale reflect reality

With the SDK selector fix the displayed language matches what actually
renders, so starting in the browser's language is correct behavior.

* refactor(ai-studio): derive log highlight from canvas selection

Clicking a node (its flag marker included) already selects it on the canvas,
so the execution store's selectedNodeId, its mirror effect and the extra
store write were redundant. The log derives the highlight from the SDK's
single-selection hook - it now also clears on deselect - and the flag click
only expands the log.

* fix(ai-studio): cancel stale visualize adaptations, drop the chart chip

Move the AI-adapt call into an effect with cleanup so an in-flight response
can't overwrite a newer output (and no LLM call is wasted on it). Remove the
'Try as chart' chip and the chartable detection it hung on - always-on adapt
covers it and the numbers-array path rendered as plain text anyway. VALID_MODES
now derives from the schema's VISUALIZE_MODES instead of a hand copy.

* fix(ai-studio): one undo per keypress, keep native text-field undo

The SDK useKeyPress toggles on OS key-repeat, so holding Ctrl+Z undid many
steps, and skipTarget hijacked Ctrl+Z inside text fields. Replace with a
plain keydown listener guarded by event.repeat and text-target checks; adds
Ctrl+Shift+Z as redo.

* refactor(ai-studio): bundle the brand logo locally instead of the CDN

* refactor(ai-studio): trim template noise and close latent races

Drop redundant errors:[] from template data (the canonical demo templates
omit it), serialize Turnstile token requests (the single widget can't handle
overlapping calls), use useId for mermaid render ids (the module counter
could collide across concurrent diagrams), and shorten the textarea font
stack to ui-monospace/monospace.

* docs(ai-studio): branch-gap override is app styling, not a pending SDK fix

The SDK gap applies correctly but scales to about 2px at the demo zoom-to-fit,
reading as no spacing; widening it is this app's presentation choice, so the
SDK default stays untouched and the change was dropped from the SDK polish PR.

* refactor(ai-studio): drop overrides superseded by SDK fixes

The decision node's dead outer port and the branch-card panel spacing are
fixed in the SDK, and prompt inputs cap their growth via the new maxRows
uischema option instead of a CSS max-height.

* docs(ai-studio): drop a narration comment from the app bootstrap

* refactor(ai-studio): drop the branch-gap override

The spacing complaint was about the properties panel, fixed in the SDK; the
canvas override was a misdiagnosis artifact.

* refactor(ai-studio): pass the brand logo through the SDK props

The logo/logoHref props landed in the SDK, so the fixed-position overlay,
the hash-prefix CSS hide and the theme-swap component all go away.

---------

Co-authored-by: Jan Librowski <jan.librowski@synergycodes.com>
…sualize copy feedback (#62)

* fix(backend): disable proxy buffering on the execution SSE stream

A fronting proxy that buffers the stream delivers every event in one
burst at stream close, so live node progress renders only after the
run finishes. Send X-Accel-Buffering: no from the route and re-emit it
from the deploy nginx (nginx hides upstream X-Accel-* headers), so a
TLS-terminating proxy in front of the stack sees it too.

* fix(ai-studio): drop copy-source and give copy-image visible feedback

The visualize copy buttons gave no feedback at all, so a successful
copy looked broken. Copy image now flips to a checkmark for 1.5s and
is disabled while AI adaptation is in flight (its target element does
not exist yet, so clicking was a silent no-op). Copy source is removed
from both the card and the modal.

* feat(ai-studio): visualize copy writes image and plain text together

One clipboard entry with both image/png and text/plain: pasting yields
the image where images are accepted and the raw result text in
text-only fields. Copying only the image looked broken when pasted
into a text field.

* fix(ai-studio): set Render as to auto in template visualize nodes

Palette drops get mode: 'auto' from defaultPropertiesData, but the
hand-authored template nodes omitted it, so their Render as select
showed an empty value.

---------

Co-authored-by: Jan Librowski <jan.librowski@synergycodes.com>
The disclaimer showed only on first visit with no way back to it. When the
modal is closed, an info button in the canvas's bottom-right corner brings
it up again.

Co-authored-by: Jan Librowski <jan.librowski@synergycodes.com>
* feat(ai-studio): dock execution log beside the properties panel

The log is no longer a centered overlay covering the canvas. It floats
bottom-right: against the left edge of the properties panel when the
panel is expanded, at the viewport edge otherwise.

- height grows with entries up to 430px instead of a fixed 60vh
- auto-scroll follows the newest entry; manual scroll pauses it,
  returning to the bottom resumes it
- collapse state persists in sessionStorage for the session
- starting an execution reopens a collapsed log

* fix(ai-studio): make the whole log entry a collapse toggle

The gray detail box showed a pointer cursor but ignored clicks; only
the entry header toggled. Now the entire entry (header + detail) is
one toggle area.

- text selection inside the entry does not trigger the toggle, so log
  content stays copyable
- clicks on interactive elements (links, buttons) inside the detail do
  not propagate to the toggle

* fix(ai-studio): hide the welcome reopen button when the corner is busy

The floating info button shares the bottom-right corner with the
execution log and overlapped the expanded properties panel footer.
Hide it while the log is on screen or the properties panel is
expanded; it returns once the corner is free.

* style(ai-studio): cut comments down to the non-obvious

* refactor(ai-studio): replace toggle guards and unit comment with self-evident code

* style(ai-studio): restore rem unit for the log panel max-height

* refactor(ai-studio): self-review pass on the execution log changes

- full-word selector params (state, not s) across touched files
- named constants for magic numbers: scroll bottom tolerance, detail
  preview length, node id preview length, expanded-panel height ratio
- anchor hook decomposed into findRightPanel / measureAnchor /
  observeAnchor / sameAnchor instead of one closure with an alias
- toggleLog reuses setLogCollapsed instead of a side effect inside the
  setState updater; persist helper renamed to persistLogCollapsed
- clsx for conditional class lists instead of template strings
- dropped a dead styles lookup (event--<type> classes do not exist)

* fix(ai-studio): toggle log entries from the header only

Clicking the detail box collapsed the entry under the cursor, which
fought with reading and copying the content. The toggle now lives on
the header row alone and the detail is plain selectable text without
a pointer affordance.

Refines the toggle behavior after hands-on testing; diverges from the ticket's
whole-entry-toggle wording, ticket to be updated.

* revert(ai-studio): restore the whole-entry log toggle

Reverts the header-only constraint - the ticket spec is correct as
written: header and detail box form one toggle area, with text
selection and interactive elements guarded.

* refactor(ai-studio): apply review feedback on the log changes

- persist the log collapse state via zustand persist middleware with
  partialize and sessionStorage instead of hand-rolled helpers;
  resetExecution keeps the current value through the updater
- is-prefix for boolean variables: isLogCollapsed, isPanelExpanded,
  isLogVisible, isOpen, isExpanded, isHighlighted, isCollapsed

---------

Co-authored-by: Jan Librowski <jan.librowski@synergycodes.com>
…#65)

* fix(ai-studio): re-adapt visualization when the render format changes

The adapted content was cached in bare state and reset only when the
source text changed, so switching the render format kept feeding the
previous format's adaptation to the new renderer (raw JSON instead of
a chart) and never called the AI again.

The adaptation is now an async derived value keyed by (renderer, text)
in a useAdaptedVisualization hook: a stale entry is ignored by the key
check instead of being reset by an effect, and each format change
triggers a fresh adapt for that format.

* refactor(ai-studio): abort in-flight adapt calls, record failures as data

The effect now uses an inner async function with an AbortController:
switching formats mid-flight cancels the HTTP request instead of just
ignoring its result, and the cancelled flag is gone.

A failed adapt is cached as { key, output: null } - the raw-text
fallback derives from data instead of a swallowed exception, so the
catch block has real content and the failure is one selector away
from the UI whenever we want to surface it.

* refactor(ai-studio): return render-ready text from useAdaptedVisualization

The hook now owns the raw-text fallback and returns renderText plus an
isAdapted flag, so the caller no longer interprets a null convention
spread across files.

* refactor(ai-studio): return render-ready text from useAdaptedVisualization

The hook now owns the raw-text fallback and returns renderText plus an
isAdapted flag, so the caller no longer interprets a null convention
spread across files.

---------

Co-authored-by: Jan Librowski <jan.librowski@synergycodes.com>
* fix(deploy): pass OPENROUTER_API_KEY to the backend service

The adapt endpoint runs its LLM call in the backend, not the worker,
and answered 501 adapt_disabled on every request because only the
worker received the key. Executions kept working, which masked it.

* fix(deploy): wire TAVILY_API_KEY into the worker service

The worker reads TAVILY_API_KEY to enable the AI Agent's web search
tool, but the deploy compose never passed it, so web search was
silently disabled on deployments: agents with the toggle on ran
without the tool. Optional - an empty value keeps it off.

* fix: unify the AI_MODEL default on the demo model

Backend and worker code defaulted to google/gemini-2.5-flash-lite
while the deploy stack defaults to mistralai/mistral-small-3.2-24b-instruct,
so an unset AI_MODEL meant different models depending on where the
process ran. One default everywhere now - the deployed demo already
runs this model, so production behavior does not change.

---------

Co-authored-by: Jan Librowski <jan.librowski@synergycodes.com>
* feat(sdk): reduce required peer dependencies

* feat(sdk): re-export JsonForms authoring primitives
@piotrblaszczyk
piotrblaszczyk merged commit dd9aabe into release Aug 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants