diff --git a/ui/src/pages/CompanySkills.tsx b/ui/src/pages/CompanySkills.tsx
index dda20192d30..980334a63f0 100644
--- a/ui/src/pages/CompanySkills.tsx
+++ b/ui/src/pages/CompanySkills.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useState, type SVGProps } from "react";
+import { useEffect, useMemo, useRef, useState, type SVGProps } from "react";
import { Link, useNavigate, useParams, useSearchParams } from "@/lib/router";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type {
@@ -97,6 +97,7 @@ import {
Paperclip,
Pause,
Pencil,
+ Pin,
Plus,
Copy,
RefreshCw,
@@ -1191,7 +1192,7 @@ export function DiscoveryGrid({
{sourceFilteredCards.length} {sourceFilteredCards.length === 1 ? "skill" : "skills"}
{activeCategory ? · {activeCategory} : null}
-
+
{sourceFilteredCards.map((card) => (
))}
@@ -1435,7 +1436,7 @@ function NewSkillWizard({
) : (
@@ -2604,6 +2605,19 @@ export function SkillDetailPage({
}) {
const [diffOpen, setDiffOpen] = useState(false);
const [settingsOpen, setSettingsOpen] = useState(false);
+ // Top-level description is clamped to four lines; "View all" expands it. We
+ // only surface the toggle when the text actually overflows the clamp.
+ const descriptionRef = useRef
(null);
+ const [descExpanded, setDescExpanded] = useState(false);
+ const [descClamped, setDescClamped] = useState(false);
+ useEffect(() => {
+ const el = descriptionRef.current;
+ if (!el || descExpanded) return;
+ setDescClamped(el.scrollHeight - el.clientHeight > 1);
+ }, [detail?.description, detail?.tagline, detail?.id, descExpanded]);
+ useEffect(() => {
+ setDescExpanded(false);
+ }, [detail?.id]);
const sortedVersions = [...versions].sort((a, b) => b.revisionNumber - a.revisionNumber);
const [leftVersionId, setLeftVersionId] = useState(null);
const [rightVersionId, setRightVersionId] = useState(null);
@@ -2639,6 +2653,17 @@ export function SkillDetailPage({
// Look up the richer agent record (icon, paused) for agents using this skill.
const attachAgentMetaById = new Map(attachAgents.map((agent) => [agent.id, agent]));
+ // Sidebar provenance: where this skill came from (org / path), linked when the
+ // locator resolves to a URL (PAP-10907).
+ const sourceLocatorText = skill.sourcePath || skill.sourceLocator || null;
+ const sourceHref =
+ skill.homepageUrl
+ ?? (sourceLocatorText && /^(https?:\/\/|[\w.-]+\.[a-z]{2,}\/)/i.test(sourceLocatorText)
+ ? sourceLocatorText.startsWith("http")
+ ? sourceLocatorText
+ : `https://${sourceLocatorText}`
+ : null);
+
function renderFilesBody() {
return (
@@ -2886,7 +2911,7 @@ export function SkillDetailPage({
-
+
Installed from {source.label}
+ {/* GitHub-style "by" attribution sits directly under the title. */}
+ {detail.authorName ? (
+
+ by {detail.authorName}
+
+ ) : null}
{subtitleText ? (
-
{subtitleText}
+
+
+ {subtitleText}
+
+ {descClamped ? (
+
setDescExpanded((value) => !value)}
+ className="mt-0.5 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
+ >
+ {descExpanded ? "Show less" : "View all"}
+
+ ) : null}
+
) : null}
@@ -2936,17 +2986,6 @@ export function SkillDetailPage({
{detail.categories.slice(0, 4).map((category) => (
))}
- {detail.sourceType === "github" ? (
-
-
-
-
- {currentPin ?? "untracked"}
-
-
- {currentPin ? `Source pin ${currentPin}` : "No source pin tracked"}
-
- ) : null}
{/* GitHub-style social proof, top-right: installs · stars · fork.
@@ -3055,6 +3094,36 @@ export function SkillDetailPage({
+ {/* Provenance: where this skill came from, with org/path linked when
+ available. Bundled/catalog skills surface their source label too
+ (PAP-10907). */}
+
+ Source
+
+
+
+
{source.label}
+ {sourceLocatorText ? (
+ sourceHref ? (
+
+ {sourceLocatorText}
+
+
+ ) : (
+
{sourceLocatorText}
+ )
+ ) : (
+
{source.managedLabel}
+ )}
+
+
+
+
{/* Revision / update controls sit under Agents, above the config gear
(PAP-10907 F). Only GitHub-sourced skills can pull updates. */}
{detail.sourceType === "github" ? (
@@ -3062,7 +3131,12 @@ export function SkillDetailPage({
Updates
-
Pin
+
+
+
+
+ Pinned source revision
+
{currentPin ?? "untracked"}
@@ -3614,6 +3688,22 @@ export function CompanySkills() {
]);
}, [routeSkillToken, setBreadcrumbs]);
+ // The old split catalog view no longer exists — bundled/catalog skills live in
+ // the discovery grid and install dialog now. Strip legacy `view=catalog` /
+ // `catalog` params so stale deep links land on the new surface (PAP-10907).
+ useEffect(() => {
+ if (!searchParams.has("view") && !searchParams.has("catalog")) return;
+ setSearchParams(
+ (current) => {
+ const next = new URLSearchParams(current);
+ next.delete("view");
+ next.delete("catalog");
+ return next;
+ },
+ { replace: true },
+ );
+ }, [searchParams, setSearchParams]);
+
const skillsQuery = useQuery({
queryKey: queryKeys.companySkills.list(selectedCompanyId ?? ""),
queryFn: () => companySkillsApi.list(selectedCompanyId!),
@@ -4203,16 +4293,19 @@ export function CompanySkills() {
importSkill.mutate(trimmedSource);
}
- // Opening a card drops out of the discovery grid: installed skills go to their
- // detail route, catalog-only skills open the catalog detail surface.
+ // Opening a card stays inside the new store: installed skills go to their
+ // detail route; catalog/bundled skills open the install dialog inline. The
+ // legacy split catalog view is gone (PAP-10907).
function openDiscoveryCard(card: DiscoveryCard) {
if (card.skillId) {
navigate(routeForSkillId(card.skillId));
return;
}
if (card.catalogRef) {
- setViewParam("catalog");
- selectCatalog(card.catalogRef);
+ const catalogSkill = (catalogListQuery.data ?? []).find(
+ (entry) => entry.id === card.catalogRef || entry.key === card.key,
+ );
+ if (catalogSkill) openInstallDialog(catalogSkill);
}
}
@@ -4331,7 +4424,7 @@ export function CompanySkills() {
/>
-
+
{createDraft.forkedFromSkillId ? "Fork skill" : "Create a new skill"}