diff --git a/maps/feature-map/skills.md b/maps/feature-map/skills.md
index 902649f3781..32d27445937 100644
--- a/maps/feature-map/skills.md
+++ b/maps/feature-map/skills.md
@@ -69,9 +69,11 @@ Manual: Enable the skill for one test agent and leave another without access. Ru
### Project repository source behavior
New GitHub project repositories import skills automatically. Skills → Sources lists
-accessible legacy repositories with Import skills, shows source/project provenance,
-groups matching repository/ref workspaces into one row, and shows queued/importing feedback,
-and provides Refresh and Automatic updates in its source menu. Verify future additions,
+accessible legacy repositories with Import skills, shows each saved repository once with
+its access scope but without project names, and groups matching repository/ref workspaces.
+Additional legacy imports for an existing source live in its More menu. Import progress
+appears in the source metadata; Refresh and Automatic updates are also available.
+Verify future additions,
retained exclusions, error recovery and reload persistence. Private-only sources must
be absent for unrelated people/agents; access through either originating project is sufficient.
diff --git a/tests/storybook-visual/project-repository-skills.spec.ts b/tests/storybook-visual/project-repository-skills.spec.ts
index 01b4a56270c..8377efc9120 100644
--- a/tests/storybook-visual/project-repository-skills.spec.ts
+++ b/tests/storybook-visual/project-repository-skills.spec.ts
@@ -12,6 +12,28 @@ test('groups matching project repositories and keeps progress visible through ad
await expect(page.getByRole('button', { name: 'Import skills', exact: true })).toHaveCount(0);
});
+test('keeps an existing source compact while allowing project import from its menu', async ({ page }, info) => {
+ await page.goto(story('existing-source-more-projects'));
+ const source = page.getByRole('region', { name: 'acme/team-skills', exact: true });
+ await expect(source.getByRole('link', { name: 'acme/team-skills', exact: true })).toHaveCount(1);
+ await expect(source).not.toContainText('Paperclip Cloud');
+ await expect(source).not.toContainText('Sentry');
+ await expect(source).not.toContainText('Other projects with this repo');
+ await expect(page.getByRole('link', { name: 'Installed skills', exact: true })).toHaveCount(0);
+ await expect(source.getByRole('button', { name: 'Import skills', exact: true })).toHaveCount(0);
+ await page.screenshot({ path: info.outputPath('existing-source-more-projects.png') });
+ await source.getByRole('button', { name: 'More actions for acme/team-skills', exact: true }).click();
+ await page.getByRole('menuitem', { name: 'Import project skills', exact: true }).click();
+ await expect(source.getByRole('status')).toContainText('Importing skills');
+ await expect(source.getByRole('status')).toHaveCount(0);
+ await source.getByRole('button', { name: 'More actions for acme/team-skills', exact: true }).click();
+ await expect(page.getByRole('menuitem', { name: /^(Import (remaining )?project skills|Retry project import)$/ })).toHaveCount(0);
+ await page.keyboard.press('Escape');
+ await expect(source).toBeVisible();
+ await expect(source).not.toContainText('Paperclip Cloud');
+ await expect(source).not.toContainText('Sentry');
+});
+
test('repairs a missing branch without prompting for another connection', async ({ page }) => {
await page.goto(story('missing-branch'));
await expect(page.getByRole('button', { name: 'Choose connection', exact: true })).toHaveCount(0);
@@ -102,7 +124,7 @@ test('legacy adoption imports directly and survives navigation within the journe
await expect(source).toBeVisible();
await expect(source.getByText('Code review', { exact: true })).toBeVisible();
await expect(action).toHaveCount(0);
- await page.getByRole('link', { name: 'Installed skills', exact: true }).click();
+ await page.getByLabel('Skills', { exact: true }).getByRole('link', { name: 'Installed', exact: true }).click();
await expect(source).toHaveCount(0);
await expect(page.getByText('Code review', { exact: true }).first()).toBeVisible();
await page.getByLabel('Skills', { exact: true }).getByRole('link', { name: 'Sources', exact: true }).click();
@@ -118,11 +140,24 @@ test('background refresh imports an addition without clicking refresh', async ({
await page.screenshot({ path: info.outputPath('automatic-update.png') });
});
+test('keeps GitHub connection management in the page actions menu', async ({ page }) => {
+ await page.goto(story('synced'));
+ const source = page.getByRole('region', { name: 'acme/team-skills', exact: true });
+ await expect(source.getByRole('link', { name: 'Manage GitHub connection', exact: true })).toHaveCount(0);
+ await page.getByRole('button', { name: 'More source actions', exact: true }).click();
+ const manageConnection = page.getByRole('menuitem', { name: 'Manage GitHub connection', exact: true });
+ await expect(manageConnection).toBeVisible();
+ await expect(manageConnection).toHaveAttribute('href', /\/apps$/);
+ await page.keyboard.press('Escape');
+ await expect(page.getByRole('button', { name: 'More source actions', exact: true })).toBeFocused();
+});
+
test('refresh failure retains content and retry recovers', async ({ page }, info) => {
await page.goto(story('failed-refresh'));
const source = page.getByRole('region', { name: 'acme/team-skills', exact: true });
await expect(source.getByText('Code review', { exact: true })).toBeVisible();
await expect(source.getByRole('alert')).toContainText('installed skills were kept');
+ await page.screenshot({ path: info.outputPath('refresh-error.png') });
await source.getByRole('button', { name: 'Refresh', exact: true }).click();
await expect(source.getByRole('button', { name: 'Refresh', exact: true })).toBeEnabled();
await source.getByRole('button', { name: 'Refresh', exact: true }).click();
diff --git a/ui/src/components/skill-studio/ProjectSkillRepositoryRow.tsx b/ui/src/components/skill-studio/ProjectSkillRepositoryRow.tsx
index e7dca5a04e7..f842acbda57 100644
--- a/ui/src/components/skill-studio/ProjectSkillRepositoryRow.tsx
+++ b/ui/src/components/skill-studio/ProjectSkillRepositoryRow.tsx
@@ -1,15 +1,13 @@
import type { ProjectSkillRepository } from '@paperclipai/shared';
import { GithubIcon } from '@/components/icons/github-icon';
import { Button } from '@/components/ui/button';
-import { Link } from '@/lib/router';
import { LoaderCircle } from 'lucide-react';
-export function ProjectSkillRepositoryRow({ repository, pending, backgroundPending, disabled, showRepository = true, onImport }: {
+export function ProjectSkillRepositoryRow({ repository, pending, backgroundPending, disabled, onImport }: {
repository: ProjectSkillRepository;
pending?: boolean;
backgroundPending?: boolean;
disabled?: boolean;
- showRepository?: boolean;
onImport: () => void;
}) {
const actionPending = pending || repository.status === 'pending';
@@ -18,9 +16,9 @@ export function ProjectSkillRepositoryRow({ repository, pending, backgroundPendi
@@ -30,6 +28,6 @@ export function ProjectSkillRepositoryRow({ repository, pending, backgroundPendi
{importing && {pending ? 'Starting import…' : repository.importState === 'importing'
? 'Checking repository for skills… This can take a few minutes for large repositories.' : 'Import queued. Skills will appear here when ready.'}
}
- {repository.lastError && {repository.lastError} Manage GitHub connection
}
+ {repository.lastError && {repository.lastError}
}
;
}
diff --git a/ui/src/pages/SkillSources.test.tsx b/ui/src/pages/SkillSources.test.tsx
index ffc16d6f7b3..7cd9b8a88d0 100644
--- a/ui/src/pages/SkillSources.test.tsx
+++ b/ui/src/pages/SkillSources.test.tsx
@@ -119,16 +119,20 @@ describe('GitHub skill source import', () => {
expect(document.querySelector('button[aria-label="Branch"]')?.textContent).toContain('master');
});
- it('keeps pending project links inside the installed source instead of duplicating its repository', async () => {
+ it('keeps an existing source compact while project import is pending', async () => {
context.sourceId = '';
- vi.mocked(skillSourcesApi.list).mockResolvedValue([{ ...sourceFixture(), trackingRef: 'HEAD' }]);
+ vi.mocked(skillSourcesApi.list).mockResolvedValue([{ ...sourceFixture(), trackingRef: 'HEAD', projects: [{ id: 'paperclip', name: 'Paperclip', visibility: 'open', active: true }] }]);
vi.mocked(skillSourcesApi.projectRepositories).mockResolvedValue([{ projectId: 'sentry', projectName: 'Sentry', projectVisibility: 'open',
workspaceId: 'second-workspace', repositoryUrl: sourceFixture().repositoryUrl, trackingRef: 'HEAD', bindingId: 'binding', sourceId: null, status: 'pending', lastError: null }]);
await mount();
expect(document.querySelectorAll(`a[href="${sourceFixture().repositoryUrl}"]`)).toHaveLength(1);
expect(document.querySelector('[aria-label="Project repositories"]')).toBeNull();
- expect(button('Importing…').disabled).toBe(true);
- expect(document.querySelector('[role="status"]')?.textContent).toContain('queued');
+ expect(document.querySelector('[aria-label="Skills from Sentry"]')).toBeNull();
+ expect(document.body.textContent).not.toContain('Other projects with this repo');
+ expect(document.body.textContent).not.toContain('Sentry');
+ expect(document.body.textContent).not.toContain('Paperclip');
+ expect(document.querySelector('a[href="/skills"]')).toBeNull();
+ expect(document.querySelector('[role="status"]')?.textContent).toContain('Importing skills');
});
it('allows remaining project imports while another matching workspace is pending', async () => {
diff --git a/ui/src/pages/SkillSources.tsx b/ui/src/pages/SkillSources.tsx
index ec21881480c..4e7d115f856 100644
--- a/ui/src/pages/SkillSources.tsx
+++ b/ui/src/pages/SkillSources.tsx
@@ -3,9 +3,9 @@ import { SkillSourceConnectionRepair } from "@/components/skill-studio/SkillSour
import { SkillSourceBranchRepair } from "@/components/skill-studio/SkillSourceBranchRepair";
import { SkillSourceSyncControls } from "@/components/skill-studio/SkillSourceSyncControls";
import { GithubIcon } from "@/components/icons/github-icon";
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState, type ReactNode } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
-import { RefreshCw, Plus, ExternalLink, Check, Lock, GitBranch, FileText, MoreVertical } from 'lucide-react';
+import { RefreshCw, Plus, ExternalLink, Check, Lock, GitBranch, FileText, MoreVertical, CircleAlert } from 'lucide-react';
import { parseGitHubSkillRepositoryUrl, type SkillSourceDisconnectRequest, type SkillSource, type SkillSourceDiscovery, type SkillSourceRefreshResult, type SkillSourceScanProgress } from '@paperclipai/shared';
import { Link, useNavigate, useParams } from '@/lib/router';
import { useCompany } from '@/context/CompanyContext';
@@ -85,9 +85,18 @@ export function SkillSources() {
return
-
Installed skills
{query.isPending &&
Loading sources…
}
{query.error &&
{query.error.message} void query.refetch()}>Try again
}
{query.data?.length === 0 && availableProjects.length === 0 &&
No repositories added yet. Import your skills to make them available in {selectedCompany?.name ?? 'this company'}.
navigate('/skills/sources/new')}>Import from GitHub }
@@ -103,6 +112,7 @@ export function SkillSources() {
const newCount = source.entries.filter(entry => entry.selection === 'new' && entry.present).length;
const result = results[source.id];
const projectGroup = availableProjects.find(group => groupSource(group)?.id === source.id);
+ const importingProjectSkills = projectGroup && (projectGroup.hasPending || (adopt.isPending && adopt.variables?.key === projectGroup.key));
const localError = result && result.message !== source.lastError
&& (result.operation === 'settings' || !source.lastSuccessAt || new Date(source.lastSuccessAt).getTime() < result.failedAt)
? result.message : null;
@@ -110,8 +120,7 @@ export function SkillSources() {
{source.fullName}
-
{source.trackingRef === 'HEAD' ? 'Default branch' : source.trackingRef}{source.lastSuccessAt ? ` · Updated ${timeAgo(source.lastSuccessAt)}` : ''}{source.accessScope === 'projects' ? ' · Project access' : ''}
-
{source.projects?.map(project => {project.name})}
+
{source.trackingRef === 'HEAD' ? 'Default branch' : source.trackingRef}{source.lastSuccessAt ? ` · Updated ${timeAgo(source.lastSuccessAt)}` : ''}{source.accessScope === 'projects' ? ' · Project access' : ''}{importingProjectSkills && · Importing skills… }
{source.enabled && source.automaticUpdates === false &&
Automatic updates paused
}
{source.trackingActive === false &&
Repository tracking stopped · installed skills kept
}
{!source.enabled &&
Disconnected
}
@@ -130,14 +139,16 @@ export function SkillSources() {
settings.mutate({ source, enabled })}>Automatic updates
navigate(`/skills/sources/${source.id}`)}>Select skills
+ {projectGroup && projectGroup.importableWorkspaces.length > 0 &&
adopt.mutate(projectGroup)}>
+ {projectGroup.repository.status === 'error' ? 'Retry project import' : projectGroup.hasPending ? 'Import remaining project skills' : 'Import project skills'}
+ }
setRepairSourceId(source.id)}>Choose connection
{source.accessScope !== 'projects' && !source.projects?.some(project => project.active) &&
setBranchSourceId(source.id)}>Choose a new branch }
{ disconnect.reset(); reloadRemoval.reset(); setDisconnectSource(source); }}>{source.enabled ? 'Disconnect source' : 'Remove source'}
- {projectGroup &&
adopt.mutate(projectGroup)} />}
+ {projectGroup?.repository.lastError && }
{installed.length > 0 &&
@@ -180,6 +191,19 @@ export function SkillSources() {
;
}
+function SourceErrorNotice({ message, detail, action }: { message: string; detail?: string; action?: ReactNode }) {
+ return
+
+
+
{message}
+ {(detail || action) &&
+ {detail && {detail} }
+ {action}
+
}
+
+
;
+}
+
function DisconnectSourceDialog({ source, pending, error, stale, onReload, onClose, onConfirm, onChoiceChange }: {
source: SkillSource; pending: boolean; error?: string; stale: boolean; onReload: () => void; onClose: () => void;
onConfirm: (skills: SkillSourceDisconnectRequest['skills']) => void; onChoiceChange: () => void;
diff --git a/ui/storybook/stories/skills/project-repository-skills/README.md b/ui/storybook/stories/skills/project-repository-skills/README.md
index e75ccdd71cf..ae1d2a1757a 100644
--- a/ui/storybook/stories/skills/project-repository-skills/README.md
+++ b/ui/storybook/stories/skills/project-repository-skills/README.md
@@ -9,6 +9,8 @@ Open **Agents and reusable capabilities → Skills and Skill Studio → Project
navigate to Skills → Sources. Legacy adoption starts before import. Background
update introduces skills without a click. Retry the failed refresh, pause updates,
inspect the private/shared source and try the mobile and light-theme variants.
+ **Existing source, more projects** reproduces a saved source with additional
+ project imports available in its More menu. The source row shows no project names.
Connection repair chooses an existing authorized account without changing skill
selections. Also inspect removed skills, invalid upstream content, two private
projects and the large-list story.
diff --git a/ui/storybook/stories/skills/project-repository-skills/components.stories.tsx b/ui/storybook/stories/skills/project-repository-skills/components.stories.tsx
index a523f59504c..982f6519898 100644
--- a/ui/storybook/stories/skills/project-repository-skills/components.stories.tsx
+++ b/ui/storybook/stories/skills/project-repository-skills/components.stories.tsx
@@ -7,12 +7,13 @@ import { projectRepository } from '../../../support/projectRepositorySkills';
function Components({ state = 'available' }: { state?: 'available' | 'pending' | 'importing' | 'partial' | 'error' | 'private' | 'paused' | 'refreshing' }) {
const [imported, setImported] = useState(false), [automatic, setAutomatic] = useState(state !== 'paused');
return
-
setImported(true)} />
setImported(true)} />
;
}
-const meta = { id: 'skills-project-repository-skills-01-components', title: 'Agents and reusable capabilities/Skills and Skill Studio/Project repository skills/01 Components', component: Components, parameters: { paperclip: {"capabilities": ["skills/discovery", "skills/versions"], "status": "current", "implementation": "production", "boundary": "fixtures", "notes": "Production Sources controls with fixture data cover empty, updating, paused, removed, invalid and private-project states. Provider access, persistence and runtime delivery require live acceptance."}, layout: 'fullscreen' } } satisfies Meta
;
+const meta = { id: 'skills-project-repository-skills-01-components', title: 'Agents and reusable capabilities/Skills and Skill Studio/Project repository skills/01 Components', component: Components, parameters: { paperclip: {"capabilities": ["skills/discovery", "skills/versions"], "status": "current", "implementation": "production", "boundary": "fixtures", "notes": "Production repository row and source controls cover legacy availability, import progress, errors, private access, paused updates and refresh. Existing-source adoption is covered by the journey story. Provider access, persistence and runtime delivery require live acceptance."}, layout: 'fullscreen' } } satisfies Meta;
export default meta;
type Story = StoryObj;
export const Available: Story = {};
diff --git a/ui/storybook/stories/skills/project-repository-skills/journeys.stories.tsx b/ui/storybook/stories/skills/project-repository-skills/journeys.stories.tsx
index 22262b2cabc..31ef2b698c2 100644
--- a/ui/storybook/stories/skills/project-repository-skills/journeys.stories.tsx
+++ b/ui/storybook/stories/skills/project-repository-skills/journeys.stories.tsx
@@ -36,13 +36,14 @@ function Journey({ scenario = 'ready' }: { scenario?: Scenario }) {
;
}
const meta = { id: 'skills-project-repository-skills-02-journeys', title: 'Agents and reusable capabilities/Skills and Skill Studio/Project repository skills/02 Journeys', component: Journey, tags: ['!autodocs'],
- parameters: { paperclip: {"capabilities": ["skills/discovery", "skills/versions", "projects/repositories", "routines/run-now", "routines/history"], "status": "current", "implementation": "production", "boundary": "fixtures", "notes": "Production app shell and pages exercise project creation/attachment, legacy adoption, refresh/repair and routine revision journeys with in-memory GitHub and execution fixtures. Live authorization and GitHub-to-run delivery are verified separately."}, layout: 'fullscreen', docs: { description: { component: 'Production pages and app shell. Imports, GitHub responses, background refresh and routine executions are in-memory fixtures. Start with Create project or Legacy adoption; all actions remain interactive. Live integration is verified separately.' } } },
+ parameters: { paperclip: {"capabilities": ["skills/discovery", "skills/versions", "projects/repositories", "routines/run-now", "routines/history"], "status": "current", "implementation": "production", "boundary": "fixtures", "notes": "Production app shell and pages exercise project creation/attachment, legacy and existing-source adoption, refresh/repair and routine revision journeys with in-memory GitHub and execution fixtures. Live authorization and GitHub-to-run delivery are verified separately."}, layout: 'fullscreen', docs: { description: { component: 'Production pages and app shell. Imports, GitHub responses, background refresh and routine executions are in-memory fixtures. Start with Create project or Legacy adoption; all actions remain interactive. Live integration is verified separately.' } } },
} satisfies Meta;
export default meta;
type Story = StoryObj;
export const CreateProject: Story = { name: '01 · Create a project', args: { scenario: 'new-project' } };
export const LegacyAdoption: Story = { name: '02 · Import from an older project', args: { scenario: 'legacy' } };
export const DuplicateProjects: Story = { name: 'Shared repository import across projects', args: { scenario: 'duplicate' } };
+export const ExistingSourceMoreProjects: Story = { name: 'Existing source, more projects', args: { scenario: 'partial-adoption' } };
export const MissingBranch: Story = { name: 'Repair an unavailable branch', args: { scenario: 'missing-branch' } };
export const Synced: Story = { name: '03 · Automatically imported skills' };
export const AutomaticUpdate: Story = { name: '04 · Background update and a new skill', args: { scenario: 'automatic' } };
diff --git a/ui/storybook/support/projectRepositorySkills.ts b/ui/storybook/support/projectRepositorySkills.ts
index 2191c7c825e..e5edd46cf3c 100644
--- a/ui/storybook/support/projectRepositorySkills.ts
+++ b/ui/storybook/support/projectRepositorySkills.ts
@@ -15,7 +15,7 @@ import { baseRoutine } from '../fixtures/routineWebhooks';
import { storybookProjects, storybookIssues } from '../fixtures/paperclipData';
import { COMPANY_ID, SOURCE_ID, COMMIT, installFixtures, candidates, sourceFixture } from '../fixtures/githubSkillSources';
-export type Scenario = 'new-project' | 'attach-repository' | 'legacy' | 'duplicate' | 'missing-branch' | 'ready' | 'refreshing' | 'error' | 'empty' | 'private' | 'shared' | 'shared-private' | 'long' | 'many' | 'paused' | 'removed' | 'validation' | 'repair' | 'repair-conflict' | 'automatic' | 'latest';
+export type Scenario = 'new-project' | 'attach-repository' | 'legacy' | 'duplicate' | 'partial-adoption' | 'missing-branch' | 'ready' | 'refreshing' | 'error' | 'empty' | 'private' | 'shared' | 'shared-private' | 'long' | 'many' | 'paused' | 'removed' | 'validation' | 'repair' | 'repair-conflict' | 'automatic' | 'latest';
export const projectRepository: ProjectSkillRepository = {
projectId: 'project-skills', projectName: 'Acme platform', projectVisibility: 'open',
workspaceId: 'workspace-skills', repositoryUrl: 'https://github.com/acme/team-skills', trackingRef: 'main',
@@ -38,9 +38,14 @@ export function installProjectSkillFixtures(scenario: Scenario, client: QueryCli
let live = true, automaticUpdates = scenario !== 'paused', failed = ['error', 'repair', 'repair-conflict', 'missing-branch'].includes(scenario);
let sourceRevision = sourceFixture().revision, repairConflict = scenario === 'repair-conflict';
const privateProject = scenario === 'private';
- let repository = { ...projectRepository, projectVisibility: privateProject ? 'private' as const : 'open' as const };
+ let repository = { ...projectRepository, projectName: scenario === 'partial-adoption' ? 'Paperclip' : projectRepository.projectName,
+ projectVisibility: privateProject ? 'private' as const : 'open' as const };
if (!['new-project', 'legacy', 'duplicate'].includes(scenario)) repository = { ...repository, sourceId: SOURCE_ID, bindingId: 'binding-skills', status: 'ready' };
- let additionalRepositories: ProjectSkillRepository[] = scenario === 'duplicate' ? [{ ...repository, workspaceId: 'second-workspace', projectId: 'second-project', projectName: 'Sentry' }] : [];
+ let additionalRepositories: ProjectSkillRepository[] = scenario === 'duplicate' ? [{ ...repository, workspaceId: 'second-workspace', projectId: 'second-project', projectName: 'Sentry' }]
+ : scenario === 'partial-adoption' ? [
+ { ...repository, workspaceId: 'cloud-workspace', projectId: 'paperclip-cloud', projectName: 'Paperclip Cloud', sourceId: null, bindingId: null, status: 'available' },
+ { ...repository, workspaceId: 'sentry-workspace', projectId: 'sentry', projectName: 'Sentry', sourceId: null, bindingId: null, status: 'available' },
+ ] : [];
let project = { ...storybookProjects[0]!, id: repository.projectId, urlKey: repository.projectId, name: repository.projectName, companyId: COMPANY_ID,
workspaces: storybookProjects[0]!.workspaces.slice(0, 1).map(workspace => ({ ...workspace, id: repository.workspaceId, projectId: repository.projectId, repoUrl: repository.repositoryUrl, repoRef: 'main', defaultRef: 'main', name: 'acme/team-skills', metadata: { ...workspace.metadata, githubRepositoryId: '123456' } })) };
const workspaceTemplate = project.workspaces[0]!;
@@ -89,6 +94,8 @@ export function installProjectSkillFixtures(scenario: Scenario, client: QueryCli
repairAction: !failed ? null : scenario === 'missing-branch' ? 'branch' : scenario.startsWith('repair') ? 'connection' : 'retry',
projects: scenario === 'missing-branch' ? [] : ['shared', 'shared-private'].includes(scenario) ? [{ id: project.id, name: 'Private research', visibility: 'private', active: true },
{ id: 'project-open', name: 'Engineering', visibility: scenario === 'shared' ? 'open' : 'private', active: true }]
+ : scenario === 'partial-adoption' ? [repository, ...additionalRepositories].filter(row => row.sourceId === source.id && row.status === 'ready')
+ .map(row => ({ id: row.projectId, name: row.projectName, visibility: row.projectVisibility, active: true }))
: [{ id: project.id, name: project.name, visibility: repository.projectVisibility, active: true }],
entries: scenario === 'empty' ? [] : scenario === 'many' ? Array.from({ length: 80 }, (_, index) => ({
...source.entries[0]!, id: 'entry-' + index, name: 'Project workflow ' + (index + 1), skillId: 'skill-' + index,