Skip to content

Commit 98a7496

Browse files
authored
Merge branch 'release-1.11.0' into feat/content-blocks-frontend-v2
2 parents d9ee215 + db5f3b0 commit 98a7496

53 files changed

Lines changed: 2988 additions & 670 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
---
2+
name: frontend-a11y-check
3+
description: "Plan, write, run, or review accessibility checks for Langflow frontend work. Use when changes touch React UI, routes, pages, modals, forms, tables, navigation, Radix/shadcn primitives, keyboard behavior, ARIA labels, focus order, or when the user asks for an a11y check, axe test, Playwright a11y scan, or accessibility coverage."
4+
---
5+
6+
# Frontend Accessibility Check
7+
8+
## When To Use
9+
10+
Use this skill for frontend work under `src/frontend` when the change affects:
11+
12+
- UI components, primitives, pages, routes, dialogs, drawers, popovers, dropdowns, tabs, accordions, tables, forms, navigation, or canvas controls.
13+
- ARIA attributes, labels, roles, focus management, tab order, keyboard interaction, disabled states, loading states, or error states.
14+
- A new route or existing route surface that should be scanned.
15+
- A request to add, fix, run, or review accessibility tests.
16+
17+
Use alongside:
18+
19+
- `frontend-testing` for Jest/React Testing Library patterns.
20+
- `e2e-testing` for Playwright patterns.
21+
- `frontend-i18n` when adding or changing user-facing labels, accessible names, `aria-label`, tooltips, or visible strings.
22+
- `ibm-a11y-automation` when the user asks to run the Python route scanner or produce Markdown/HTML reports.
23+
24+
## Goal
25+
26+
Do not stop at "run axe on default render." Inspect the changed UI and cover every meaningful user-visible state that can expose accessibility bugs.
27+
28+
## First Pass
29+
30+
1. Read the changed files and nearby tests.
31+
2. Identify the UI surface:
32+
- Primitive/component only
33+
- Composed component
34+
- Page/route
35+
- Stateful workflow
36+
- Shared helper that changes labels, roles, tab order, or focus behavior
37+
3. List interactive surfaces:
38+
- Buttons, links, inputs, checkboxes, switches, radios, selects, comboboxes
39+
- Dropdown menus, popovers, dialogs, drawers, tooltips
40+
- Tabs, accordions, tables/treegrids, row actions, pagination
41+
- Keyboard-only interactions and focus traps
42+
4. List states worth scanning:
43+
- Default
44+
- Populated data
45+
- Empty data
46+
- Loading
47+
- Error/validation visible
48+
- Disabled/read-only
49+
- Selected/expanded/open
50+
- Modal/dropdown/popover open
51+
- Mobile viewport when layout changes
52+
53+
## Choose Test Layer
54+
55+
### Jest + axe
56+
57+
Use Jest when the changed surface is a primitive or component that can be rendered in jsdom without full app routing.
58+
59+
Good for:
60+
61+
- `src/frontend/src/components/ui/*`
62+
- Reusable form fields, buttons, inputs, dialogs, popovers, tables
63+
- Local component states: loading, disabled, error, selected, invalid
64+
- Accessible name/role assertions
65+
66+
Pattern:
67+
68+
```tsx
69+
import { render, screen } from "@testing-library/react";
70+
import { axe } from "@/utils/a11y-test";
71+
72+
describe("Component accessibility", () => {
73+
it("should_have_no_axe_violations", async () => {
74+
const { container } = render(<Component />);
75+
76+
expect(await axe(container)).toHaveNoViolations();
77+
});
78+
79+
it("should_expose_accessible_name", () => {
80+
render(<Component />);
81+
82+
expect(screen.getByRole("button", { name: "Save Flow" })).toBeInTheDocument();
83+
});
84+
});
85+
```
86+
87+
Notes:
88+
89+
- Use `src/frontend/src/utils/a11y-test.ts`; it disables `color-contrast` because jsdom cannot check real layout.
90+
- Prefer role/label/text queries. Use test ids only when semantic queries are not stable enough.
91+
- Test accessible names explicitly when labels are visually hidden, loading, icon-only, or generated from props.
92+
- Add a11y tests next to component tests: `__tests__/<name>.a11y.test.tsx`.
93+
94+
Command:
95+
96+
```bash
97+
cd src/frontend
98+
npx jest path/to/<name>.a11y.test.tsx --runInBand
99+
```
100+
101+
### Playwright + `page.runA11yScan`
102+
103+
Use Playwright when the changed surface needs real browser layout, routing, app state, data mocking, modals, tables, focus order, keyboard behavior, or full-page interactions.
104+
105+
Good for:
106+
107+
- Routes and pages
108+
- Settings pages
109+
- Flow canvas workflows
110+
- Tables/treegrids and row selection
111+
- Dialogs, dropdowns, popovers, navigation sidebars
112+
- Any issue involving tab order, focus trap, viewport layout, or real CSS
113+
114+
Pattern:
115+
116+
```ts
117+
import { expect, test } from "../fixtures";
118+
import { awaitBootstrapTest } from "../utils/await-bootstrap-test";
119+
120+
test.describe("Feature route accessibility", () => {
121+
test(
122+
"scans populated state",
123+
{ tag: ["@release", "@api"] },
124+
async ({ page }) => {
125+
await awaitBootstrapTest(page, { skipModal: true });
126+
await page.goto("/settings/example");
127+
await expect(page.getByText("Example")).toBeVisible();
128+
129+
await page.runA11yScan("settings-example-populated");
130+
},
131+
);
132+
});
133+
```
134+
135+
Rules:
136+
137+
- Put focused route/state specs under `src/frontend/tests/a11y/<feature>.a11y.spec.ts`.
138+
- Import `test` and `expect` from `../fixtures`, not `@playwright/test`.
139+
- Every test must include `@release` plus valid domain tag(s): `@workspace`, `@api`, `@database`, `@components`, `@starter-projects`.
140+
- Use stable scan names: lowercase, feature-first, state-specific.
141+
- Mock API responses for deterministic empty/populated/error states.
142+
- Disable animations if timing or focus assertions are flaky.
143+
- Use explicit interactions. Do not randomly crawl or click arbitrary destructive controls.
144+
- If route belongs in static route coverage, update `scripts/a11y/a11y_routes.json` with stable `ready` check.
145+
146+
Commands:
147+
148+
```bash
149+
cd src/frontend
150+
RUN_A11Y=true npx playwright test tests/a11y/<feature>.a11y.spec.ts --project=chromium --workers=1
151+
npm run a11y:html-report --silent
152+
npm run a11y:job-summary --silent
153+
```
154+
155+
To assert against checker baselines:
156+
157+
```bash
158+
cd src/frontend
159+
RUN_A11Y=true RUN_A11Y_ASSERT=true npx playwright test tests/a11y/<feature>.a11y.spec.ts --project=chromium --workers=1
160+
```
161+
162+
## Route/Page Coverage Matrix
163+
164+
For route or page changes, cover the smallest matrix that represents real user states:
165+
166+
- Loaded/default page
167+
- Empty data state
168+
- Populated data state
169+
- Primary modal open
170+
- Dropdown/popover open
171+
- Validation or error visible
172+
- Selected/expanded table row or bulk action state
173+
- Mobile viewport when responsive layout changes
174+
175+
Do not force every state if the page cannot enter it. Do explain skipped states briefly.
176+
177+
## PR #13953 Pattern
178+
179+
Use `src/frontend/tests/a11y/api-keys.a11y.spec.ts` as model for route-level work:
180+
181+
- Mock API data.
182+
- Scan populated table.
183+
- Scan empty table.
184+
- Open create modal and scan.
185+
- Submit form to generated-result modal and scan.
186+
- Open text-cell modal and scan.
187+
- Select table row and scan selected state.
188+
- Set mobile viewport and scan responsive state.
189+
190+
This is the expected bar for data-rich routes.
191+
192+
## Manual Code Review Checklist
193+
194+
While reading changed UI code, check:
195+
196+
- Interactive icon-only controls have accessible names.
197+
- Inputs have labels or valid `aria-labelledby`/`aria-label`.
198+
- Visible label and accessible name stay aligned.
199+
- Dialogs have title/description and trap/restore focus.
200+
- Dropdown/popover content is reachable by keyboard and closes with Escape.
201+
- Custom buttons/links use semantic elements where possible.
202+
- Disabled controls are not focusable unless intentionally discoverable.
203+
- Tables/treegrids have one logical tab stop and no duplicate row/header tab stops.
204+
- Pagination/disabled table controls are not in tab order.
205+
- Error text is associated with inputs.
206+
- Loading state keeps useful accessible name.
207+
- `aria-hidden` does not hide focusable descendants.
208+
- New user-facing labels go through i18n in every locale.
209+
210+
## Report Back
211+
212+
When done, state:
213+
214+
- Test files added or updated.
215+
- States scanned.
216+
- Commands run.
217+
- Any states not covered and why.
218+
- Any remaining a11y risk.

