@nospt/plugin-dev-ai-hub 1.1.3 → 1.2.0-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +93 -2
  2. package/config.schema.json +15 -0
  3. package/dist/api/DevAiHubResourceClient.esm.js +42 -2
  4. package/dist/api/DevAiHubResourceClient.esm.js.map +1 -1
  5. package/dist/components/AiResourcesPage/AiResourcesPage.esm.js +98 -20
  6. package/dist/components/AiResourcesPage/AiResourcesPage.esm.js.map +1 -1
  7. package/dist/components/AiResourcesPage/AiResourcesPage.module.css.esm.js +2 -2
  8. package/dist/components/AiResourcesPage/ResourceCard.esm.js +13 -4
  9. package/dist/components/AiResourcesPage/ResourceCard.esm.js.map +1 -1
  10. package/dist/components/AiResourcesPage/ResourceCard.module.css.esm.js +2 -2
  11. package/dist/components/AiResourcesPage/ResourceDetailPanel.esm.js +55 -9
  12. package/dist/components/AiResourcesPage/ResourceDetailPanel.esm.js.map +1 -1
  13. package/dist/components/AiResourcesPage/ResourceFilters.esm.js +57 -3
  14. package/dist/components/AiResourcesPage/ResourceFilters.esm.js.map +1 -1
  15. package/dist/components/AiResourcesPage/ResourceInstallDialog.esm.js +4 -2
  16. package/dist/components/AiResourcesPage/ResourceInstallDialog.esm.js.map +1 -1
  17. package/dist/components/AiResourcesPage/SortControl.esm.js +139 -0
  18. package/dist/components/AiResourcesPage/SortControl.esm.js.map +1 -0
  19. package/dist/components/AiResourcesPage/SortControl.module.css.esm.js +8 -0
  20. package/dist/components/AiResourcesPage/SortControl.module.css.esm.js.map +1 -0
  21. package/dist/components/AiResourcesPage/ownerLabel.esm.js +6 -0
  22. package/dist/components/AiResourcesPage/ownerLabel.esm.js.map +1 -0
  23. package/dist/components/AiResourcesPage/typeMeta.esm.js +1 -12
  24. package/dist/components/AiResourcesPage/typeMeta.esm.js.map +1 -1
  25. package/dist/components/Backoffice/Backoffice.module.css.esm.js +8 -0
  26. package/dist/components/Backoffice/Backoffice.module.css.esm.js.map +1 -0
  27. package/dist/components/Backoffice/BackofficeCuration.esm.js +191 -0
  28. package/dist/components/Backoffice/BackofficeCuration.esm.js.map +1 -0
  29. package/dist/components/Backoffice/BackofficeExclusion.esm.js +185 -0
  30. package/dist/components/Backoffice/BackofficeExclusion.esm.js.map +1 -0
  31. package/dist/components/Backoffice/BackofficeGate.esm.js +25 -0
  32. package/dist/components/Backoffice/BackofficeGate.esm.js.map +1 -0
  33. package/dist/components/Backoffice/BackofficeHeaderAction.esm.js +43 -0
  34. package/dist/components/Backoffice/BackofficeHeaderAction.esm.js.map +1 -0
  35. package/dist/components/Backoffice/BackofficeOverview.esm.js +517 -0
  36. package/dist/components/Backoffice/BackofficeOverview.esm.js.map +1 -0
  37. package/dist/components/Backoffice/BackofficeSection.esm.js +37 -0
  38. package/dist/components/Backoffice/BackofficeSection.esm.js.map +1 -0
  39. package/dist/components/Backoffice/TypeDistributionBar.esm.js +44 -0
  40. package/dist/components/Backoffice/TypeDistributionBar.esm.js.map +1 -0
  41. package/dist/components/Backoffice/rowEntrance.esm.js +11 -0
  42. package/dist/components/Backoffice/rowEntrance.esm.js.map +1 -0
  43. package/dist/components/Backoffice/useCurationAccess.esm.js +12 -0
  44. package/dist/components/Backoffice/useCurationAccess.esm.js.map +1 -0
  45. package/dist/components/RecommendedBadge/RecommendedBadgeIcon.esm.js +25 -0
  46. package/dist/components/RecommendedBadge/RecommendedBadgeIcon.esm.js.map +1 -0
  47. package/dist/components/RecommendedBadge/recommended-badge.svg +9 -0
  48. package/dist/components/ToolIcon/ToolIcon.esm.js +41 -6
  49. package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -1
  50. package/dist/hooks/useDefaultSortMode.esm.js +37 -0
  51. package/dist/hooks/useDefaultSortMode.esm.js.map +1 -0
  52. package/dist/hooks/useFrameworkLabel.esm.js +42 -0
  53. package/dist/hooks/useFrameworkLabel.esm.js.map +1 -0
  54. package/dist/hooks/useResources.esm.js +25 -14
  55. package/dist/hooks/useResources.esm.js.map +1 -1
  56. package/dist/hooks/useTelemetryCounts.esm.js +16 -7
  57. package/dist/hooks/useTelemetryCounts.esm.js.map +1 -1
  58. package/dist/hooks/useTelemetrySummary.esm.js +46 -0
  59. package/dist/hooks/useTelemetrySummary.esm.js.map +1 -0
  60. package/dist/index.d.ts +8 -1
  61. package/dist/index.esm.js +1 -1
  62. package/dist/plugin.esm.js +90 -3
  63. package/dist/plugin.esm.js.map +1 -1
  64. package/dist/routes.esm.js +2 -1
  65. package/dist/routes.esm.js.map +1 -1
  66. package/dist/theme/typeTokens.module.css.esm.js +5 -0
  67. package/dist/theme/typeTokens.module.css.esm.js.map +1 -0
  68. package/package.json +9 -4
