Commit Graph

3996 Commits

Author SHA1 Message Date
rabbitblood
595aa3681d chore: move spike/ → docs/spikes/ — keep explorations out of repo root
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 16:09:12 -07:00
Molecule AI Frontend Engineer
17be61d10b fix(canvas): align SkillsTab aria-label with spec — "Install from source URL"
Corrects the source-input aria-label wording to match the UIUX Cycle 4
spec exactly. Previous commit used "Install plugin from source URL";
spec says "Install from source URL" (matches the visible "Install from
source" section heading). Updates the corresponding test assertions.

No functional change. All 736 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:06:21 +00:00
Molecule AI Frontend Engineer
b57a8fa62b fix(canvas): align SkillsTab aria-label with spec — "Install from source URL"
Corrects the source-input aria-label wording to match the UIUX Cycle 4
spec exactly. Previous commit used "Install plugin from source URL";
spec says "Install from source URL" (matches the visible "Install from
source" section heading). Updates the corresponding test assertions.

No functional change. All 736 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:06:21 +00:00
Molecule AI Frontend Engineer
eba6e3a3de fix(canvas): expand a11y htmlFor/aria-label to SkillsTab, FilesTab, ChannelsTab, ScheduleTab (issue #856)
WCAG 1.3.1 fixes for 4 remaining tabs identified in UIUX Cycle 4 audit:

- SkillsTab: aria-label="Install plugin from source URL" on bare source input
- FilesTab: aria-label="New file path" on bare new-file input
- ChannelsTab: useId() + htmlFor/id pairs for Platform, Bot Token,
  Chat IDs, and Allowed Users label↔input associations (4 pairs)
- ScheduleTab: aria-label="Schedule name" on bare name input;
  useId() + htmlFor/id pairs for Cron Expression, Timezone,
  and Prompt/Task label↔control associations (3 pairs)
- DetailsTab: fix ReactElement<{ id?: string }> cast in Field
  component to resolve React 19 TypeScript overload error

Adds 14 new WCAG tests in tabs.a11y.test.tsx covering all above fixes.
No visual change. All 736 tests pass. Build clean.

Closes #856

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:01:43 +00:00
Molecule AI Frontend Engineer
d9177a4cf4 fix(canvas): expand a11y htmlFor/aria-label to SkillsTab, FilesTab, ChannelsTab, ScheduleTab (issue #856)
WCAG 1.3.1 fixes for 4 remaining tabs identified in UIUX Cycle 4 audit:

- SkillsTab: aria-label="Install plugin from source URL" on bare source input
- FilesTab: aria-label="New file path" on bare new-file input
- ChannelsTab: useId() + htmlFor/id pairs for Platform, Bot Token,
  Chat IDs, and Allowed Users label↔input associations (4 pairs)
- ScheduleTab: aria-label="Schedule name" on bare name input;
  useId() + htmlFor/id pairs for Cron Expression, Timezone,
  and Prompt/Task label↔control associations (3 pairs)
- DetailsTab: fix ReactElement<{ id?: string }> cast in Field
  component to resolve React 19 TypeScript overload error

Adds 14 new WCAG tests in tabs.a11y.test.tsx covering all above fixes.
No visual change. All 736 tests pass. Build clean.

Closes #856

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:01:43 +00:00
Molecule AI Backend Engineer
c1b4dca5b7 fix(security): redact secrets from commit_memory payloads (#834)
Add _redact_secrets() in builtin_tools/security.py and apply it at every
commit_memory call site before content reaches the memories table.

Patterns scrubbed (replaced with [REDACTED]):
- sk-[A-Za-z0-9_-]{20,}          OpenAI/Anthropic keys (sk-, sk-ant-, sk-proj-)
- ghp_[A-Za-z0-9]{36}            GitHub classic PAT
- ghs_[A-Za-z0-9]{36}            GitHub server-to-server token
- github_pat_[A-Za-z0-9_]{82}    GitHub fine-grained PAT
- AKIA[0-9A-Z]{16}               AWS access key ID
- key/token/secret/password/api_key=<40+ chars>  Generic contextual (value replaced,
  keyword preserved: "api_key=[REDACTED]" not "[REDACTED]")

Call sites wired:
- builtin_tools/memory.py::commit_memory()     — LangChain tool (LangGraph path)
- a2a_tools.py::tool_commit_memory()           — MCP server path
- executor_helpers.py::commit_memory()         — CLI/SDK executor path

Implementation guarantees:
- Pure function (no side effects, no I/O)
- Idempotent: [REDACTED] does not match any pattern
- No false positives on normal prose (all patterns require ≥20-char prefix
  or ≥40-char value after known keyword)

Tests (36 passing):
- Per-pattern unit tests for all 6 secret types
- Idempotency tests
- Normal prose non-regression tests
- Integration: a2a_tools.tool_commit_memory scrubs ghp_ tokens before HTTP POST
- Integration: executor_helpers.commit_memory scrubs AWS keys and OpenAI keys
- Source inspection: memory.py imports and applies _redact_secrets before
  build_awareness_client() (i.e. before any storage operation)

conftest.py updated to load the real builtin_tools/security.py so that
executor_helpers and a2a_tools can import _redact_secrets during test collection.

Closes #834
Sub-issue of #725

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 22:43:50 +00:00
Molecule AI Backend Engineer
fc6c7a63b9 fix(security): redact secrets from commit_memory payloads (#834)
Add _redact_secrets() in builtin_tools/security.py and apply it at every
commit_memory call site before content reaches the memories table.

Patterns scrubbed (replaced with [REDACTED]):
- sk-[A-Za-z0-9_-]{20,}          OpenAI/Anthropic keys (sk-, sk-ant-, sk-proj-)
- ghp_[A-Za-z0-9]{36}            GitHub classic PAT
- ghs_[A-Za-z0-9]{36}            GitHub server-to-server token
- github_pat_[A-Za-z0-9_]{82}    GitHub fine-grained PAT
- AKIA[0-9A-Z]{16}               AWS access key ID
- key/token/secret/password/api_key=<40+ chars>  Generic contextual (value replaced,
  keyword preserved: "api_key=[REDACTED]" not "[REDACTED]")

Call sites wired:
- builtin_tools/memory.py::commit_memory()     — LangChain tool (LangGraph path)
- a2a_tools.py::tool_commit_memory()           — MCP server path
- executor_helpers.py::commit_memory()         — CLI/SDK executor path

Implementation guarantees:
- Pure function (no side effects, no I/O)
- Idempotent: [REDACTED] does not match any pattern
- No false positives on normal prose (all patterns require ≥20-char prefix
  or ≥40-char value after known keyword)

Tests (36 passing):
- Per-pattern unit tests for all 6 secret types
- Idempotency tests
- Normal prose non-regression tests
- Integration: a2a_tools.tool_commit_memory scrubs ghp_ tokens before HTTP POST
- Integration: executor_helpers.commit_memory scrubs AWS keys and OpenAI keys
- Source inspection: memory.py imports and applies _redact_secrets before
  build_awareness_client() (i.e. before any storage operation)

conftest.py updated to load the real builtin_tools/security.py so that
executor_helpers and a2a_tools can import _redact_secrets during test collection.

Closes #834
Sub-issue of #725

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 22:43:50 +00:00
Molecule AI Frontend Engineer
8611d38638 fix(canvas): resolve TypeScript errors exposed by incremental cache invalidation
- WorkspaceNode.eject.test.tsx: add draggable/selectable/deletable to
  NodeProps render call (TS2739); add `as WorkspaceNodeData` cast on
  makeNodeData return to silence Partial<> spread widening (TS2322)

The cherry-picked fix/canvas-test-fixture-budgetlimit commit (9e0aa61)
also lands here — it resolves latent test-fixture drift in 7 test files
that the incremental tsc cache had masked on main but that became visible
once the new WorkspaceNode.eject.test.tsx file invalidated the cache.

tsc --noEmit: 0 errors | npm test: 726 passed | npm run build: clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 22:41:16 +00:00
Molecule AI Frontend Engineer
6a328646e2 fix(canvas): resolve TypeScript errors exposed by incremental cache invalidation
- WorkspaceNode.eject.test.tsx: add draggable/selectable/deletable to
  NodeProps render call (TS2739); add `as WorkspaceNodeData` cast on
  makeNodeData return to silence Partial<> spread widening (TS2322)

The cherry-picked fix/canvas-test-fixture-budgetlimit commit (fef664d)
also lands here — it resolves latent test-fixture drift in 7 test files
that the incremental tsc cache had masked on main but that became visible
once the new WorkspaceNode.eject.test.tsx file invalidated the cache.

tsc --noEmit: 0 errors | npm test: 726 passed | npm run build: clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 22:41:16 +00:00
Molecule AI Frontend Engineer
9e0aa61837 fix(canvas): add missing budgetLimit/budget_limit to test fixtures, fix AuthGate mock types
The budget PR (#541) added budgetLimit: number | null as a required field
on WorkspaceNodeData and budget_limit: number | null on WorkspaceData.
Seven test fixture factories were not updated, causing tsc --noEmit to
produce 34 TS2322/TS2345 errors (runtime tests still passed because
Vitest transpiles via esbuild which strips types).

Fixes:
- canvas-events.test.ts: makeNode factory +budgetLimit: null
- canvas-events-pan.test.ts: makeNode factory +budgetLimit: null
- canvas-capabilities.test.ts: makeNodeData factory +budgetLimit: null
- canvas-topology.test.ts: makeWS factory +budget_limit: null
- canvas.test.ts: makeWS factory +budget_limit: null; two inline
  summarizeWorkspaceCapabilities args +budgetLimit: null; context-menu
  fixture +budgetLimit: null
- ProvisioningTimeout.test.tsx: makeWS factory +budget_limit: null

Also fixes 3 TS2348 errors in AuthGate.test.tsx: newer Vitest type defs
resolve ReturnType<typeof vi.fn> to Mock<Procedure|Constructable> which
TypeScript no longer considers directly callable in a vi.mock factory.
Fix: intersect the mock variables with a plain function type so both the
call expression and the mock API (mockReturnValue etc.) type-check.

tsc --noEmit: 0 errors. npm test: 722/722.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 22:39:54 +00:00
Molecule AI Frontend Engineer
fef664d6d0 fix(canvas): add missing budgetLimit/budget_limit to test fixtures, fix AuthGate mock types
The budget PR (#541) added budgetLimit: number | null as a required field
on WorkspaceNodeData and budget_limit: number | null on WorkspaceData.
Seven test fixture factories were not updated, causing tsc --noEmit to
produce 34 TS2322/TS2345 errors (runtime tests still passed because
Vitest transpiles via esbuild which strips types).

Fixes:
- canvas-events.test.ts: makeNode factory +budgetLimit: null
- canvas-events-pan.test.ts: makeNode factory +budgetLimit: null
- canvas-capabilities.test.ts: makeNodeData factory +budgetLimit: null
- canvas-topology.test.ts: makeWS factory +budget_limit: null
- canvas.test.ts: makeWS factory +budget_limit: null; two inline
  summarizeWorkspaceCapabilities args +budgetLimit: null; context-menu
  fixture +budgetLimit: null
- ProvisioningTimeout.test.tsx: makeWS factory +budget_limit: null

Also fixes 3 TS2348 errors in AuthGate.test.tsx: newer Vitest type defs
resolve ReturnType<typeof vi.fn> to Mock<Procedure|Constructable> which
TypeScript no longer considers directly callable in a vi.mock factory.
Fix: intersect the mock variables with a plain function type so both the
call expression and the mock API (mockReturnValue etc.) type-check.

tsc --noEmit: 0 errors. npm test: 722/722.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 22:39:54 +00:00
molecule-ai[bot]
5d7e58ff5b Merge pull request #840 from Molecule-AI/feat/issue-800-opencode-mcp-bridge
feat(platform): opencode MCP bridge — remote A2A tools over HTTP (#800)
2026-04-17 22:15:38 +00:00
molecule-ai[bot]
18cb498bca
Merge pull request #840 from Molecule-AI/feat/issue-800-opencode-mcp-bridge
feat(platform): opencode MCP bridge — remote A2A tools over HTTP (#800)
2026-04-17 22:15:38 +00:00
molecule-ai[bot]
55f719f737 chore: sync opencode.md with main (conflict resolution post PR#842 merge)
PR#842 merged the docs/opencode.json to main with the correct MCP URL path.
PR#840 branch had an older version — sync to main's content to resolve conflict.
2026-04-17 22:14:59 +00:00
molecule-ai[bot]
9bce00d856
chore: sync opencode.md with main (conflict resolution post PR#842 merge)
PR#842 merged the docs/opencode.json to main with the correct MCP URL path.
PR#840 branch had an older version — sync to main's content to resolve conflict.
2026-04-17 22:14:59 +00:00
molecule-ai[bot]
1649808c09 chore: sync opencode.json with main (conflict resolution post PR#842 merge)
PR#842 merged the docs/opencode.json to main with the correct MCP URL path.
PR#840 branch had an older version — sync to main's content to resolve conflict.
2026-04-17 22:14:57 +00:00
molecule-ai[bot]
00e3753f37
chore: sync opencode.json with main (conflict resolution post PR#842 merge)
PR#842 merged the docs/opencode.json to main with the correct MCP URL path.
PR#840 branch had an older version — sync to main's content to resolve conflict.
2026-04-17 22:14:57 +00:00
molecule-ai[bot]
bbb2f1b847 fix(mcp): add TODO(#838) in toolCommitMemory + document X-Workspace-ID trust in toolDelegateTask
Security Auditor pre-merge conditions for PR#840:

C5: toolCommitMemory passes content directly to DB insert without secret
redaction. Gap is tracked to #838 (platform-wide _redactSecrets pass).
Adds inline TODO(#838) comment at the insert site so the gap is visible
in-code, not only in the issue tracker.

C6: toolDelegateTask sets X-Workspace-ID but no bearer token on the
outbound A2A call. The /workspaces/:id/a2a route is intentionally outside
WorkspaceAuth (by design in router.go). CanCommunicate is enforced before
the request is constructed, and callerID was authenticated by WorkspaceAuth
on the MCP bridge entry point. Documents this trust assumption at the call
site.
2026-04-17 22:13:55 +00:00
molecule-ai[bot]
c5a1318de8
fix(mcp): add TODO(#838) in toolCommitMemory + document X-Workspace-ID trust in toolDelegateTask
Security Auditor pre-merge conditions for PR#840:

C5: toolCommitMemory passes content directly to DB insert without secret
redaction. Gap is tracked to #838 (platform-wide _redactSecrets pass).
Adds inline TODO(#838) comment at the insert site so the gap is visible
in-code, not only in the issue tracker.

C6: toolDelegateTask sets X-Workspace-ID but no bearer token on the
outbound A2A call. The /workspaces/:id/a2a route is intentionally outside
WorkspaceAuth (by design in router.go). CanCommunicate is enforced before
the request is constructed, and callerID was authenticated by WorkspaceAuth
on the MCP bridge entry point. Documents this trust assumption at the call
site.
2026-04-17 22:13:55 +00:00
molecule-ai[bot]
d1855863e9 Merge pull request #842 from Molecule-AI/feat/issue-813-814-opencode-template
feat(opencode): org-template + integration guide for remote MCP auth (closes #813, closes #814)
2026-04-17 22:12:10 +00:00
molecule-ai[bot]
d898b4f7bc
Merge pull request #842 from Molecule-AI/feat/issue-813-814-opencode-template
feat(opencode): org-template + integration guide for remote MCP auth (closes #813, closes #814)
2026-04-17 22:12:10 +00:00
molecule-ai[bot]
a0425903c7 fix(opencode): update URL example in opencode.md + add WORKSPACE_ID env var
The inline JSON example still showed the bare ${MOLECULE_MCP_URL} without
the /workspaces/${WORKSPACE_ID}/mcp path. Updated to match opencode.json fix
in previous commit (bf80f15). Added WORKSPACE_ID to the env section.
2026-04-17 22:06:37 +00:00
molecule-ai[bot]
4f8837cc20
fix(opencode): update URL example in opencode.md + add WORKSPACE_ID env var
The inline JSON example still showed the bare ${MOLECULE_MCP_URL} without
the /workspaces/${WORKSPACE_ID}/mcp path. Updated to match opencode.json fix
in previous commit (9542348). Added WORKSPACE_ID to the env section.
2026-04-17 22:06:37 +00:00
molecule-ai[bot]
bf80f15619 fix(opencode): add full MCP path to opencode.json URL
Security Auditor FINDING-1: bare ${MOLECULE_MCP_URL} missing the router path.
Fix adds /workspaces/${WORKSPACE_ID}/mcp so opencode reaches MCPHandler.
Unblocks PR#842 merge.
2026-04-17 22:06:05 +00:00
molecule-ai[bot]
9542348ebf
fix(opencode): add full MCP path to opencode.json URL
Security Auditor FINDING-1: bare ${MOLECULE_MCP_URL} missing the router path.
Fix adds /workspaces/${WORKSPACE_ID}/mcp so opencode reaches MCPHandler.
Unblocks PR#842 merge.
2026-04-17 22:06:05 +00:00
rabbitblood
9ef314fef5 fix(slack): tables as monospace blocks + ASCII dividers + strikethrough
Tables: Slack has no table syntax. Converter now detects markdown tables
and renders them as monospace code blocks with aligned columns.

Dividers: replaced unicode em-dash (caused encoding artifacts) with
plain ASCII dashes.

Strikethrough: ~~text~~ converts to ~text~ (Slack native).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 15:01:46 -07:00
rabbitblood
a6ba22d8ec fix(slack): tables as monospace blocks + ASCII dividers + strikethrough
Tables: Slack has no table syntax. Converter now detects markdown tables
and renders them as monospace code blocks with aligned columns.

Dividers: replaced unicode em-dash (caused encoding artifacts) with
plain ASCII dashes.

Strikethrough: ~~text~~ converts to ~text~ (Slack native).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 15:01:46 -07:00
rabbitblood
9b1139ee90 fix(slack): restore FetchChannelHistory — was lost during branch juggling
The function was defined on a feature branch, referenced by manager.go
and slack_test.go, but never made it to main after the rebase. This
caused go build to fail with 'undefined: FetchChannelHistory', which
Docker masked by using a cached binary from the last successful build.

That cached binary had neither the mrkdwn blocks nor the Level 3
context injection — explaining why Slack messages showed raw markdown
despite the source having the converter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:55:53 -07:00
rabbitblood
ea574723df fix(slack): restore FetchChannelHistory — was lost during branch juggling
The function was defined on a feature branch, referenced by manager.go
and slack_test.go, but never made it to main after the rebase. This
caused go build to fail with 'undefined: FetchChannelHistory', which
Docker masked by using a cached binary from the last successful build.

That cached binary had neither the mrkdwn blocks nor the Level 3
context injection — explaining why Slack messages showed raw markdown
despite the source having the converter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:55:53 -07:00
Molecule AI Frontend Engineer
ee07380ae0 fix(canvas): dynamic aria-label + title on TeamMemberChip eject button (issue #854)
- EjectIcon now accepts React.SVGProps<SVGSVGElement> so aria-hidden can be passed
- Eject button: aria-label and title both use `Extract ${data.name} from team`
  (previously title was static 'Extract from team'; aria-label was absent)
- <EjectIcon aria-hidden="true"> prevents assistive tech from double-announcing
  the icon content inside the already-labelled button
- Added WorkspaceNode.eject.test.tsx (4 tests) covering aria-label, title,
  label==title invariant, and aria-hidden on the SVG

Closes #854

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:54:51 +00:00
Molecule AI Frontend Engineer
8d1bbd56f2 fix(canvas): dynamic aria-label + title on TeamMemberChip eject button (issue #854)
- EjectIcon now accepts React.SVGProps<SVGSVGElement> so aria-hidden can be passed
- Eject button: aria-label and title both use `Extract ${data.name} from team`
  (previously title was static 'Extract from team'; aria-label was absent)
- <EjectIcon aria-hidden="true"> prevents assistive tech from double-announcing
  the icon content inside the already-labelled button
- Added WorkspaceNode.eject.test.tsx (4 tests) covering aria-label, title,
  label==title invariant, and aria-hidden on the SVG

Closes #854

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:54:51 +00:00
Molecule AI Backend Engineer
210c6b5b2c fix(security): allowlist-based env sanitization for LocalPythonExecutor (#826)
Replace denylist approach with strict allowlist: only PATH, HOME, LANG,
PYTHONPATH, WORKSPACE_ID, WORKSPACE_NAME, PLATFORM_URL (and a small set
of locale/Python runtime vars) pass through to agent-executed code.  Every
other env var — including ANTHROPIC_API_KEY, GH_TOKEN, DATABASE_URL,
REDIS_URL, *_SECRET, *_PASSWORD — is stripped from os.environ for the
duration of SafeLocalPythonExecutor.__call__ and restored on exit.

- make_safe_env() is a pure read (never mutates os.environ)
- _ENV_PATCH_LOCK serialises concurrent calls for thread safety
- os.environ fully restored even on exception (try/finally)
- 38 unit tests covering all secret categories, thread safety, import
  restrictions, and env-restore guarantees

Closes #826
Sub-issue of #804

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:54:11 +00:00
Molecule AI Backend Engineer
054226e39f fix(security): allowlist-based env sanitization for LocalPythonExecutor (#826)
Replace denylist approach with strict allowlist: only PATH, HOME, LANG,
PYTHONPATH, WORKSPACE_ID, WORKSPACE_NAME, PLATFORM_URL (and a small set
of locale/Python runtime vars) pass through to agent-executed code.  Every
other env var — including ANTHROPIC_API_KEY, GH_TOKEN, DATABASE_URL,
REDIS_URL, *_SECRET, *_PASSWORD — is stripped from os.environ for the
duration of SafeLocalPythonExecutor.__call__ and restored on exit.

- make_safe_env() is a pure read (never mutates os.environ)
- _ENV_PATCH_LOCK serialises concurrent calls for thread safety
- os.environ fully restored even on exception (try/finally)
- 38 unit tests covering all secret categories, thread safety, import
  restrictions, and env-restore guarantees

Closes #826
Sub-issue of #804

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:54:11 +00:00
rabbitblood
230cf693c6 fix(slack): use blocks API for mrkdwn rendering + restore Level 3
Slack's chat.postMessage renders the text field as plain text when
username override is used. Switching to blocks with type=mrkdwn
forces rich formatting (bold, links, code, dividers).

Also restores FetchWorkspaceChannelContext that was lost in rebase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:47:07 -07:00
rabbitblood
e3ada13adf fix(slack): use blocks API for mrkdwn rendering + restore Level 3
Slack's chat.postMessage renders the text field as plain text when
username override is used. Switching to blocks with type=mrkdwn
forces rich formatting (bold, links, code, dividers).

Also restores FetchWorkspaceChannelContext that was lost in rebase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:47:07 -07:00
molecule-ai[bot]
37d0b3005f fix(canvas): a11y — keyboard access, role=alert, close label, ProvisioningTimeout (#830 #831 #832 #833)
Closes #830, Closes #831, Closes #832, Closes #833

QA-approved (verified via A2A relay — QA token-blocked). All 4 fixes confirmed against local source:
- #830: role=alert + aria-live=assertive on error elements (MemoryInspectorPanel)
- #831: TeamMemberChip role=button + tabIndex + aria-label + onKeyDown Enter/Space (WorkspaceNode)
- #832: aria-label='Close workspace panel' + aria-hidden on SVG (SidePanel)
- #833: ProvisioningTimeout uncommented and mounted in Canvas tree

731/731 tests pass, build clean, use client check clean.
2026-04-17 21:44:17 +00:00
molecule-ai[bot]
c50d83ecf0
fix(canvas): a11y — keyboard access, role=alert, close label, ProvisioningTimeout (#830 #831 #832 #833)
Closes #830, Closes #831, Closes #832, Closes #833

QA-approved (verified via A2A relay — QA token-blocked). All 4 fixes confirmed against local source:
- #830: role=alert + aria-live=assertive on error elements (MemoryInspectorPanel)
- #831: TeamMemberChip role=button + tabIndex + aria-label + onKeyDown Enter/Space (WorkspaceNode)
- #832: aria-label='Close workspace panel' + aria-hidden on SVG (SidePanel)
- #833: ProvisioningTimeout uncommented and mounted in Canvas tree

731/731 tests pass, build clean, use client check clean.
2026-04-17 21:44:17 +00:00
rabbitblood
e415dfb60e fix(slack): restore mrkdwn converter + FetchWorkspaceChannelContext after rebase
Both were lost during the PR #844 rebase — the converter was in the
source but the binary couldn't compile because FetchWorkspaceChannelContext
was missing from manager.go (interface mismatch). Previous deploys
silently used the cached old binary without the converter.

Also removed unused 'log' import that blocked compilation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:38:53 -07:00
rabbitblood
a3579d92b2 fix(slack): restore mrkdwn converter + FetchWorkspaceChannelContext after rebase
Both were lost during the PR #844 rebase — the converter was in the
source but the binary couldn't compile because FetchWorkspaceChannelContext
was missing from manager.go (interface mismatch). Previous deploys
silently used the cached old binary without the converter.

Also removed unused 'log' import that blocked compilation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:38:53 -07:00
Molecule AI Frontend Engineer
20bc92a2a9 fix(canvas): add missing budgetLimit/budget_limit to test fixtures, fix AuthGate mock types
The budget PR (#541) added budgetLimit: number | null as a required field
on WorkspaceNodeData and budget_limit: number | null on WorkspaceData.
Seven test fixture factories were not updated, causing tsc --noEmit to
produce 34 TS2322/TS2345 errors (runtime tests still passed because
Vitest transpiles via esbuild which strips types).

Fixes:
- canvas-events.test.ts: makeNode factory +budgetLimit: null
- canvas-events-pan.test.ts: makeNode factory +budgetLimit: null
- canvas-capabilities.test.ts: makeNodeData factory +budgetLimit: null
- canvas-topology.test.ts: makeWS factory +budget_limit: null
- canvas.test.ts: makeWS factory +budget_limit: null; two inline
  summarizeWorkspaceCapabilities args +budgetLimit: null; context-menu
  fixture +budgetLimit: null
- ProvisioningTimeout.test.tsx: makeWS factory +budget_limit: null

Also fixes 3 TS2348 errors in AuthGate.test.tsx: newer Vitest type defs
resolve ReturnType<typeof vi.fn> to Mock<Procedure|Constructable> which
TypeScript no longer considers directly callable in a vi.mock factory.
Fix: intersect the mock variables with a plain function type so both the
call expression and the mock API (mockReturnValue etc.) type-check.

tsc --noEmit: 0 errors. npm test: 722/722.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:37:50 +00:00
Molecule AI Frontend Engineer
1c4247002a fix(canvas): add missing budgetLimit/budget_limit to test fixtures, fix AuthGate mock types
The budget PR (#541) added budgetLimit: number | null as a required field
on WorkspaceNodeData and budget_limit: number | null on WorkspaceData.
Seven test fixture factories were not updated, causing tsc --noEmit to
produce 34 TS2322/TS2345 errors (runtime tests still passed because
Vitest transpiles via esbuild which strips types).

Fixes:
- canvas-events.test.ts: makeNode factory +budgetLimit: null
- canvas-events-pan.test.ts: makeNode factory +budgetLimit: null
- canvas-capabilities.test.ts: makeNodeData factory +budgetLimit: null
- canvas-topology.test.ts: makeWS factory +budget_limit: null
- canvas.test.ts: makeWS factory +budget_limit: null; two inline
  summarizeWorkspaceCapabilities args +budgetLimit: null; context-menu
  fixture +budgetLimit: null
- ProvisioningTimeout.test.tsx: makeWS factory +budget_limit: null

Also fixes 3 TS2348 errors in AuthGate.test.tsx: newer Vitest type defs
resolve ReturnType<typeof vi.fn> to Mock<Procedure|Constructable> which
TypeScript no longer considers directly callable in a vi.mock factory.
Fix: intersect the mock variables with a plain function type so both the
call expression and the mock API (mockReturnValue etc.) type-check.

tsc --noEmit: 0 errors. npm test: 722/722.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:37:50 +00:00
Hongming Wang
2d9083b155 Merge pull request #851 from Molecule-AI/fix/slack-mrkdwn-formatting
fix(slack): convert Markdown → mrkdwn before posting
2026-04-17 14:27:17 -07:00
Hongming Wang
4abf58826f
Merge pull request #851 from Molecule-AI/fix/slack-mrkdwn-formatting
fix(slack): convert Markdown → mrkdwn before posting
2026-04-17 14:27:17 -07:00
rabbitblood
72cb636692 fix(slack): convert Markdown to mrkdwn before posting
Agents output standard Markdown (Claude Code default) but Slack uses
its own mrkdwn format. Without conversion:
  **bold** shows as literal **bold**
  ### heading shows as literal ###
  [text](url) shows as raw markdown link

Converter handles:
  **bold** → *bold* (Slack bold is single asterisk)
  ### heading → *heading* (bold text, no headings in Slack)
  [text](url) → <url|text> (Slack link format)
  --- → ——— (visual separator)
  `code` and ```blocks``` pass through unchanged

6 new tests: bold, heading, link, hr, code block, mixed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:26:41 -07:00
rabbitblood
1de7e5788a fix(slack): convert Markdown to mrkdwn before posting
Agents output standard Markdown (Claude Code default) but Slack uses
its own mrkdwn format. Without conversion:
  **bold** shows as literal **bold**
  ### heading shows as literal ###
  [text](url) shows as raw markdown link

Converter handles:
  **bold** → *bold* (Slack bold is single asterisk)
  ### heading → *heading* (bold text, no headings in Slack)
  [text](url) → <url|text> (Slack link format)
  --- → ——— (visual separator)
  `code` and ```blocks``` pass through unchanged

6 new tests: bold, heading, link, hr, code block, mixed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:26:41 -07:00
Molecule AI Frontend Engineer
c49616292d fix(canvas): add role=alert and focus-return to delete confirm in DetailsTab
Two WCAG violations in the Danger Zone delete flow:

1. WCAG 4.1.3 (Status Messages): the confirmation UI that appears when
   the user clicks "Delete Workspace" had no ARIA live region, so screen
   readers never announced the confirmation prompt. Adding role="alert"
   to the confirmation container makes it an implicit assertive live
   region that is announced immediately.

2. WCAG 2.4.3 (Focus Order): pressing Cancel left focus wherever the
   browser placed it (often body). Keyboard users had to re-navigate to
   find the Delete Workspace button. The Cancel handler now calls
   deleteButtonRef.current?.focus() to return focus to the trigger
   button, matching the expected modal/disclosure focus-management pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:18:05 +00:00
Molecule AI Frontend Engineer
22b7d69f63 fix(canvas): add role=alert and focus-return to delete confirm in DetailsTab
Two WCAG violations in the Danger Zone delete flow:

1. WCAG 4.1.3 (Status Messages): the confirmation UI that appears when
   the user clicks "Delete Workspace" had no ARIA live region, so screen
   readers never announced the confirmation prompt. Adding role="alert"
   to the confirmation container makes it an implicit assertive live
   region that is announced immediately.

2. WCAG 2.4.3 (Focus Order): pressing Cancel left focus wherever the
   browser placed it (often body). Keyboard users had to re-navigate to
   find the Delete Workspace button. The Cancel handler now calls
   deleteButtonRef.current?.focus() to return focus to the trigger
   button, matching the expected modal/disclosure focus-management pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:18:05 +00:00
Molecule AI Frontend Engineer
c32334c8db fix(canvas): add ARIA landmark and live region to OnboardingWizard
WCAG 1.3.1 / 4.1.3: the onboarding card had no landmark role and no
live region, so screen readers had no way to know the card exists or
that the step changed.

- Add role="complementary" aria-label="Onboarding guide" to the card
  container so it appears as a named landmark in assistive technology.
- Add a role="status" aria-live="polite" aria-atomic="true" sr-only div
  that holds the current step label. When the step state changes React
  updates the div content, which the live region broadcasts to the AT
  without pulling focus away from the user's current position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:17:32 +00:00
Molecule AI Frontend Engineer
2d9dd08ec2 fix(canvas): add ARIA landmark and live region to OnboardingWizard
WCAG 1.3.1 / 4.1.3: the onboarding card had no landmark role and no
live region, so screen readers had no way to know the card exists or
that the step changed.

- Add role="complementary" aria-label="Onboarding guide" to the card
  container so it appears as a named landmark in assistive technology.
- Add a role="status" aria-live="polite" aria-atomic="true" sr-only div
  that holds the current step label. When the step state changes React
  updates the div content, which the live region broadcasts to the AT
  without pulling focus away from the user's current position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:17:32 +00:00
Molecule AI Frontend Engineer
3cee4e1859 fix(canvas): add aria-label to Toolbar buttons and status pills
NVDA and other screen readers ignore the title attribute on interactive
elements and non-interactive divs. Add aria-label alongside title on:
- Stop All button (dynamic label reflects active task count)
- Restart All button (dynamic label reflects pending workspace count)
- StatusPill component (online/offline/failed/provisioning counts)
- WsStatusPill component (connected/connecting/disconnected variants)

Inner dot and text spans get aria-hidden="true" so the screen reader
reads the single aria-label rather than individual child nodes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 21:17:05 +00:00