ExecutorService declares a hard dependency on CAPABILITY_SERVICE, but the
langflow backend's register_all_service_factories() registered
ExecutorServiceFactory without CapabilityServiceFactory. The service manager
could not resolve the dependency when building ExecutorService, so every flow
run failed with NoFactoryRegisteredError / "ExecutorService is not available".
lfx's own bootstrap (get_factories) auto-discovers the capability factory, so
lfx run/serve were unaffected; only the backend's hand-maintained factory list
had drifted. Add a regression test asserting the backend registers a factory
for every ExecutorService dependency.
Coordinator.stream() no longer takes an inputs parameter (the streaming path
reads initial_inputs from runtime_options); update the capability routing
stream-close test to the current signature.
Tool-mode components built by component_tool returned a synthetic
"Tool ... executed with inputs" payload instead of running the wrapped
component; only a hard-coded calculator actually executed, so agent flows
appeared to succeed while their tools never ran real component logic.
Resolve the tool's component blob in ToolWrapperInputHandler.prepare (where
the Stepflow context is available), reshape the raw component definition into
the executor's enhanced blob shape, pre-compile it via CustomCodeExecutor, and
wire an async tool_func that runs the real component through
_execute_compiled_component on each invocation. The calculator stays as a
self-contained fast-path.
Stop unwrapping SecretStr fields and resolving env-var-named secrets onto the
serialized output edge. model_dump(mode="json") leaves them masked, and the
worker never emits the plaintext onto an edge the orchestrator routes between
steps and may stream back. A component that needs the real value must resolve
it on its own input/config edge inside the worker.
Add StepflowExecutor, an lfx Executor that translates a Langflow flow to a
Stepflow Flow, runs it via stepflow_py (local orchestrator by default, or a
remote endpoint), and streams results back through the seam. Registered under
the `lfx.executors` entry-point as kind "stepflow".
Map the lfx run-path contract on both edges: inputs in {INPUT_FIELD_NAME: ...}
shape plus session_id become the flow's $.message / $.session_id; the run's
per-item result (fetched via get_run_items after completion) is assembled into
list[RunOutputs] so /run serializers find the output. A non-success terminal
status raises, matching Graph.arun's fail-loud behavior.
The loop body now runs its subgraph through get_default_coordinator().stream,
so InProcessExecutor calls async_start with inputs/max_iterations/config/
reset_output_values/fallback_to_env_vars alongside event_manager. The mock
only accepted event_manager and raised TypeError on the extra kwargs. Widen it
to **_kwargs while still capturing event_manager for the assertion.
teardown() emptied self.factories but left factory_registered set, so
get_service()'s lazy re-registration was skipped and later lookups raised
NoFactoryRegisteredError. Once Graph.arun started routing through the
executor service, that surfaced as intermittent flow-execution 500s in the
backend unit suite whenever a sibling test had torn the global manager down.
- ExecutorService discovery skips a loaded entry-point object that is not an
Executor instance instead of registering it and failing only at execute() time
- Coordinator.run docstring notes the single-Unit / single-RunComplete assumption
that identity_partition currently guarantees
- correct two stale docstrings: Unit's session_id is honored only on the legacy
passthrough path, and ExecutorService.teardown no longer claims the service
manager keeps the cached instance
- register ExecutorServiceFactory in register_all_service_factories so
EXECUTOR_SERVICE resolves at app startup; without it the seam raised
NoFactoryRegisteredError across integration and mcp tests
- test_base.py graph.async_start mocks now default the leading positional
param (the seam calls async_start with inputs as a keyword), fixing the
'missing 1 required positional argument' failures
- add executor_kind to the settings composition EXPECTED_FIELDS set
- run_to_completion and Coordinator.stream wrap iteration in contextlib.aclosing
so the underlying generator is finalized when the loop returns or raises early
- execute_loop_body wraps its stream in aclosing for the same mid-iteration guarantee
- drop the unused inputs param from Coordinator.stream; the streaming path reads
initial_inputs from runtime_options, so inputs was a silent no-op, and document it
- test_coordinator_uses_settings_executor_kind uses monkeypatch + a finalizer so a
mid-test failure can't poison session settings
- test_registry_returns_same_executor_instance_across_runs builds a fresh graph per run
- test_set_default_coordinator_overrides_singleton restores the singleton afterward
Robustness pass on the execution seam after using it to integrate an external
executor end-to-end. Four things this pins down:
1. Reusable executor contract suite
- tests/unit/execution/test_executor_contract.py
- ExecutorContract with 7 universal seam guarantees: kind shape, execute()
returns an AsyncIterator, stream ends with exactly one RunComplete,
instance is reusable across runs, concurrent runs are isolated, consumer
cancellation does not hang or leak, execute() signature is stable.
- TestInProcessExecutorContract subclasses and provides fixtures. Downstream
executors (stepflow, future remote/sandbox) get the same battery by
subclassing and overriding two fixtures.
2. Cancellation/cleanup correctness
- tests/unit/execution/test_cancellation.py
- InProcessExecutor wraps graph.async_start() in try/finally with explicit
aclose so consumer aclose cascades to the underlying graph generator.
Previously the inner generator was abandoned and only finalized on a GC
pass: a real leak for executors holding subprocesses or sockets.
- Coordinator.run and Coordinator.stream propagate aclose the same way so
the full chain (consumer -> coordinator -> executor -> graph) cleans up
deterministically.
3. Documented seam contracts
- Unit.runtime_options: free-form bag, "_"-prefixed keys reserved for
executor-internal flags, common in-process keys listed.
- StepResult.payload: untyped on purpose; each executor defines its own
event vocabulary; consumers normalize at consumption site.
- RunComplete.outputs: only the legacy in-process path populates it;
streaming consumers should collect from StepResult.payload.
- Executor ABC: lifecycle (shared instance, must be reusable, must tolerate
concurrent execute() calls and consumer aclose).
4. Coordinator-routed E2E suite
- tests/unit/execution/test_coordinator_e2e.py
- Real Graph through Coordinator -> registry -> InProcessExecutor chain.
- Asserts payload shape, RunComplete never leaks to stream() consumers,
dispatch routes to configured kind, registry returns same instance.
Full execution suite: 63 passing. flow_executor coordinator test: passing.
Introduce an ExecutorService that owns the registry and the default coordinator,
discovered through the standard lfx ServiceManager (parity with cache, database,
storage, etc). Add an executor_kind setting so the default kind can be configured
without touching code, and an lfx.executors entry-point group for third-party
executors. Entry-point discovery refuses to overwrite an existing kind so an
installed package cannot silently replace the built-in in-process executor;
explicit replacement still works through ExecutorService.register().
The lfx.execution public API (get_default_coordinator, get_default_registry,
set_default_coordinator, reset_default_coordinator) now resolves through the
service manager but keeps the same surface, so existing call sites in Graph.arun,
flow_executor, the CLI, run/base, and loop_utils are unchanged.
Also surface root-cause tracebacks in lfx.services.deps.get_service: the helper
still returns None on failure (callers like get_db_service rely on that), but it
now logs the exception so init failures stop disappearing into the void.
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: add Firecrawl Search API to the bundle page
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* fix(firecrawl): D205 docstring + defensive .get('data') in crawl
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(firecrawl): D205 docstring in scrape
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(firecrawl): use list.extend in map (PERF401)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: add unit tests for Firecrawl v2 components
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* test: align Firecrawl component tests with lfx _attributes pattern
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* refactor(firecrawl): remove deprecated extract endpoint component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(firecrawl): remove deprecated extract endpoint component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: drop extract component test (extract endpoint removed)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: remove Firecrawl Extract API section (deprecated)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* fix(firecrawl): map Search component to Firecrawl icon, drop Extract
The frontend icon map in styleUtils.ts still referenced the removed
FirecrawlExtractApi and lacked the new FirecrawlSearchApi, so the Search
node would not render the Firecrawl logo.
* chore: regenerate component index for Firecrawl v2 (Search added, Extract removed)
* refactor(firecrawl): extract components into the lfx-firecrawl extension bundle
Port the firecrawl provider out of lfx.components into a standalone
Extension Bundle at src/bundles/firecrawl (distribution: lfx-firecrawl),
following src/bundles/PORTING.md:
- Move the v2-SDK components (Scrape, Crawl, Map, Search) to
src/bundles/firecrawl/src/lfx_firecrawl/components/firecrawl/ and
remove the in-tree provider + its lfx.components registration.
- Own the firecrawl-py>=4,<5 pin in the bundle; drop it from
langflow-base.
- Wire the workspace (root pyproject deps/sources/members) and uv.lock.
- Append migration-table entries (bare name, both import paths, pre-a
slot) for the four classes; FirecrawlExtractApi gets no entries since
the v2 SDK removed the extract endpoint and the component was dropped.
- Regenerate the component index (firecrawl category removed) and
locales/en.json (54 firecrawl keys move out of core).
- Bundle-local unit tests + test_pilot_firecrawl_upgrade integration
test; update Dockerfile bundle enumeration comments.
* fix(lfx): repair migration table entry fused during release-1.11.0 merge
The release-1.11.0 back-merge collided the FirecrawlSearchApi legacy_slot
entry with the NextPlaidVectorStoreComponent bare_class_name entry, mashing
both into a single object with duplicate 'target' keys, populating two of
{bare_class_name, import_path, legacy_slot}. That fails the MigrationTable
validator (entries.51) and broke the lfx loader tests.
Split it into the two intended entries so each populates exactly one
key-field. Restores the FirecrawlSearchApi and NextPlaidVectorStoreComponent
quads (60 entries total, +16 vs base). Append-only and bare-name guards pass.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
* fix(authz): schedule periodic audit-log retention cleanup
clean_authz_audit_log() was only invoked once, at startup inside
initialize_services(). A long-running instance never pruned authz_audit_log
again after boot, so the table grew unbounded between restarts even though the
retention helper was already implemented and unit-tested.
Wire the same helper to a recurring background worker:
- New AuditLogCleanupWorker (services/task/audit_cleanup.py), modelled on the
sibling temp_flow_cleanup.CleanupWorker: a stop-event-driven asyncio task that
prunes on a fixed interval in its own session_scope(). Best-effort -- the
helper logs-and-swallows DB errors and an outer guard keeps the loop alive, so
it never blocks the event loop or request path.
- Gated: the worker only schedules when AUTHZ_AUDIT_ENABLED is True and
AUTHZ_AUDIT_RETENTION_DAYS > 0; otherwise start() is a no-op. Retention=0
stays a no-op end to end.
- Sleep-first loop: the unconditional startup sweep still prunes at boot, so the
first scheduled pass waits one interval to avoid a redundant immediate delete.
The startup sweep is left unchanged.
- New AUTHZ_AUDIT_CLEANUP_INTERVAL setting (default 86400 = daily, floor 300s).
- Started/stopped from the application lifespan, both best-effort.
Tests (test_audit_cleanup_worker.py) show cleanup runs repeatedly on the
recurring schedule (not just at startup), is a no-op when retention or auditing
is disabled, survives sweep failures, and -- end to end against a real SQLite
engine -- prunes a row inserted after startup while leaving in-window rows.
Closes the audit-retention-scheduling gap (C) for OSS RBAC on release-1.10.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(authz): read AUTHZ_AUDIT_CLEANUP_INTERVAL directly
The cleanup worker resolved the sweep interval via getattr-with-default
plus a TypeError/ValueError guard, mirroring the field default in a
module constant. Pydantic already guarantees the field is present and
>= 300, so those fallbacks were unreachable and the constant was a
second source of truth that could drift.
Read the setting directly; keep DEFAULT_CLEANUP_INTERVAL_SECONDS only as
the pre-start() placeholder for unstarted workers. Drop the matching
missing/garbage-fallback test assertion.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(authz): RBAC enforcement + share-lifecycle integration tests via a test-double enforcer
No automated test exercised end-to-end authorization enforcement. The OSS
authorization service is a pass-through (enforce() always returns True,
supports_cross_user_fetch() is False), so allow/deny semantics could not be
asserted against it — only guard-wiring unit tests existed.
Add a lightweight, OSS-only test-double enforcer plus integration tests that
drive the real flow routes over HTTP under a genuine allow/deny signal — no EE
Casbin package required.
- _policy_double.py: PolicyTestAuthorizationService derives allow/deny from the
seeded authz_role / authz_role_assignment / authz_share rows and sets
SUPPORTS_CROSS_USER_FETCH=True. install_policy_authz() swaps it onto the
service manager (str-enum keys make ServiceType interchangeable) and flips
AUTHZ_ENABLED=true / AUTHZ_SUPERUSER_BYPASS=false, restoring both on exit, so
every get_authorization_service() call site (guards, fetch, listing, helpers)
sees it for the duration of a test. Ships seed helpers (roles/assignments/
shares) so tests stay declarative.
- test_rbac_enforcement_integration.py:
* Role matrix (Phase 1.11) on flow routes, with flows owned by a separate user
so the guards' owner-override does not mask the role decision:
- viewer: read + execute (build) allowed; write/delete -> 404; create -> 403
- developer: write + create allowed; delete -> 404
- admin: full, including delete
* Share lifecycle (Phase 3.13): Alice shares a flow with Bob ->
Bob GET/PATCH/build succeed; without the share every fetch route -> 404
(UUID-privacy mask). A read-level share grants GET but 404s PATCH, proving
permission_level (not mere share presence) is enforced.
* Domain resolution: a workspace-scoped grant authorizes a flow in that
workspace but 404s a flow in another — trips if _resolve_authz_domain
regresses.
In each test a cross-user GET -> 200 (only possible with enforcement on +
share-aware fetch) is the linchpin proving the paired deny is real enforcement,
not an owner-scoped fetch miss; removing a guard flips a 404 to 200/200.
Closes the enforcement integration-test gap (D) for OSS RBAC on release-1.10.0.
Per the ticket's triage note, the reusable test-double also stands alone if the
team prefers the deny-path matrix to live in EE.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(authz): harden policy double per review — strict scoped grants, deterministic seeding, read-share build denial
- _assignment_covers: a scoped assignment missing domain_id no longer
widens to global coverage; only domain_type='global' is global
- assign_role: fail fast (ValueError) on non-global assignments without
a domain_id so malformed grants can't hide domain-resolution bugs
- seed_system_roles: overwrite preexisting viewer/developer/admin rows
so the asserted policy matrix is always SYSTEM_ROLE_PERMISSIONS
- read-only share test: also assert build/execute is denied (404), not
just PATCH
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* nextplaid integrate
* doc ids fix
* multivec fixes
* type check
* refactor: ship NextPlaid as the lfx-nextplaid extension bundle
Convert the in-tree NextPlaid vector store and its companion vLLM
multivector embeddings into a standalone `lfx-nextplaid` Extension Bundle,
following src/bundles/PORTING.md (cf. lfx-arxiv, lfx-ibm). The whole
feature now ships as an additive bundle with no core modifications.
- Move both components into src/bundles/nextplaid (components/nextplaid),
dropping src/lfx/.../components/nextplaid and reverting the core vllm
__init__ multivector additions.
- Bundle declares its own runtime deps (langchain-plaid, pillow) and ships
extension.json + the langflow.extensions entry point.
- Add migration_table entries mapping the legacy class names / import
paths to ext:nextplaid:*@official; wire the workspace (root pyproject,
uv.lock).
- Add the pilot upgrade integration test and bundle-local unit tests.
- Declare explicit outputs on NextPlaidVectorStoreComponent so the
extension validator can resolve its output methods.
* fix: address CodeRabbit review on NextPlaid bundle
- nextplaid: derive stable text IDs from source/page+content (not content
alone) and fingerprint raw PIL images by bytes (not batch position) so
ingests upsert instead of colliding/overwriting unrelated vectors
- vllm impl: validate the /pooling response envelope before nested indexing,
raising a clear RuntimeError on malformed text/image responses
- icon: use the isDark boolean prop contract instead of the isdark string
- test: gate the distribution check on package presence (PackageNotFoundError)
so genuine import failures surface instead of being skipped
---------
Co-authored-by: Meet <meet@dhcp-9-127-22-227.c4p-in.ibm.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
fix(frontend): restore Inspection Panel access to hidden advanced fields
Fields that were both advanced=True and listed in HIDDEN_FIELDS became
unreachable from the Inspection Panel: hidden from the node body (advanced)
and removed from the panel (HIDDEN_FIELDS), leaving no edit path. This
affected every HIDDEN_FIELDS entry, originally reported for Chat Input/Output
Store Messages (should_store_message).
Keep HIDDEN_FIELDS out of the default advanced view (the #12473 declutter
intent) but stop filtering them out of edit-fields mode, so users can surface
and edit them via the visibility toggle. Also drop the stale Agent verbose
entry (removed from the component via drop = {verbose}) while keeping the
still-live format_instructions and output_schema hidden. i18n the two
remaining hardcoded panel strings.
Fixes#13595
fix(frontend): remove duplicate tooltip and raw i18n key on collapsed sidebar nav
The floating canvas control nav (shown when the sidebar is collapsed)
rendered two tooltips per icon: a native `title` attribute on
ControlButton plus the styled ShadTooltip. On top of that,
MemoizedComponents passed the raw i18n key (`item.tooltip`) as the
tooltip text instead of running it through `t()`, so the ShadTooltip
exposed strings like `sidebar.nav.bundles` while the native title showed
the bare id (`bundles`).
- CanvasControlButton: drop the native `title` (the duplicate tooltip);
keep accessibility via `aria-label`. The ShadTooltip is the single
visual tooltip. This also removes the redundant native tooltip from the
zoom/fit/lock control buttons.
- MemoizedComponents: translate the nav tooltip with `t(item.tooltip)`.
Adds an isolated CanvasControlButton regression test asserting no native
`title` and that the label is surfaced via aria-label + ShadTooltip.
* fix: fail fast when Redis job queue backend is unreachable
When LANGFLOW_JOB_QUEUE_TYPE=redis is set but Redis is not reachable,
Langflow booted normally and then raised a raw redis ConnectionError as
a 500 on the first flow execution, with no clear cause.
Probe Redis at startup (bounded retry) and abort boot with an actionable
error when it stays unreachable, mirroring the existing external-cache
connectivity check. Translate a later Redis outage on the build and
ownership paths into a clean HTTP 503 via a typed
JobQueueBackendUnavailableError instead of a raw stack trace.
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]
* [autofix.ci] apply automated fixes
* fix: address review - probe redis pre-start, redact credentials, cancel orphaned build
- is_connected() now probes with a temporary client when the service has
not started yet: the startup fail-fast in initialize_services() runs
before the per-worker start() creates the client, so it previously
rejected every redis boot, healthy or not
- connection_target redacts URL userinfo so credentials never reach
server logs or the HTTP 503 detail
- build_flow cancels the just-started build (best-effort) when owner
registration fails, instead of leaving an unreachable build running
- register_job_owner only records the local owner after the Redis write
succeeds, so a failed write cannot leave same-worker ownership checks
passing while other workers see the job as unowned
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* docs: migrate code blocks from CodeSnippet to native Prism
Replace the custom CodeSnippet component (@code-hike/lighter) with
Docusaurus's native @theme/CodeBlock across all MDX files (current and
versioned docs). Add bash to additionalLanguages and swizzle
prism-include-languages.js to add custom token highlighting for shell
commands and flags. Remove @code-hike/mdx dependency.
* docs: improve inline code styling
Darken inline code background, increase horizontal padding to 0.4em,
fix vertical alignment, and remove border in light mode.
* docs: address PR review — fix code slice regression and CSS/regex polish
- Inline RecursiveCharacterTextSplitter inputs and methods as literal
code blocks in concepts-components.mdx (current + 1.8.0 + 1.9.0),
restoring the focused slices lost when migrating from CodeSnippet
- Scope bash-plain Prism regex to unambiguous CLI subcommands only,
removing generic bash builtins (run, add, get, set, start, stop, etc.)
- Merge duplicate .theme-code-block CSS rules into a single declaration
* fix(docs): prevent horizontal scroll on API docs pages
The Redoc two-column layout (sidebar 300px + api-content 1300px)
totals 1600px, expanding .main-wrapper beyond narrower viewports
because it has overflow:visible. Clips at .main-wrapper using the
html.plugin-redoc class that Docusaurus adds on API pages only.
* fix(docs): API docs sidebar and layout fixes
- Disable Redoc built-in search (disableSearch: true)
- Pin sidebar top to navbar height (top: 60px) so it never hides behind navbar
- Remove hardcoded #111 background from dark mode sidebar
* fix(docs): render markdown correctly in API docs descriptions
The _clean_descriptions function was converting newlines to <br> tags,
mixing HTML with Markdown. CommonMark stops parsing Markdown headings
(###) inside HTML blocks, causing them to appear as literal text in Redoc.
Replace the <br> conversion with a simple strip() so descriptions remain
pure Markdown and Redoc renders headings, lists, and code blocks correctly.
* feat(docs): align API docs colors with Langflow brand
- Set primaryColor to #F471B5 (Langflow pink)
- Add HTTP method badge colors matching Langflow palette
- Set schema.linesColor and requireLabelColor to brand pink
- Set inline code color to pink, headers to #e3e3e3
- Set sidebar background to #18181b (matches frontend dark bg)
- Set rightPanel background to #0d0d0f, codeBlock to #161618
- Refactor: move color config from CSS to theme.theme where safe
- Remove dead search input CSS (search disabled via disableSearch:true)
- Consolidate duplicate .menu-content rules
* wip(docs): API docs styling — colors, components, light/dark themes
* fix(docs): fix Response samples h3 title padding and refactor HTTP method button CSS
* feat(docs): add sidebar dark background, active item styles, and right panel color adjustments
* fix(docs): add sidebar borders and remove operation divider border
* fix(docs): fix expanded response background and align dark/light theme colors
* refactor(docs): standardize selectors, comments and remove duplicate rules in redocusaurus.css
* refactor(docs): apply PR review — CSS custom properties, version pin, dom version comments, fix overflow
* fix(docs): fix Redocly badge visibility covered by sidebar background
* fix(docs): extend sidebar border-right to Redocly badge area
* refactor(docs): replace hardcoded #ffffff label color with --redoc-text-label variable
* fix(docs): lighten inline code background in API docs dark theme
Redoc's default typography.code.backgroundColor (rgba(38, 50, 56, 0.05))
is nearly invisible over the dark background. Override it with
rgba(255, 255, 255, 0.05) in dark theme only, excluding pre > code so
code sample blocks stay unaffected.
* feat(docs): align docs primary pink with API spec brand color
Use #f471b5 (API spec primaryColor) as --ifm-color-primary in dark theme
and #e44fa0 (slightly darkened for contrast on white) in light theme.
Remove dark-theme pink overrides in sidebar.css (#ff6ad0 CTA and
hsla(329, 55%, 68%) active TOC link) — they compensated for the old
muted pink and are redundant now that the primary itself is bright.
* feat(docs): lighten dark theme text colors for better readability
Bump body text (#a8a8b0 -> #bcbcc4), headings (#cdcdd4 -> #dcdce2)
and sidebar menu (#8a8a92 -> #9c9ca4) one step brighter.
* chore(docs): add IBM Equal Access accessibility-checker setup
Add accessibility-checker as devDependency with aceconfig.js (policy
IBM_Accessibility, JSON reports in docs/a11y-results/, gitignored).
Scan with: npx achecker <url> against a built docs site.
* fix(docs): WCAG AA contrast and ARIA fixes across docs and API reference
Validated with IBM Equal Access scans (light theme): home, quickstart and
component pages at 0 violations; /api from 3382 down to 167 (all remaining
are Redoc-internal DOM: schema table headers, svg/select labels).
Docs site:
- Light primary pink #d11074 — passes 4.5:1 on white and inline-code bg
- Light Prism palette darkened per-token to pass 4.5:1 on #f9f9fd
- TabItem swizzled to give tabpanels an accessible name (aria-label)
- codeBlockA11y client module: scrollable code blocks get role=region +
unique label; non-scrollable ones lose the needless tabindex
API reference (Redocusaurus):
- redocA11y client module: role=main on api-content, role=navigation on
sidebar — fixes 1924 aria_content_in_landmark violations
- HTTP method badges and response chips darkened to pass with white text
- Light-theme overrides: accessible pink #cd1072 for links, required
markers, constraint chips, schema tree lines; darker grays for utility
buttons and type labels (incl. 0.7-opacity wrapper fix)
- Sidebar active/hover items use regular text color, method badges keep
their own colors; expandable property names match non-expandable ones
* fix(docs): WCAG AA contrast fixes for dark theme
Validated with IBM Equal Access scans in dark mode (temporary
colorMode.defaultMode flip during scanning): home, quickstart and
component pages at 0 violations; /api matches light at 167 remaining
(all Redoc-internal DOM: table headers, svg/select labels).
- Code block comments/line numbers #4a5060 -> #798197 (4.56:1 on #18181a)
- Redoc dark sample tokens: boolean/null #e95c59, number #5392b8
- Status-code tabs: lift docusaurus-theme-redoc's #303846 !important
selected-tab rule with a higher-specificity override
- oneOf variant buttons: dark text in both themes (their white/pink
backgrounds are theme-independent)
- redocA11y client module: patch response chip colors (success green /
error red) to dark-accessible variants when data-theme=dark — a single
Redoc theme color cannot pass on both light and dark derived
backgrounds, and status is only distinguishable by computed color
* fix(docs): resolve remaining Redoc-internal accessibility violations
Extend the redocA11y client module with semantic patches for Redoc DOM
the theme cannot reach (validated: 0 IBM Equal Access violations on all
scanned pages in both light and dark themes):
- Decorative svg chevrons/arrows: aria-hidden=true (svg_graphics_labelled)
- Content-type dropdowns: aria-label (input_label_exists)
- Schema field tables (2-col name|description layout, no <th> anywhere):
role=presentation — content reads in DOM order; role=rowheader on <td>
is invalid ARIA inside a native table (table_headers_exists/related)
- Semantic patches run on the next animation frame after DOM changes so
Redoc's lazy-rendered operations are covered immediately; the heavier
color patch stays debounced
* ci(docs): gate docs accessibility with IBM Equal Access scans
Add test-docs-accessibility job to docs_test.yml (rides the existing
docs/** path filter from ci.yml): build + scan 4 representative pages
(home, quickstart, component page, /api) in light theme, then flip
colorMode.defaultMode to dark, rebuild and scan again.
- scripts/a11y-ci.sh: serves the build and runs npx achecker per page
with one retry to absorb Redoc lazy-render timing flakes; any real
violation fails the job (failLevels: violation)
- aceconfig.js: pin ruleArchive to 19May2026 so IBM rule updates don't
break CI without a deliberate bump
* ci(docs): fix Chrome sandbox launch on Ubuntu 24.04 runners
Ubuntu 24.04 restricts unprivileged user namespaces via AppArmor, which
prevents puppeteer's Chrome (used by the IBM checker) from starting its
sandbox. Re-enable them with the documented sysctl workaround instead of
weakening the browser with --no-sandbox.
* fix(docs): align response status code with description text
Redoc sets vertical-align: top and a smaller line-height on the status
code <strong> inside response buttons, leaving "200" visually higher
than "Successful Response". Align both to the shared text baseline.
* refactor(docs): apply PR review feedback
- redocA11y: match only real Redoc routes (/api, /api/workflow) — a bare
startsWith("/api") also matched docs pages like /api-request and
leaked one body MutationObserver per navigation
- codeBlockA11y: also observe the hidden attribute — Docusaurus tabs
toggle panels via hidden (no childList mutation), so scrollable blocks
inside an initially hidden tab were never re-evaluated for tabindex
- Extract Prism themes to src/prismThemes.js (docusaurus.config.js was
past the 600-line red flag)
- concepts-components.mdx (current + 1.9.0 + 1.8.0): comment pointing
hardcoded snippets to recursive_character.py to mitigate drift
Validated: clean build + IBM Equal Access scans 4/4 passing.
* replace-openapi-file-with-1.10
* migrate-prism-changes-to-1.10-version
* a11y-script-dont-block
---------
Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com>
fix(ci): resolve validate-version output in release-lfx changelog link
The create-release job references
needs.validate-version.outputs.current_version in its generated release
notes (the Full Changelog compare link), but validate-version was not in
the job's needs array, so the expression evaluated empty and the link
rendered as compare/v...lfx-vX.Y.Z (broken base).
Add validate-version to the create-release needs. This adds no real
serialization: create-release already waits on release-lfx, which
transitively requires validate-version via run-tests, and the job's
always() if-condition is unaffected.
Flagged by actionlint: property "validate-version" is not defined in
object type {build-docker, release-lfx}.
fix(ci): create GitHub releases on the dispatched v-prefixed tag
The create_release job passed the bare version (v stripped) as the
release tag with no commit target, so when that tag did not exist
GitHub minted a new lightweight tag at the default-branch HEAD -- the
wrong commit, still carrying the previous version (main only adopts a
release's version via the post-release back-merge). Every release since
1.8.2 shipped a stray bare tag (1.8.2, 1.8.3, 1.9.0-1.9.6, 1.10.0)
pointing at a previous-version commit, and the GitHub release had to be
manually re-pointed to the real vX.Y.Z tag after each release.
- create_release now attaches the release to inputs.release_tag for
stable releases; pre-releases keep their computed tag (e.g.
1.10.0rc1) but it is minted at the release commit via 'commit:'.
- release-lfx.yml pins the minted lfx-v* tag to github.sha instead of
the default branch.
The validate-tag-format guard (#12847) only blocks at dispatch time;
create_release was re-creating the very duplicates it guards against.
fix(ci): scope nightly migration-test pre-releases to the langflow stack
The previous fix (#13599) added a global --prerelease=allow, which let
UNRELATED dependencies resolve to alphas: on nightly run 27274206250
the stable->nightly upgrade pulled pydantic 2.14.0a1 + pydantic-yaml
1.6.1a1, and the pydantic alpha breaks langchain-core at import time
(RunnablePassthrough pydantic ValidationError), failing the nightly
boot right after a successful install. Clean installs were fine - only
this upgrade path resolved the alpha combo.
Scope pre-release eligibility to the langflow lockstep stack instead:
uv accepts a pre-release when the package's own requirement carries a
pre-release marker, but not via transitive pins, and the nightly chain
is langflow -> langflow-base -> lfx -> langflow-sdk with exact ==devN
pins. Request each directly; langflow-sdk versions independently
(0.2.0.devN), so an explicit .dev0 floor marks it eligible while lfx's
exact pin selects the version. The 'latest' branch gets the same
treatment via .dev0 floors on all four.
Verified by dry-run against PyPI: langflow/langflow-base/lfx at
1.11.0.dev2 + langflow-sdk 0.2.0.dev2 resolve with pydantic staying at
stable 2.13.4.
fix(ci): allow pre-releases when pinning the nightly in migration validation
Migration Test: pip/venv (stable -> nightly) failed deterministically on
nightly run 27260425158 (twice, including a rerun):
hint: langflow-base was requested with a pre-release marker (e.g.,
langflow-base==1.11.0.dev1), but pre-releases weren't enabled
(try: --prerelease=allow)
This is the first nightly publishing as a canonical .devN pre-release
of the langflow distribution (nightly -> stable bundle cutover). The
'latest' branch of the upgrade step already passes --prerelease=allow,
but the pinned-version branches do not. uv implicitly allows the
pre-release for the directly requested ==X.Y.Z.devN pin, yet langflow's
metadata pins langflow-base==X.Y.Z.devN transitively, and uv rejects
transitive pre-releases unless they are enabled - so the install fails
after the stable uninstall, sinking the migration test.
Add --prerelease=allow to both pinned-version install lines.
Integration tests failed twice in nightly run 27260425158 with pyleak
EventLoopBlockError - first Integration Tests 3.14, then 3.12 on the
rerun, each time in a different test. The blocking stack points at
refresh_models_dev_periodically: every app boot unconditionally starts
a lifespan task that immediately fetches https://models.dev/api.json,
so the request lands mid-test in whatever test happens to be running.
Under pyleak's asyncio debug instrumentation the fetch blocked the loop
0.797s against a 0.2s threshold. Whichever test draws the short straw
flakes - which is why it looked transient and moved between versions.
Add a LANGFLOW_MODELS_DEV_REFRESH env gate (default unchanged: enabled)
and disable it session-wide in the backend test conftest. Tests fall
back to the bundled static model lists, which is also deterministic.
Verified: with the gate set, app boot makes zero models.dev requests;
the previously failing integration test passes.
fix(ci): anchor langflow-base version extraction in nightly docker build
The first nightly on the 1.11 workspace (tag v1.11.0.dev0, run
27253229568) failed in Build Nightly Base Package within seconds:
'Base version format is incorrect'. The extraction (uv tree, grep
langflow-base unanchored, awk field 3, first line) broke because uv
tree now prints the langflow-base workspace-root line
('langflow-base v1.11.0.dev0', two fields) before any dependency line
('langflow-base[complete] v1.11.0.dev0' under the langflow root, three
fields), so the first match yields an empty field 3 and the format
check exits 1. uv tree's stderr is discarded, which made the real
cause invisible in CI.
Anchor to the root line and take field 2 instead - the exact pattern
the langflow (main package) extraction in this same file already uses,
which is why the main-package builds passed on the same tag. Verified
both extractions print 1.11.0.dev0 against a local checkout of
v1.11.0.dev0.