## Description Let users manage external-app and MCP action permissions in the Plugins UI. Scheduled tasks use the same controls and keep independent app selections and policies. Workspace-disabled actions remain locked. External-app admin tabs follow the MCP flow: Configuration, Authentication, and Tools, followed by Skills where supported. Configuration contains the user-management switch. Authentication contains credentials and custom headers. Tools contains individual action policies. Bulk changes use a dropdown; each action uses Auto-approve, Ask, and Disabled buttons. Plugin modals fit their content up to 80% of the viewport. Scrollbars do not shift the tabs or move content off center. MCP descriptions appear below tool names. Save requires changes, and closing an edited form asks for confirmation. Each opening loads fresh settings; refreshes preserve the open draft. Policy links open the matching app's permissions tab. Remove the superseded standalone policy editors. Keep personally disabled MCP servers in permission catalogs while preserving runtime restrictions. ## How Has This Been Tested? - This PR's intermediate tree passed 233 frontend tests across 14 suites after rebasing onto main. - Commit hooks passed, including TypeScript, formatting, lint, and feature-map integrity. - Earlier local Kind/browser checks covered external-app and MCP saves, workspace restrictions, bulk edits, reset, dirty-form dismissal, failed saves, retries, desktop/mobile sizing, and independent browser-tab saves. ## Additional Options - [ ] [Optional] Please cherry-pick this PR to the latest release version. - [x] [Optional] Override Linear Check <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Manage approval settings for connected Craft-compatible apps and MCP servers from plugin details. Search actions, apply bulk Auto-approve, Ask, or Disabled choices, and reset to workspace defaults. * Task editors offer the same per-action and bulk controls, with independent task settings and confirmation before discarding unsaved changes. * App configuration lets administrators control whether users can manage approval settings. Editor tabs separate Configuration, Authentication, Tools, and Skills. * Connected app setup cards link directly to approval settings. * **Improvements** * MCP tool descriptions are visible inline, and plugin editors use larger, scrollable modals. * Workspace-disabled actions remain visible but locked with an explanation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Opal
Onyx's TypeScript component library and design system.
Install
bun add @onyx-ai/opal
Peer dependencies (install whichever the lib actually exercises in your usage):
bun add react react-dom next \
@radix-ui/react-popover \
@radix-ui/react-slot @radix-ui/react-tooltip \
@dnd-kit/core @dnd-kit/sortable @dnd-kit/modifiers @dnd-kit/utilities \
@tanstack/react-table formik \
react-markdown remark-gfm rehype-sanitize
Setup
1. Import the design tokens once
In your app's root entry (e.g. Next.js app/layout.tsx):
import "@onyx-ai/opal/styles.css";
The CSS file defines the custom properties (--text-01, --background-neutral-00, etc.) that
the Tailwind preset references.
2. Wire up the Tailwind preset
In your tailwind.config.js:
module.exports = {
presets: [require("@onyx-ai/opal/tailwind-preset")],
content: [
"./src/**/*.{ts,tsx}",
"./node_modules/@onyx-ai/opal/dist/**/*.{js,mjs}",
],
};
The content glob ensures Tailwind picks up the classes used inside Opal components.
You also need to define the underlying CSS variables (--text-01, etc.) in your own
colors.css or import a copy from Onyx. The preset references them but does not define them —
they live with the consumer so the consumer controls the palette.
Usage
import { Button, Text } from "@onyx-ai/opal/components";
import { Content } from "@onyx-ai/opal/layouts";
import SvgPlus from "@onyx-ai/opal/icons/plus";
function MyComponent() {
return (
<Content
icon={SvgPlus}
title="Hello"
description="World"
sizePreset="main-ui"
variant="section"
/>
);
}
Subpath imports
| Subpath | Contents |
|---|---|
@onyx-ai/opal/components |
Buttons, Text, Tag, Tooltip, Popover, Table, etc. |
@onyx-ai/opal/layouts |
Content, ContentAction, IllustrationContent, Section |
@onyx-ai/opal/core |
Interactive primitives, Hoverable, Disabled |
@onyx-ai/opal/icons |
SVG icon components |
@onyx-ai/opal/illustrations |
Larger SVG illustrations |
@onyx-ai/opal/types |
Shared types (RichStr, IconProps, etc.) |
@onyx-ai/opal/utils |
cn, markdown helpers |
@onyx-ai/opal/styles.css |
Bundled component CSS |
@onyx-ai/opal/tailwind-preset |
Tailwind preset with tokens |
Structure
web/lib/opal/
├── src/
│ ├── core/ # Low-level primitives (Interactive, Hoverable, Disabled)
│ ├── components/ # High-level components (Button, Popover, Tooltip, Table, ...)
│ ├── layouts/ # Layout primitives (Content, ContentAction, Section, ...)
│ ├── icons/ # SVG icon components
│ ├── illustrations/ # Larger SVG illustrations
│ ├── logos/ # Brand / product logos
│ ├── types.ts # Shared types (RichStr, IconProps, etc.)
│ ├── utils.ts # cn, markdown helpers
│ ├── shared.ts
│ └── root.css # Library-owned design tokens
├── scripts/
│ └── bundle-css.mjs # Concatenates root.css + leaf component CSS into dist/styles.css
├── package.json
├── tsconfig.json # Source typecheck config
├── tsconfig.build.json # Used by tsdown to emit dist/
├── tsdown.config.ts
├── tailwind-preset.cjs
└── README.md
Local development (inside the Onyx repo)
Opal reuses /web/node_modules — it does not have its own node_modules. To add a runtime
dependency, declare it under peerDependencies in web/lib/opal/package.json AND add the
matching version in the root web/package.json dependencies block, then run bun install in /web
so Onyx's web app keeps building.
The package is consumed by web/ as a workspace via web/package.json's "@onyx-ai/opal": "./lib/opal". During Onyx development, web/ resolves Opal source through the @opal/*
TypeScript path alias (defined in web/tsconfig.json), so changes are picked up live without
running bun run build.
To produce the published artifact:
cd web/lib/opal
bun run build # tsdown -> dist/, then bundle-css.mjs -> dist/styles.css
Releasing to npm
Releases go out through the Release Opal GitHub Actions workflow
(.github/workflows/release-opal.yml). It uses npm OIDC Trusted Publishers — no
NPM_TOKEN, signed provenance attestation. Pushing a tag is the only thing that triggers a
release.
Steps:
-
Bump
versioninweb/lib/opal/package.json(semver:MAJOR.MINOR.PATCH, prerelease suffix-rc.Nallowed). -
Open a PR with the version bump and any release-shaped changes. Merge it.
-
From
main, tag and push:git switch main && git pull git tag opal/v0.1.1 git push origin opal/v0.1.1 -
The workflow runs automatically on tag push. It builds (
tsdown+ CSS barrel) and runsbun publish --provenance --access public. Watch the run under the Actions tab; verify the new version on https://www.npmjs.com/package/@onyx-ai/opal.
The tag pattern must match opal/v*.*.* for the workflow to fire.
Conventions
- Component directories are kebab-case (e.g.
select-button/,open-button/,content-action/). - Each component dir contains
components.tsx,README.md,styles.css(when needed), and a<PascalName>.stories.tsx(when applicable). - Imports inside the lib use the
@opal/path alias; never@/. - Types/interfaces are declared at the top of
components.tsxwithoutexport; everything is re-exported from a singleexport { Foo, type FooProps };block at the bottom. - See
web/AGENTS.mdfor broader frontend standards.
Third-party trademarks
The @onyx-ai/opal/logos subpath ships brand marks of third-party
products Onyx integrates with. Marks remain the property of their
respective owners; Onyx claims no trademark over them. See
NOTICE.md for details.