@@ -1,13 +1,17 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { createFrontendPlugin, PageBlueprint, ApiBlueprint, createApiFactory, fetchApiRef, discoveryApiRef, SubPageBlueprint } from '@backstage/frontend-plugin-api';
2
+ import { createFrontendPlugin, PageBlueprint, ApiBlueprint, createApiFactory, fetchApiRef, discoveryApiRef, SubPageBlueprint, PluginHeaderActionBlueprint, coreExtensionData } from '@backstage/frontend-plugin-api';
3
3
  import { RiRobot3Fill } from '@remixicon/react';
4
4
  import { DevAiHubResourceClient, devAiHubResourceApiRef } from './api/DevAiHubResourceClient.esm.js';
5
- import { rootRouteRef } from './routes.esm.js';
5
+ import { BackofficeGate } from './components/Backoffice/BackofficeGate.esm.js';
6
+ import { BackofficeHeaderAction } from './components/Backoffice/BackofficeHeaderAction.esm.js';
7
+ import { backofficeRouteRef, rootRouteRef } from './routes.esm.js';
8
+ import './theme/typeTokens.module.css.esm.js';
6
9
 
7
10
  const devAiHubPlugin = createFrontendPlugin({
8
11
  pluginId: "dev-ai-hub",
9
12
  routes: {
10
- root: rootRouteRef
13
+ root: rootRouteRef,
14
+ backoffice: backofficeRouteRef
11
15
  },
12
16
  extensions: [
13
17
  PageBlueprint.make({
@@ -38,6 +42,89 @@ const devAiHubPlugin = createFrontendPlugin({
38
42
  title: "Browse",
39
43
  loader: () => import('./components/AiResourcesPage/index.esm.js').then((m) => /* @__PURE__ */ jsx(m.AiResourcesPage, {}))
40
44
  }
45
+ }),
46
+ // ── Backoffice surface (#83) ──────────────────────────────────────────
47
+ //
48
+ // The entry point is a header action, not a sub-page tab. `PageBlueprint`
49
+ // derives its tabs from static extension data at wiring time, before any
50
+ // user exists, so a tab is there for everyone or for no one. A header
51
+ // action is an ordinary component resolved at render time and may return
52
+ // `null`, which is what lets an unauthorised user see no way in at all
53
+ // (ADR-0016).
54
+ PluginHeaderActionBlueprint.make({
55
+ name: "backoffice",
56
+ params: {
57
+ loader: async () => /* @__PURE__ */ jsx(BackofficeHeaderAction, {})
58
+ }
59
+ }),
60
+ /*
61
+ * The backoffice page.
62
+ *
63
+ * Two things here are load-bearing and easy to undo by accident:
64
+ *
65
+ * 1. It carries a `title` but deliberately no `icon`. The app's sidebar
66
+ * builds an entry for any page that has a route ref, and drops the ones
67
+ * missing a title or an icon — falling back to the *plugin's*, which
68
+ * this plugin does not set. Adding an icon here, or a plugin-level one,
69
+ * would put the backoffice in everyone's sidebar. A regression test
70
+ * guards this.
71
+ *
72
+ * 2. The gate wraps the element the original factory produced instead of
73
+ * being passed as `loader`. `PageBlueprint` ignores its sub-pages when
74
+ * a loader is given, so gating via the loader would silently drop every
75
+ * backoffice section. Decorating the output leaves upstream's tab and
76
+ * nested-route behaviour untouched.
77
+ */
78
+ PageBlueprint.makeWithOverrides({
79
+ name: "backoffice",
80
+ factory(originalFactory) {
81
+ const original = originalFactory({
82
+ path: "/dev-ai-hub/backoffice",
83
+ title: "AI Hub Admin Panel",
84
+ routeRef: backofficeRouteRef
85
+ });
86
+ return [...original].map(
87
+ (value) => value.id === coreExtensionData.reactElement.id ? coreExtensionData.reactElement(
88
+ /* @__PURE__ */ jsx(BackofficeGate, { children: value.value })
89
+ ) : value
90
+ );
91
+ }
92
+ }),
93
+ SubPageBlueprint.make({
94
+ name: "backoffice-overview",
95
+ attachTo: {
96
+ relative: { kind: "page", name: "backoffice" },
97
+ input: "pages"
98
+ },
99
+ params: {
100
+ path: "overview",
101
+ title: "Overview",
102
+ loader: () => import('./components/Backoffice/BackofficeOverview.esm.js').then((m) => /* @__PURE__ */ jsx(m.BackofficeOverview, {}))
103
+ }
104
+ }),
105
+ SubPageBlueprint.make({
106
+ name: "backoffice-recommendations",
107
+ attachTo: {
108
+ relative: { kind: "page", name: "backoffice" },
109
+ input: "pages"
110
+ },
111
+ params: {
112
+ path: "recommendations",
113
+ title: "Recommendations",
114
+ loader: () => import('./components/Backoffice/BackofficeCuration.esm.js').then((m) => /* @__PURE__ */ jsx(m.BackofficeCuration, {}))
115
+ }
116
+ }),
117
+ SubPageBlueprint.make({
118
+ name: "backoffice-excluded",
119
+ attachTo: {
120
+ relative: { kind: "page", name: "backoffice" },
121
+ input: "pages"
122
+ },
123
+ params: {
124
+ path: "excluded",
125
+ title: "Excluded",
126
+ loader: () => import('./components/Backoffice/BackofficeExclusion.esm.js').then((m) => /* @__PURE__ */ jsx(m.BackofficeExclusion, {}))
127
+ }
41
128
  })
42
129
  ]
43
130
  });
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.tsx"],"sourcesContent":["import {\n createFrontendPlugin,\n PageBlueprint,\n SubPageBlueprint,\n ApiBlueprint,\n createApiFactory,\n discoveryApiRef,\n fetchApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { RiRobot3Fill } from '@remixicon/react';\nimport {\n devAiHubResourceApiRef,\n DevAiHubResourceClient,\n} from './api/DevAiHubResourceClient';\nimport { rootRouteRef } from './routes';\n\nexport const devAiHubPlugin = createFrontendPlugin({\n pluginId: 'dev-ai-hub',\n routes: {\n root: rootRouteRef,\n },\n extensions: [\n PageBlueprint.make({\n params: {\n path: '/dev-ai-hub',\n // Display name only. The route, the `pluginId` and the backend\n // discovery key stay `dev-ai-hub` — renaming those would break the\n // mount path and the API the frontend resolves against.\n title: 'AI Hub',\n icon: <RiRobot3Fill />,\n routeRef: rootRouteRef,\n },\n }),\n ApiBlueprint.make({\n name: 'resources',\n params: defineParams =>\n defineParams(\n createApiFactory({\n api: devAiHubResourceApiRef,\n deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },\n factory: ({ discoveryApi, fetchApi }) =>\n new DevAiHubResourceClient(discoveryApi, fetchApi),\n }),\n ),\n }),\n SubPageBlueprint.make({\n name: 'browse',\n params: {\n path: 'browse',\n title: 'Browse',\n loader: () =>\n import('./components/AiResourcesPage').then(m => (\n <m.AiResourcesPage />\n )),\n },\n }),\n ],\n});\n"],"names":[],"mappings":";;;;;;AAgBO,MAAM,iBAAiB,oBAAA,CAAqB;AAAA,EACjD,QAAA,EAAU,YAAA;AAAA,EACV,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM;AAAA,GACR;AAAA,EACA,UAAA,EAAY;AAAA,IACV,cAAc,IAAA,CAAK;AAAA,MACjB,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,aAAA;AAAA;AAAA;AAAA;AAAA,QAIN,KAAA,EAAO,QAAA;AAAA,QACP,IAAA,sBAAO,YAAA,EAAA,EAAa,CAAA;AAAA,QACpB,QAAA,EAAU;AAAA;AACZ,KACD,CAAA;AAAA,IACD,aAAa,IAAA,CAAK;AAAA,MAChB,IAAA,EAAM,WAAA;AAAA,MACN,QAAQ,CAAA,YAAA,KACN,YAAA;AAAA,QACE,gBAAA,CAAiB;AAAA,UACf,GAAA,EAAK,sBAAA;AAAA,UACL,IAAA,EAAM,EAAE,YAAA,EAAc,eAAA,EAAiB,UAAU,WAAA,EAAY;AAAA,UAC7D,OAAA,EAAS,CAAC,EAAE,YAAA,EAAc,UAAS,KACjC,IAAI,sBAAA,CAAuB,YAAA,EAAc,QAAQ;AAAA,SACpD;AAAA;AACH,KACH,CAAA;AAAA,IACD,iBAAiB,IAAA,CAAK;AAAA,MACpB,IAAA,EAAM,QAAA;AAAA,MACN,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,QAAA;AAAA,QACN,KAAA,EAAO,QAAA;AAAA,QACP,MAAA,EAAQ,MACN,OAAO,2CAA8B,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAC1C,GAAA,CAAC,CAAA,CAAE,eAAA,EAAF,EAAkB,CACpB;AAAA;AACL,KACD;AAAA;AAEL,CAAC;;;;"}
1
+ {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.tsx"],"sourcesContent":["import {\n createFrontendPlugin,\n coreExtensionData,\n PageBlueprint,\n PluginHeaderActionBlueprint,\n SubPageBlueprint,\n ApiBlueprint,\n createApiFactory,\n discoveryApiRef,\n fetchApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { RiRobot3Fill } from '@remixicon/react';\nimport {\n devAiHubResourceApiRef,\n DevAiHubResourceClient,\n} from './api/DevAiHubResourceClient';\nimport { BackofficeGate } from './components/Backoffice/BackofficeGate';\nimport { BackofficeHeaderAction } from './components/Backoffice/BackofficeHeaderAction';\nimport { backofficeRouteRef, rootRouteRef } from './routes';\n// Side-effect import: the six ResourceType identity colours (ADR-0008) are\n// `:root`-scoped custom properties consumed by both the Browse page and the\n// Admin Panel, which load as separate code-split chunks. Importing the\n// tokens here — the plugin's always-eager entry point — guarantees they\n// exist no matter which page (if any) has rendered first.\nimport './theme/typeTokens.module.css';\n\nexport const devAiHubPlugin = createFrontendPlugin({\n pluginId: 'dev-ai-hub',\n routes: {\n root: rootRouteRef,\n backoffice: backofficeRouteRef,\n },\n extensions: [\n PageBlueprint.make({\n params: {\n path: '/dev-ai-hub',\n // Display name only. The route, the `pluginId` and the backend\n // discovery key stay `dev-ai-hub` — renaming those would break the\n // mount path and the API the frontend resolves against.\n title: 'AI Hub',\n icon: <RiRobot3Fill />,\n routeRef: rootRouteRef,\n },\n }),\n ApiBlueprint.make({\n name: 'resources',\n params: defineParams =>\n defineParams(\n createApiFactory({\n api: devAiHubResourceApiRef,\n deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },\n factory: ({ discoveryApi, fetchApi }) =>\n new DevAiHubResourceClient(discoveryApi, fetchApi),\n }),\n ),\n }),\n SubPageBlueprint.make({\n name: 'browse',\n params: {\n path: 'browse',\n title: 'Browse',\n loader: () =>\n import('./components/AiResourcesPage').then(m => (\n <m.AiResourcesPage />\n )),\n },\n }),\n\n // ── Backoffice surface (#83) ──────────────────────────────────────────\n //\n // The entry point is a header action, not a sub-page tab. `PageBlueprint`\n // derives its tabs from static extension data at wiring time, before any\n // user exists, so a tab is there for everyone or for no one. A header\n // action is an ordinary component resolved at render time and may return\n // `null`, which is what lets an unauthorised user see no way in at all\n // (ADR-0016).\n PluginHeaderActionBlueprint.make({\n name: 'backoffice',\n params: {\n loader: async () => <BackofficeHeaderAction />,\n },\n }),\n\n /*\n * The backoffice page.\n *\n * Two things here are load-bearing and easy to undo by accident:\n *\n * 1. It carries a `title` but deliberately no `icon`. The app's sidebar\n * builds an entry for any page that has a route ref, and drops the ones\n * missing a title or an icon — falling back to the *plugin's*, which\n * this plugin does not set. Adding an icon here, or a plugin-level one,\n * would put the backoffice in everyone's sidebar. A regression test\n * guards this.\n *\n * 2. The gate wraps the element the original factory produced instead of\n * being passed as `loader`. `PageBlueprint` ignores its sub-pages when\n * a loader is given, so gating via the loader would silently drop every\n * backoffice section. Decorating the output leaves upstream's tab and\n * nested-route behaviour untouched.\n */\n PageBlueprint.makeWithOverrides({\n name: 'backoffice',\n factory(originalFactory) {\n const original = originalFactory({\n path: '/dev-ai-hub/backoffice',\n title: 'AI Hub Admin Panel',\n routeRef: backofficeRouteRef,\n });\n\n return [...original].map(value =>\n value.id === coreExtensionData.reactElement.id\n ? coreExtensionData.reactElement(\n <BackofficeGate>{value.value}</BackofficeGate>,\n )\n : value,\n );\n },\n }),\n SubPageBlueprint.make({\n name: 'backoffice-overview',\n attachTo: {\n relative: { kind: 'page', name: 'backoffice' },\n input: 'pages',\n },\n params: {\n path: 'overview',\n title: 'Overview',\n loader: () =>\n import('./components/Backoffice/BackofficeOverview').then(m => (\n <m.BackofficeOverview />\n )),\n },\n }),\n SubPageBlueprint.make({\n name: 'backoffice-recommendations',\n attachTo: {\n relative: { kind: 'page', name: 'backoffice' },\n input: 'pages',\n },\n params: {\n path: 'recommendations',\n title: 'Recommendations',\n loader: () =>\n import('./components/Backoffice/BackofficeCuration').then(m => (\n <m.BackofficeCuration />\n )),\n },\n }),\n SubPageBlueprint.make({\n name: 'backoffice-excluded',\n attachTo: {\n relative: { kind: 'page', name: 'backoffice' },\n input: 'pages',\n },\n params: {\n path: 'excluded',\n title: 'Excluded',\n loader: () =>\n import('./components/Backoffice/BackofficeExclusion').then(m => (\n <m.BackofficeExclusion />\n )),\n },\n }),\n ],\n});\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { createRouteRef } from '@backstage/frontend-plugin-api';
2
2
 
3
3
  const rootRouteRef = createRouteRef();
4
+ const backofficeRouteRef = createRouteRef();
4
5
 
5
- export { rootRouteRef };
6
+ export { backofficeRouteRef, rootRouteRef };
6
7
  //# sourceMappingURL=routes.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"routes.esm.js","sources":["../src/routes.ts"],"sourcesContent":["import { createRouteRef } from '@backstage/frontend-plugin-api';\n\nexport const rootRouteRef = createRouteRef();\n"],"names":[],"mappings":";;AAEO,MAAM,eAAe,cAAA;;;;"}
1
+ {"version":3,"file":"routes.esm.js","sources":["../src/routes.ts"],"sourcesContent":["import { createRouteRef } from '@backstage/frontend-plugin-api';\n\nexport const rootRouteRef = createRouteRef();\n\n/**\n * The backoffice surface (#83). A separate route from the browse page so the\n * whole surface — not just its contents — can be withheld from users without\n * `dev-ai-hub.backoffice.read` (ADR-0016).\n */\nexport const backofficeRouteRef = createRouteRef();\n"],"names":[],"mappings":";;AAEO,MAAM,eAAe,cAAA;AAOrB,MAAM,qBAAqB,cAAA;;;;"}
@@ -0,0 +1,5 @@
1
+ import styleInject from '../node_modules_dist/style-inject/dist/style-inject.es.esm.js';
2
+
3
+ var css_248z = "/*\n * Plugin-owned colour tokens for the six ResourceType roles (ADR-0008,\n * ADR-0010, amended 2026-08-03).\n *\n * Source of truth is the NOS *digital* brand palette — `brand` in\n * nosportugal/backstage `packages/app/src/themes/palette.ts`, the same one\n * that app's MUI themes and BUI variable overrides derive from. Hue and\n * saturation come from there and never change; only lightness moves, and only\n * far enough to clear 3:1 against the card background of the theme the value\n * is used in (`--bui-bg-neutral-1`: #ededeb in nos-light, #252830 in\n * nos-dark). Several values are the brand hex untouched, because they already\n * cleared it.\n *\n * marketplace is the exception: it keeps NOS Coral (#f26b43, from the\n * presentation brand reference) rather than taking brand.red, which that\n * palette binds to `error` — a semantic colour used for identity is the\n * misuse ADR-0008 exists to avoid.\n *\n * This is the ONLY place raw hex may appear for type identity; components\n * reference the custom properties. Chrome accents are NOT here — they use\n * BUI's own tokens, so they track the host theme instead of competing.\n *\n * These are `:root`-scoped, not local to any one page, because `typeMeta.tsx`\n * is shared by both the Browse page and the Admin Panel (Backoffice), and\n * each of those loads via its own code-split chunk. This file is imported\n * once, unconditionally, from `plugin.tsx` (which every route of this plugin\n * loads eagerly) so the variables exist regardless of which page — if any —\n * has rendered first. Do not move this block back into a page-owned\n * `.module.css`: a page's own stylesheet only loads once that page's chunk\n * does, which left these variables undefined for anyone landing directly on\n * the Admin Panel without visiting Browse first.\n */\n:root,\n[data-theme-mode='light'] {\n /* green / pink / yellow / blue / turquoise darkened onto the off-white card */\n --devaihub-type-skill: #609112;\n --devaihub-type-agent: #e042b2;\n --devaihub-type-hook: #9b8100;\n --devaihub-type-plugin: #4f60d2;\n --devaihub-type-mcp-config: #1d9381;\n --devaihub-type-marketplace: #ef4716;\n\n --devaihub-type-skill-bg: rgba(96, 145, 18, 0.14);\n --devaihub-type-agent-bg: rgba(224, 66, 178, 0.14);\n --devaihub-type-hook-bg: rgba(155, 129, 0, 0.14);\n --devaihub-type-plugin-bg: rgba(79, 96, 210, 0.14);\n --devaihub-type-mcp-config-bg: rgba(29, 147, 129, 0.14);\n --devaihub-type-marketplace-bg: rgba(239, 71, 22, 0.14);\n}\n\n[data-theme-mode='dark'] {\n /* The brand hexes as published — all but blue already clear 3:1 on #252830 */\n --devaihub-type-skill: #6ea514;\n --devaihub-type-agent: #eb84cd;\n --devaihub-type-hook: #fcd200;\n --devaihub-type-plugin: #5d6dd6;\n --devaihub-type-mcp-config: #4bdbc5;\n --devaihub-type-marketplace: #f26b43;\n\n --devaihub-type-skill-bg: rgba(110, 165, 20, 0.2);\n --devaihub-type-agent-bg: rgba(235, 132, 205, 0.2);\n --devaihub-type-hook-bg: rgba(252, 210, 0, 0.2);\n --devaihub-type-plugin-bg: rgba(93, 109, 214, 0.2);\n --devaihub-type-mcp-config-bg: rgba(75, 219, 197, 0.2);\n --devaihub-type-marketplace-bg: rgba(242, 107, 67, 0.2);\n}\n";
4
+ styleInject(css_248z);
5
+ //# sourceMappingURL=typeTokens.module.css.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeTokens.module.css.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nospt/plugin-dev-ai-hub",
3
- "version": "1.1.3",
3
+ "version": "1.2.0-rc1",
4
4
  "description": "Frontend plugin for Dev AI Hub",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.esm.js",
@@ -36,7 +36,9 @@
36
36
  "@nospt/plugin-dev-ai-hub-common"
37
37
  ]
38
38
  },
39
- "sideEffects": false,
39
+ "sideEffects": [
40
+ "**/*.css"
41
+ ],
40
42
  "files": [
41
43
  "dist",
42
44
  "config.schema.json",
@@ -61,8 +63,9 @@
61
63
  "@backstage/core-plugin-api": "^1.12.9",
62
64
  "@backstage/frontend-plugin-api": "^0.18.0",
63
65
  "@backstage/plugin-catalog-react": "^3.2.2",
64
- "@backstage/ui": "^0.17.1",
65
- "@nospt/plugin-dev-ai-hub-common": "^1.1.3",
66
+ "@backstage/plugin-permission-react": "^0.5.4",
67
+ "@backstage/ui": "^0.18.0",
68
+ "@nospt/plugin-dev-ai-hub-common": "^1.2.0-rc1",
66
69
  "@remixicon/react": "^4.9.0",
67
70
  "react-markdown": "^9.0.0",
68
71
  "react-syntax-highlighter": "^16.1.1",
@@ -73,8 +76,10 @@
73
76
  "@backstage/cli": "^0.36.5",
74
77
  "@backstage/dev-utils": "^1.1.26",
75
78
  "@backstage/frontend-defaults": "^0.5.5",
79
+ "@backstage/frontend-test-utils": "^0.6.3",
76
80
  "@backstage/plugin-app-react": "^0.2.6",
77
81
  "@backstage/plugin-catalog": "^2.0.8",
82
+ "@backstage/plugin-permission-common": "^0.9.10",
78
83
  "@backstage/plugin-user-settings": "^0.9.6",
79
84
  "@playwright/test": "^1.60.0",
80
85
  "@testing-library/dom": "^10.0.0",