.github/workflows/typescript_test.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,48 @@ jobs:
472472
name: html-report--attempt-${{ github.run_attempt }}
473473
path: playwright-report
474474
retention-days: 14
475+
476+
flaky-report:
477+
name: Flaky Test Report
478+
needs: setup-and-test
479+
# Runs on green too: flaky tests (failed -> passed on retry) only show up
480+
# in runs that end green, which the merge-reports job above skips.
481+
if: ${{ always() && needs.setup-and-test.result != 'skipped' && needs.setup-and-test.result != 'cancelled' }}
482+
runs-on: ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['runs-on'])) || inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }}
483+
# Metrics only — must never turn CI red.
484+
continue-on-error: true
485+
steps:
486+
- name: Setup Node.js
487+
uses: actions/setup-node@v6
488+
with:
489+
node-version: ${{ env.NODE_VERSION }}
490+
491+
- name: Download blob reports from GitHub Actions Artifacts
492+
# A single shard artifact failing its download (transient artifact
493+
# storage errors, seen in practice) must not void the whole
494+
# measurement — merge whatever did download.
495+
continue-on-error: true
496+
uses: actions/download-artifact@v7
497+
with:
498+
path: all-blob-reports
499+
pattern: blob-report-*
500+
merge-multiple: true
501+
502+
- name: Merge into JSON report
503+
env:
504+
PLAYWRIGHT_JSON_OUTPUT_NAME: flaky-report.json
505+
# Pinned to the repo's Playwright version: blob format is
506+
# version-sensitive and bare npx would fetch latest.
507+
run: |
508+
if ! ls ./all-blob-reports/*.zip >/dev/null 2>&1; then
509+
echo "No blob reports downloaded; skipping flaky report."
510+
exit 0
511+
fi
512+
npx playwright@1.60.0 merge-reports --reporter json ./all-blob-reports
513+
514+
- name: Upload flaky report
515+
uses: actions/upload-artifact@v6
516+
with:
517+
name: flaky-report--attempt-${{ github.run_attempt }}
518+
path: flaky-report.json
519+
retention-days: 30

docker/build_and_push.Dockerfile

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ RUN microdnf install -y tar xz python3.14-devel \
4141
&& NODE_VERSION="22.14.0" \
4242
&& curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz" \
4343
| tar -xJ -C /usr/local --strip-components=1 \
44-
&& npm install -g npm@latest \
4544
&& microdnf clean all
4645

4746
# Copy files first to avoid permission issues with bind mounts
@@ -106,8 +105,7 @@ RUN ARCH=$(uname -m) \
106105
| head -1) \
107106
&& if [ -z "$NODE_VERSION" ]; then echo "ERROR: Could not determine Node.js version" && exit 1; fi \
108107
&& curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz" \
109-
| tar -xJ -C /usr/local --strip-components=1 \
110-
&& npm install -g npm@latest
108+
| tar -xJ -C /usr/local --strip-components=1
111109
RUN useradd user -u 1000 -g 0 --no-create-home --home-dir /app/data
112110

113111
COPY --from=builder --chown=1000 /app/.venv /app/.venv
@@ -125,6 +123,19 @@ ENV BASH_ENV="" \
125123
# /app/langflow/secret_key. See https://github.com/langflow-ai/langflow/issues/10437
126124
RUN mkdir -p /app/langflow && chown -R 1000:0 /app/langflow && chmod -R g+rwX /app/langflow
127125

126+
# Give the runtime user (uid 1000) a writable npm cache. The image ships Node so
127+
# users can spawn stdio MCP servers via `npx`, but on the ubi10 base
128+
# HOME=/opt/app-root/src is not owned by uid 1000, so npx otherwise fails with
129+
# `EACCES` on ~/.npm/_cacache and every stdio MCP server registers but never
130+
# lists any tools (toolsCount stays null). Pin npm's cache to a
131+
# uid-1000-owned dir (immune to the base image's HOME) and hand ownership of the
132+
# default HOME cache to the runtime user as a fallback.
133+
# See https://github.com/langflow-ai/langflow/pull/13893 (ubi10 base change).
134+
ENV NPM_CONFIG_CACHE=/app/.npm
135+
RUN mkdir -p /app/.npm /opt/app-root/src/.npm \
136+
&& chown -R 1000:0 /app/.npm /opt/app-root/src/.npm \
137+
&& chmod -R g+rwX /app/.npm /opt/app-root/src/.npm
138+
128139
LABEL org.opencontainers.image.title=langflow
129140
LABEL org.opencontainers.image.authors=['Langflow']
130141
LABEL org.opencontainers.image.licenses=MIT

docker/build_and_push_base.Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,18 @@ ENV BASH_ENV="" \
127127
# /app/langflow/secret_key. See https://github.com/langflow-ai/langflow/issues/10437
128128
RUN mkdir -p /app/langflow && chown -R 1000:0 /app/langflow && chmod -R g+rwX /app/langflow
129129

130+
# Give the runtime user (uid 1000) a writable npm cache. The image ships Node so
131+
# users can spawn stdio MCP servers via `npx`, but on the ubi10 base
132+
# HOME=/opt/app-root/src is not owned by uid 1000, so npx otherwise fails with
133+
# `EACCES` on ~/.npm/_cacache and stdio MCP servers never list any tools. Pin
134+
# npm's cache to a uid-1000-owned dir (immune to the base image's HOME) and hand
135+
# ownership of the default HOME cache to the runtime user as a fallback.
136+
# See https://github.com/langflow-ai/langflow/pull/13893 (ubi10 base change).
137+
ENV NPM_CONFIG_CACHE=/app/.npm
138+
RUN mkdir -p /app/.npm /opt/app-root/src/.npm \
139+
&& chown -R 1000:0 /app/.npm /opt/app-root/src/.npm \
140+
&& chmod -R g+rwX /app/.npm /opt/app-root/src/.npm
141+
130142
LABEL org.opencontainers.image.title=langflow
131143
LABEL org.opencontainers.image.authors=['Langflow']
132144
LABEL org.opencontainers.image.licenses=MIT

docker/build_and_push_with_extras.Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ ENV BASH_ENV="" \
115115
# /app/langflow/secret_key. See https://github.com/langflow-ai/langflow/issues/10437
116116
RUN mkdir -p /app/langflow && chown -R 1000:0 /app/langflow && chmod -R g+rwX /app/langflow
117117

118+
# Give the runtime user (uid 1000) a writable npm cache. The image ships Node so
119+
# users can spawn stdio MCP servers via `npx`, but on the ubi10 base
120+
# HOME=/opt/app-root/src is not owned by uid 1000, so npx otherwise fails with
121+
# `EACCES` on ~/.npm/_cacache and stdio MCP servers never list any tools. Pin
122+
# npm's cache to a uid-1000-owned dir (immune to the base image's HOME) and hand
123+
# ownership of the default HOME cache to the runtime user as a fallback.
124+
# See https://github.com/langflow-ai/langflow/pull/13893 (ubi10 base change).
125+
ENV NPM_CONFIG_CACHE=/app/.npm
126+
RUN mkdir -p /app/.npm /opt/app-root/src/.npm \
127+
&& chown -R 1000:0 /app/.npm /opt/app-root/src/.npm \
128+
&& chmod -R g+rwX /app/.npm /opt/app-root/src/.npm
129+
118130
LABEL org.opencontainers.image.title=langflow
119131
LABEL org.opencontainers.image.authors=['Langflow']
120132
LABEL org.opencontainers.image.licenses=MIT

docs/docs/Deployment/deployment-multi-worker.mdx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,17 @@ See [Troubleshoot multi-worker deployments](./troubleshoot#multi-worker-deployme
273273
| `LANGFLOW_REDIS_QUEUE_POLLING_WATCHDOG_INTERVAL_S` | `15.0` | How often in seconds the watchdog scans for stale jobs. Lower values reclaim resources faster at the cost of more Redis reads. |
274274
| `LANGFLOW_GUNICORN_PRELOAD` | `False` | **Experimental.** Loads the app in the Gunicorn master process before workers fork, reducing per-worker startup overhead. Pairs well with `LANGFLOW_WORKERS`. Non-Windows only. |
275275

276+
:::note
277+
Rate limit counters are stored _per-process_ by default.
278+
To share counters across all workers, point `LANGFLOW_RATE_LIMIT_STORAGE_URI` at the same Redis instance:
279+
280+
```text
281+
LANGFLOW_RATE_LIMIT_STORAGE_URI=redis://your-redis-host:6379/2
282+
```
283+
284+
For more information, see [Login rate limiting](/api-keys-and-authentication#login-rate-limiting).
285+
:::
286+
276287
## Monitor the job queue
277288

278289
The `GET /monitor/job_queue` endpoint returns a metrics snapshot for the running worker. It requires superuser authentication and returns HTTP 403 otherwise.

0 commit comments

Comments
 (0)