mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
feat(web): enhance TasksView and plugin preview components with new features and styles
- Updated `TasksView` to include a title row with a "Coming soon" label and added a preview note for user guidance. - Enhanced `DesignSystemSurface` to fetch and display design system showcases, improving visual representation in the plugin home section. - Refactored `PreviewSurface` to pass the `inView` prop to `DesignSystemSurface`, optimizing rendering behavior. - Improved CSS styles for tasks and design system components, ensuring a cohesive and visually appealing layout. This update significantly enhances user experience by providing clearer information and improved visual elements in the tasks and plugin previews.
This commit is contained in:
parent
9f4e76d507
commit
3d6d434f11
10 changed files with 266 additions and 53 deletions
|
|
@ -187,9 +187,12 @@ export function TasksView({ config, onOpenOrbitSettings }: Props) {
|
|||
<header className="tasks-view__hero">
|
||||
<div>
|
||||
<p className="tasks-view__kicker">Automation workspace</p>
|
||||
<h1 id="tasks-title" className="entry-section__title">
|
||||
Tasks
|
||||
</h1>
|
||||
<div className="tasks-view__title-row">
|
||||
<h1 id="tasks-title" className="entry-section__title">
|
||||
Tasks
|
||||
</h1>
|
||||
<span className="tasks-view__coming-soon">Coming soon</span>
|
||||
</div>
|
||||
<p className="tasks-view__lede">
|
||||
Tasks turn prompts into durable work: Orbit runs them, routines keep
|
||||
them around, schedules decide when they fire, and live artifacts show
|
||||
|
|
@ -206,6 +209,14 @@ export function TasksView({ config, onOpenOrbitSettings }: Props) {
|
|||
</button>
|
||||
</header>
|
||||
|
||||
<div className="tasks-view__preview-note" role="note">
|
||||
<Icon name="orbit" size={14} />
|
||||
<span>
|
||||
Preview surface only. Orbit settings are available today; routines,
|
||||
schedules, and live artifact wiring will land as the backend branches merge.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="tasks-primitives" aria-label="Task primitives">
|
||||
<PrimitiveCard
|
||||
icon="orbit"
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
// referenced design system (`/api/design-systems/:slug/showcase`)
|
||||
// - Tokens tab — the palette / typography / components inspector
|
||||
// (`/api/design-systems/:slug/preview`)
|
||||
// - DESIGN.md sidebar — the raw spec served from the plugin's own
|
||||
// bundled asset (`/api/plugins/:id/asset/DESIGN.md`)
|
||||
// - Plugin info sidebar — manifest metadata first, with the raw
|
||||
// DESIGN.md spec included as a section underneath
|
||||
// (`/api/plugins/:id/asset/DESIGN.md`)
|
||||
//
|
||||
// Falls back gracefully when the plugin does not reference an
|
||||
// upstream design system (some bundles ship DESIGN.md only): the
|
||||
|
|
@ -106,8 +107,9 @@ export function PluginDesignSystemDetail({
|
|||
|
||||
// When no upstream design system is referenced we still need a view
|
||||
// for the iframe stage so PreviewModal has something to render. Fall
|
||||
// back to a minimal placeholder that explains the spec lives in the
|
||||
// sidebar; the user can still apply the plugin from the primary CTA.
|
||||
// back to a minimal placeholder that explains the design spec lives
|
||||
// in the plugin-info sidebar; the user can still apply the plugin
|
||||
// from the primary CTA.
|
||||
const views: PreviewView[] = dsRef
|
||||
? [
|
||||
{ id: 'showcase', label: t('ds.showcase'), html: showcaseHtml },
|
||||
|
|
@ -117,7 +119,7 @@ export function PluginDesignSystemDetail({
|
|||
{
|
||||
id: 'spec',
|
||||
label: 'Spec',
|
||||
html: '<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;color:#666;display:flex;align-items:center;justify-content:center;height:100vh;text-align:center;padding:0 24px;margin:0;">This plugin ships only the DESIGN.md spec — open the sidebar to read it.</body>',
|
||||
html: '<!doctype html><meta charset="utf-8"><body style="font:14px system-ui;color:#666;display:flex;align-items:center;justify-content:center;height:100vh;text-align:center;padding:0 24px;margin:0;">This plugin ships only the design spec — open Plugin info to read DESIGN.md.</body>',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -131,22 +133,16 @@ export function PluginDesignSystemDetail({
|
|||
exportTitleFor={(viewId) => `${record.title} — ${viewId}`}
|
||||
onClose={onClose}
|
||||
sidebar={{
|
||||
label: t('ds.specToggle'),
|
||||
label: 'Plugin info',
|
||||
defaultOpen: true,
|
||||
onToggle: handleSidebarToggle,
|
||||
contentKey: record.id,
|
||||
// DESIGN.md sits at the top so the spec is the first thing users
|
||||
// read; the plugin-common metadata (workflow / context bundles /
|
||||
// connectors / file paths / source provenance) stacks below in
|
||||
// the same scroll container so the design-system modal carries
|
||||
// the full inspector depth the scenario fallback already does.
|
||||
// Design-system plugins are still plugins, so the inspector
|
||||
// comes first. DESIGN.md remains available in the same sidebar,
|
||||
// but as a spec section below the plugin-common metadata.
|
||||
content: (
|
||||
<div className="plugin-design-sidebar">
|
||||
<DesignSpecView
|
||||
source={specBody}
|
||||
loadingLabel={t('ds.specLoading')}
|
||||
/>
|
||||
<div className="plugin-info-pane plugin-design-sidebar__meta">
|
||||
<div className="plugin-info-pane">
|
||||
<PluginMetaSections
|
||||
record={record}
|
||||
omit={{ description: true }}
|
||||
|
|
@ -154,6 +150,16 @@ export function PluginDesignSystemDetail({
|
|||
heading="Plugin info"
|
||||
/>
|
||||
</div>
|
||||
<section className="plugin-design-sidebar__spec">
|
||||
<div className="plugin-design-sidebar__spec-head">
|
||||
<h3>DESIGN.md</h3>
|
||||
<span>{assetPath.replace(/^\.\//, '')}</span>
|
||||
</div>
|
||||
<DesignSpecView
|
||||
source={specBody}
|
||||
loadingLabel={t('ds.specLoading')}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,90 @@
|
|||
// Design-system preview surface — a stylised "X feels like X" tile.
|
||||
// Design-system preview surface — showcase thumbnail with a brand-patch fallback.
|
||||
//
|
||||
// Design-system plugins do not ship a runnable preview entry, so we
|
||||
// synthesise a brand patch from the manifest:
|
||||
// - large serif headline using the bare brand label
|
||||
// - three colour swatches derived deterministically from the
|
||||
// plugin id (so each system gets a stable visual fingerprint)
|
||||
// - subtle typographic specimen ("Aa Bb Cc") for character
|
||||
//
|
||||
// This mirrors the look of the design-systems gallery in the
|
||||
// project view without requiring the daemon to render a real
|
||||
// HTML preview for every system at home-load time.
|
||||
// Most design-system plugins reference an upstream design system in
|
||||
// `od.context.designSystem.ref`. When available, reuse the same
|
||||
// showcase HTML as the detail modal so the home grid reads like real
|
||||
// website thumbnails rather than synthetic color swatches. The fetch
|
||||
// is lazy and cached to keep the 100+ design-system catalog cheap.
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { DesignPreviewSpec } from '../preview';
|
||||
import { fetchDesignSystemShowcase } from '../../../providers/registry';
|
||||
import { buildSrcdoc } from '../../../runtime/srcdoc';
|
||||
|
||||
interface Props {
|
||||
preview: DesignPreviewSpec;
|
||||
inView: boolean;
|
||||
}
|
||||
|
||||
export function DesignSystemSurface({ preview }: Props) {
|
||||
const showcaseCache = new Map<string, string | null>();
|
||||
const showcaseInflight = new Map<string, Promise<string | null>>();
|
||||
|
||||
function fetchCachedShowcase(id: string): Promise<string | null> {
|
||||
const cached = showcaseCache.get(id);
|
||||
if (cached !== undefined) return Promise.resolve(cached);
|
||||
const existing = showcaseInflight.get(id);
|
||||
if (existing) return existing;
|
||||
const run = fetchDesignSystemShowcase(id).then((html) => {
|
||||
showcaseCache.set(id, html);
|
||||
showcaseInflight.delete(id);
|
||||
return html;
|
||||
});
|
||||
showcaseInflight.set(id, run);
|
||||
return run;
|
||||
}
|
||||
|
||||
function useShowcaseHtml(
|
||||
designSystemId: string | null,
|
||||
inView: boolean,
|
||||
): string | null | undefined {
|
||||
const [html, setHtml] = useState<string | null | undefined>(() =>
|
||||
designSystemId ? showcaseCache.get(designSystemId) : undefined,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!designSystemId) {
|
||||
setHtml(undefined);
|
||||
return;
|
||||
}
|
||||
const cached = showcaseCache.get(designSystemId);
|
||||
if (cached !== undefined) {
|
||||
setHtml(cached);
|
||||
return;
|
||||
}
|
||||
if (!inView) return;
|
||||
let cancelled = false;
|
||||
setHtml(null);
|
||||
fetchCachedShowcase(designSystemId).then((next) => {
|
||||
if (!cancelled) setHtml(next);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [designSystemId, inView]);
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
export function DesignSystemSurface({ preview, inView }: Props) {
|
||||
const showcaseHtml = useShowcaseHtml(preview.designSystemId, inView);
|
||||
|
||||
if (showcaseHtml) {
|
||||
return (
|
||||
<div className="plugins-home__design plugins-home__design--showcase">
|
||||
<div className="plugins-home__design-showcase">
|
||||
<iframe
|
||||
title={`${preview.brand} showcase preview`}
|
||||
sandbox="allow-scripts"
|
||||
srcDoc={buildSrcdoc(showcaseHtml)}
|
||||
tabIndex={-1}
|
||||
aria-hidden
|
||||
className="plugins-home__design-iframe"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const [primary, secondary, ink] = preview.swatches;
|
||||
return (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export function PreviewSurface({ pluginId, pluginTitle, preview }: Props) {
|
|||
inView={inView}
|
||||
/>
|
||||
) : preview.kind === 'design' ? (
|
||||
<DesignSystemSurface preview={preview} />
|
||||
<DesignSystemSurface preview={preview} inView={inView} />
|
||||
) : (
|
||||
<TextSurface pluginTitle={pluginTitle} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
// - `html` → sandboxed iframe rendering the plugin's example
|
||||
// output / preview entry (examples + scenarios that
|
||||
// ship a `od.preview.entry` or `exampleOutputs[]`)
|
||||
// - `design` → stylized "X feels like X" tile (design-system
|
||||
// plugins, which do not ship a runnable preview)
|
||||
// - `design` → design-system showcase thumbnail, falling back to
|
||||
// a stylized brand patch when no showcase ref exists
|
||||
// - `text` → fallback layout (other scenario plugins, atoms
|
||||
// that slip through the visiblePlugins filter, …)
|
||||
//
|
||||
|
|
@ -55,6 +55,7 @@ export interface HtmlPreviewSpec {
|
|||
export interface DesignPreviewSpec {
|
||||
kind: 'design';
|
||||
brand: string;
|
||||
designSystemId: string | null;
|
||||
swatches: string[];
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +83,10 @@ interface ExampleOutputEntry {
|
|||
title?: unknown;
|
||||
}
|
||||
|
||||
interface ContextRef {
|
||||
ref?: unknown;
|
||||
}
|
||||
|
||||
function readPreview(record: InstalledPluginRecord): PreviewBlock | null {
|
||||
const od = record.manifest?.od as { preview?: unknown } | undefined;
|
||||
if (!od || typeof od.preview !== 'object' || od.preview === null) return null;
|
||||
|
|
@ -114,6 +119,14 @@ function isDesignSystemPlugin(record: InstalledPluginRecord): boolean {
|
|||
return tags.some((t) => t.toLowerCase() === 'design-system');
|
||||
}
|
||||
|
||||
function designSystemRef(record: InstalledPluginRecord): string | null {
|
||||
const od = record.manifest?.od as
|
||||
| { context?: { designSystem?: ContextRef } }
|
||||
| undefined;
|
||||
const ref = od?.context?.designSystem?.ref;
|
||||
return typeof ref === 'string' && ref.length > 0 ? ref : null;
|
||||
}
|
||||
|
||||
// Synthetic colour swatches derived from the plugin id so cards stay
|
||||
// visually distinct without dragging in the real DESIGN.md content.
|
||||
// Hue is pinned per-plugin (stable across renders) but lightness /
|
||||
|
|
@ -219,6 +232,7 @@ export function inferPluginPreview(
|
|||
return {
|
||||
kind: 'design',
|
||||
brand: brandLabel(record),
|
||||
designSystemId: designSystemRef(record),
|
||||
swatches: deriveSwatches(record),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16225,30 +16225,51 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* Design-system sidebar stacks DESIGN.md prose at the top of the pane
|
||||
and the plugin-info block below; the divider mirrors the dashed
|
||||
separators inside the meta sections so they feel like one column. */
|
||||
/* Design-system sidebar keeps the plugin inspector first, then exposes
|
||||
DESIGN.md as a secondary spec section. This makes design systems read
|
||||
like first-class plugins instead of a special DESIGN.md-only viewer. */
|
||||
.plugin-design-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.plugin-design-sidebar > .design-spec-pre,
|
||||
.plugin-design-sidebar > .design-spec-empty {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.plugin-design-sidebar__meta {
|
||||
.plugin-design-sidebar__spec {
|
||||
border-top: 1px dashed var(--border);
|
||||
padding-top: 8px;
|
||||
padding: 14px 18px 24px;
|
||||
background: var(--bg-panel);
|
||||
}
|
||||
.plugin-design-sidebar__divider {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
.plugin-design-sidebar__spec-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.plugin-design-sidebar__spec-head h3 {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text);
|
||||
}
|
||||
.plugin-design-sidebar__spec-head span {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
padding: 6px 0 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
.plugin-design-sidebar__spec > .design-spec-pre,
|
||||
.plugin-design-sidebar__spec > .design-spec-empty {
|
||||
margin: 0;
|
||||
max-height: 520px;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Plugin info heading — the badge-style header shown by the
|
||||
|
|
|
|||
|
|
@ -556,7 +556,7 @@
|
|||
-webkit-backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
/* Design-system synthesised tile */
|
||||
/* Design-system showcase tile */
|
||||
.plugins-home__design {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
|
@ -568,6 +568,12 @@
|
|||
font-family: var(--serif);
|
||||
overflow: hidden;
|
||||
}
|
||||
.plugins-home__design--showcase {
|
||||
display: block;
|
||||
padding: 0;
|
||||
background: var(--bg-panel);
|
||||
font-family: inherit;
|
||||
}
|
||||
.plugins-home__design::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
|
@ -575,6 +581,29 @@
|
|||
background: linear-gradient(180deg, transparent 60%, rgba(0, 0, 0, 0.18));
|
||||
pointer-events: none;
|
||||
}
|
||||
.plugins-home__design--showcase::before {
|
||||
z-index: 1;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(0, 0, 0, 0) 52%, rgba(0, 0, 0, 0.18) 100%),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0));
|
||||
}
|
||||
.plugins-home__design-showcase {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
background: var(--bg-panel);
|
||||
}
|
||||
.plugins-home__design-iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1280px;
|
||||
height: 960px;
|
||||
border: 0;
|
||||
transform: scale(0.24);
|
||||
transform-origin: 0 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.plugins-home__design-headline {
|
||||
position: relative;
|
||||
font-size: 18px;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,28 @@
|
|||
color: var(--accent-strong, var(--accent));
|
||||
}
|
||||
|
||||
.tasks-view__title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tasks-view__coming-soon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--amber, #b26200);
|
||||
border-radius: 999px;
|
||||
background: var(--amber-bg, #fff3e0);
|
||||
color: var(--amber, #b26200);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tasks-view__lede {
|
||||
margin: 8px 0 0;
|
||||
max-width: 680px;
|
||||
|
|
@ -32,6 +54,25 @@
|
|||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.tasks-view__preview-note {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--amber, #b26200) 22%, var(--border));
|
||||
border-radius: var(--radius);
|
||||
background: color-mix(in srgb, var(--amber-bg, #fff3e0) 70%, var(--bg-panel));
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.tasks-view__preview-note svg {
|
||||
flex: 0 0 auto;
|
||||
margin-top: 1px;
|
||||
color: var(--amber, #b26200);
|
||||
}
|
||||
|
||||
.tasks-view__new,
|
||||
.tasks-list__head button,
|
||||
.task-detail__primary,
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ describe('PluginDetailsModal common metadata coverage', () => {
|
|||
expect(html).toContain('mcp:invoke');
|
||||
});
|
||||
|
||||
it('surfaces meta sections in the design-system sidebar under DESIGN.md', () => {
|
||||
it('surfaces Plugin info first in the design-system sidebar, with DESIGN.md below', () => {
|
||||
const html = render(
|
||||
pluginWithMeta({
|
||||
id: 'ds-with-meta',
|
||||
|
|
@ -260,6 +260,9 @@ describe('PluginDetailsModal common metadata coverage', () => {
|
|||
expect(html).toContain('plugin-meta-sections');
|
||||
expect(html).toContain('plugin-meta-sections__heading');
|
||||
expect(html).toMatch(/<h3[^>]*>Plugin info<\/h3>/);
|
||||
expect(html).toContain('plugin-design-sidebar__spec');
|
||||
expect(html).toContain('DESIGN.md');
|
||||
expect(html.indexOf('Plugin info')).toBeLessThan(html.indexOf('DESIGN.md'));
|
||||
expect(html).toContain('Workflow');
|
||||
expect(html).toContain('Source');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ interface MakeArgs {
|
|||
title?: string;
|
||||
tags?: string[];
|
||||
mode?: string;
|
||||
designSystemRef?: string;
|
||||
preview?: Record<string, unknown>;
|
||||
exampleOutputs?: Array<{ path: string; title?: string }>;
|
||||
}
|
||||
|
|
@ -37,6 +38,9 @@ function make(args: MakeArgs): InstalledPluginRecord {
|
|||
od: {
|
||||
kind: 'scenario',
|
||||
...(args.mode ? { mode: args.mode } : {}),
|
||||
...(args.designSystemRef
|
||||
? { context: { designSystem: { ref: args.designSystemRef } } }
|
||||
: {}),
|
||||
...(args.preview ? { preview: args.preview } : {}),
|
||||
...(args.exampleOutputs
|
||||
? { useCase: { exampleOutputs: args.exampleOutputs } }
|
||||
|
|
@ -123,12 +127,27 @@ describe('inferPluginPreview', () => {
|
|||
expect(out.label).toBe('Weekly');
|
||||
});
|
||||
|
||||
it('renders design-system plugins (mode signal) as design patches with stable swatches', () => {
|
||||
const a = inferPluginPreview(make({ id: 'ds-a', mode: 'design-system', title: 'Airbnb' }));
|
||||
const b = inferPluginPreview(make({ id: 'ds-a', mode: 'design-system', title: 'Airbnb' }));
|
||||
it('renders design-system plugins (mode signal) as showcase-backed design surfaces', () => {
|
||||
const a = inferPluginPreview(
|
||||
make({
|
||||
id: 'ds-a',
|
||||
mode: 'design-system',
|
||||
title: 'Airbnb',
|
||||
designSystemRef: 'airbnb',
|
||||
}),
|
||||
);
|
||||
const b = inferPluginPreview(
|
||||
make({
|
||||
id: 'ds-a',
|
||||
mode: 'design-system',
|
||||
title: 'Airbnb',
|
||||
designSystemRef: 'airbnb',
|
||||
}),
|
||||
);
|
||||
expect(a.kind).toBe('design');
|
||||
if (a.kind !== 'design' || b.kind !== 'design') return;
|
||||
expect(a.brand).toBe('Airbnb');
|
||||
expect(a.designSystemId).toBe('airbnb');
|
||||
expect(a.swatches).toHaveLength(3);
|
||||
expect(a.swatches).toEqual(b.swatches);
|
||||
});
|
||||
|
|
@ -136,6 +155,8 @@ describe('inferPluginPreview', () => {
|
|||
it('treats the design-system tag as a fallback signal when mode is missing', () => {
|
||||
const out = inferPluginPreview(make({ id: 'ds-tag', tags: ['design-system'] }));
|
||||
expect(out.kind).toBe('design');
|
||||
if (out.kind !== 'design') return;
|
||||
expect(out.designSystemId).toBeNull();
|
||||
});
|
||||
|
||||
it('returns text fallback for plain scenario plugins without preview material', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue