CI's `npm ci` failed because the previous lock was generated on macOS
arm64, which omits the Linux-specific optional deps that
@tailwindcss/postcss → lightningcss-linux-x64-gnu transitively need
(@emnapi/runtime, @emnapi/core).
Re-ran `npm install --include=optional` so the lock includes every
platform variant of lightningcss + the @emnapi packages they pull in.
Runner (Linux x64) now has what it needs; local macOS install still
fine (npm picks the matching binary at install time).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consolidates the remaining safe-to-merge dependabot PRs from the
2026-04-28 wave into one consumable PR. Replaces three earlier
single-bump PRs (#2245, #2230, #2231) which were closed in favor of
this single batch — same pattern as #2235.
GitHub Actions majors (SHA-pinned per org convention):
github/codeql-action v3 → v4.35.2 (#2228)
actions/setup-node v4 → v6.4.0 (#2218)
actions/upload-artifact v4 → v7.0.1 (#2216)
actions/setup-python v5 → v6.2.0 (#2214)
npm dev deps (canvas/, lockfile regenerated in node:22-bookworm
container so @emnapi/* and other Linux-only optional deps are
properly resolved — Mac-native `npm install` strips them, which
caused the earlier #2235 batch to drop these two):
@types/node ^22 → ^25.6 (#2231)
jsdom ^25 → ^29.1 (#2230)
Why each is safe
setup-node v4 → v6 / setup-python v5 → v6:
Every consumer call pins node-version / python-version
explicitly. v5 / v6 changed defaults but pinned consumers
are unaffected. Confirmed via grep across .github/workflows/
— all setup-node call sites pin '20' or '22', all
setup-python call sites pin '3.11'.
codeql-action v3 → v4.35.2:
Used as init/autobuild/analyze sub-actions in codeql.yml.
v4 bundles a newer CodeQL CLI; ubuntu-latest auto-updates
so functional behavior is unchanged. The deprecated
CODEQL_ACTION_CLEANUP_TRAP_CACHES env var (per v4.35.2
release notes) is undocumented and we don't set it.
upload-artifact v4 → v7.0.1:
v6 introduced Node.js 24 runtime requiring Actions Runner
>= 2.327.1. All upload-artifact users (codeql.yml,
e2e-staging-canvas.yml) run on `ubuntu-latest` (GitHub-
hosted), which auto-updates the runner agent. Self-hosted
runners are NOT used for these jobs.
@types/node 22 → 25 / jsdom 25 → 29:
Both are dev-only — @types/node is type definitions,
jsdom backs vitest's DOM environment. Tests pass:
79 files / 1154 tests in node:22-bookworm container.
Verified locally (Linux container so the lockfile reflects what
CI's `npm ci` will install):
- cd canvas && npm install --include=optional → 169 packages
- npm test → 1154/1154 pass
- npm ci → clean install succeeds
- npm run build → Next.js prerendering succeeds
Closes when this lands (the 3 individual auto-merge PRs from earlier
were closed):
#2228#2218#2216#2214#2231#2230
NOT included (CI failing on dependabot's own run — major framework
bumps that need code-side migration tasks, not safe auto-bumps):
#2233 next 15 → 16
#2232 tailwindcss 3 → 4
#2226 typescript 5 → 6
The first commit on this branch left the lockfile inconsistent for
Node 20's npm 10:
npm error \`npm ci\` can only install packages when your package.json
and package-lock.json are in sync. Please update your lock file...
npm error Missing: @emnapi/runtime@1.10.0 from lock file
npm error Missing: @emnapi/core@1.10.0 from lock file
Root cause: my local install ran on Node 24 / npm 11, which doesn't
write peer-optional transitive entries (@img/sharp-* declares
@emnapi/runtime as peerOptional). The Canvas tabs E2E job uses Node 20
/ npm 10, which DOES expect those entries and rejected the lockfile
with EUSAGE.
Regenerated the lockfile under Node 20.19.4 (matches the lowest CI
node version, lockfile is forward-compatible with 22 and 24). 6 new
@emnapi/* entries added; postcss stays at 8.5.12 (the original goal
of this branch).
Verification:
- \`nvm use 20 && npm ci\` clean
- 1148/1148 vitest pass under Node 20
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the medium-severity dependabot alert on canvas/package-lock.json.
Upstream advisory GHSA-qx2v-qp2m-jg93: "PostCSS has XSS via Unescaped
</style> in its CSS Stringify Output" — fixed in 8.5.10. We pull
8.5.12 since it's already published in the ^8.5.10 line.
package.json's caret range bumps from ^8.4.0 to ^8.5.12 — wider floor
prevents a future install from re-pinning below the safe version. The
8.x major-line constraint is preserved, so no breaking-change risk.
Verification: full canvas vitest suite passes (1148/1148 across
78 files).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[Molecule-Platform-Evolvement-Manager]
Closes step 1+2 of #1815. Step 3 (CI gate + threshold) is split into
a follow-up because today's baseline is ~46% lines / ~45% statements,
not the 70% the issue's draft thresholds assumed.
## What this lands
- `canvas/vitest.config.ts` — `coverage` block with v8 provider,
reporters: text (terminal) / html (./coverage/index.html) /
json-summary (machine-readable for tooling). NO threshold —
pure observability.
- `canvas/package.json` — adds `test:coverage` script
(`vitest run --coverage`); existing `test` script is unchanged so
the default workflow is identical.
- `canvas/package-lock.json` — adds @vitest/coverage-v8@^4.1.5 (the
v8 provider Vitest uses for native coverage).
## Why no threshold yet
Issue draft threshold was 70%/70%/65%/70% (lines/funcs/branches/stmts).
Local baseline today:
```
Statements : 45.19% (3248/7186)
Branches : 39.87% (2034/5101)
Functions : 40.99% (724/1766)
Lines : 46.36% (2905/6265)
```
Turning on a 70% gate today would either fail CI immediately or get
papered over with an ad-hoc exclude list. Better path: land
observability now, run coverage in PR review for any new code
(via the new script), gate later when the baseline catches up.
## Heatmap (from local run, top gaps)
- `src/lib/runtime-names.ts` — 0% (untouched by tests)
- `src/lib/utils.ts` — 0%
- `src/lib/canvas-actions.ts` — 25%
- `src/store/classNames.ts` — 17%
- `src/store/canvas.ts` — 73% (already-tested but the largest absolute
gap by lines)
Each is a concrete follow-up issue / PR target.
## Test plan
- [x] `npx vitest run --coverage` runs cleanly locally (~10s) and
produces `./coverage/index.html` + a `coverage-summary.json`
- [x] Existing `npm run test` workflow unchanged — instrumentation
only activates with `--coverage` flag
- [x] No production-code changes — pure tooling addition
## Follow-ups (each tracked separately; this PR keeps minimal scope)
- Step 3a — write tests for the 0% files above (~tiny each)
- Step 3b — once baseline ≥ thresholds, add `thresholds` block to
vitest.config.ts + a `npm run test:coverage` step in
`.github/workflows/ci.yml`'s Canvas job
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(canvas/test): restore test regressions from PR #1243
PR #1243 introduced two regressions in the canvas vitest suite:
1. ContextMenu.keyboard.test.tsx: the setPendingDelete call now
passes `{hasChildren, id, name}` (not just `{id, name}`). Updated
the keyboard-a11y test assertion to match the new store shape.
2. orgs-page.test.tsx: mockFetch.mockResolvedValueOnce() returned a
plain object that didn't match the two-argument (url, options)
call signature used by the component's fetch wrapper. Switched to
mockImplementationOnce returning a rejected Promise — matching
real fetch's rejection contract — and added runAllTimersAsync after
advanceTimersByTimeAsync(50) to flush React state updates.
54 test files · 813 tests · all passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(canvas): replace bounding-box intersection with distance threshold for nest detection
ReactFlow's getIntersectingNodes uses bounding-box overlap detection, which
fires the drag-over state whenever any part of two nodes' position rectangles
overlap — even when the dragged node is far from the target. This made the
"Nest Workspace" dialog appear from large distances.
Fix: scan all nodes on each drag tick and set dragOverNodeId to the closest
node within NEST_PROXIMITY_THRESHOLD (150 px, center-to-center). This matches
the intuitive behavior: nest only when the node is actually dropped near another.
Constants:
- NEST_PROXIMITY_THRESHOLD = 150px (~60% of a collapsed node's width)
- DEFAULT_NODE_WIDTH = 245px (mid-range of min/max node widths)
- DEFAULT_NODE_HEIGHT = 110px
Also removed the unused getIntersectingNodes import (was causing duplicate
identifier error when both onNodeDrag and the zoom handler called useReactFlow
in the same component scope).
Closes#1052.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(canvas): cascade-delete UX — show child count and require checkbox before Delete All
Issue #1137: with ?confirm=true always sent, a single confirmation silently
cascades — a team lead with 20 children gets nuked on one click.
Changes:
- store/canvas.ts: pendingDelete type now includes children: {id, name}[]
- ContextMenu.tsx: passes child list to setPendingDelete on Delete click
- DeleteCascadeConfirmDialog.tsx: new component — shows child names, a
cascade warning, and requires the operator to tick a checkbox before
Delete All activates. Disabled by default; only enables after checkbox.
- Canvas.tsx: conditionally renders DeleteCascadeConfirmDialog for
hasChildren workspaces, or plain ConfirmDialog for leaf workspaces.
confirmDelete requires cascadeConfirmChecked=true when hasChildren.
- ContextMenu.keyboard.test.tsx: updated setPendingDelete assertion to
include children:[] (no children in the test fixture).
813 tests pass.
Closes#1137.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Molecule AI Core-UIUX <core-uiux@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sets up shadcn/ui CLI so new components can be added with
`npx shadcn add <component>`. Uses new-york style, zinc base color,
no CSS variables (matches existing Tailwind-only approach).
Adds clsx + tailwind-merge for the cn() utility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses the three release-blocking WCAG violations from the UX audit
(3rd consecutive cycle) and the new ChatTab ARIA gap from Audit #2.
Changes:
- Toaster: split into polite (success/info) + assertive (error) live
regions, both always in DOM so screen readers register them before
any toast fires. Adds x dismiss button on every toast. Errors no
longer auto-expire after 4s — persist until explicitly dismissed.
- ConfirmDialog: on open, requestAnimationFrame focuses the first
button inside the dialog. Tab/Shift-Tab is now trapped inside the
dialog while open. Added role="dialog" aria-modal="true" and
aria-labelledby pointing to the title h3.
- WorkspaceNode: outer div gains role="button", tabIndex={0},
aria-label, aria-pressed, and onKeyDown (Enter/Space => selectNode,
ContextMenu key => openContextMenu). Keyboard-only users can now
reach and activate workspace nodes.
- ChatTab sub-tab bar: role="tablist" on wrapper, role="tab" +
aria-selected + aria-controls on each button, matching
role="tabpanel" + id on each panel div. Textarea gets
aria-label="Message to agent".
453/453 Vitest tests pass. Production build clean (Next.js 15).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Walks the real UI end-to-end:
1. Creates + registers a workspace on the platform
2. Opens the detail side panel
3. Clicks the Files tab (force-click since it's in an overflow-x bar)
4. Asserts all 3 split components render:
- FilesToolbar: "+ New" + "Upload" buttons
- FileTree: the config.yaml seeded by the default template
- FileEditor: "Select a file to edit" empty-state
Saves screenshots at /tmp/filestab-{1,2,3}-*.png for manual review.
Run: cd canvas && npx playwright test e2e/filestab-smoke.spec.ts
Requires platform on :8080 + canvas on :3000.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>