@burdenoff/microfe-vibecontrols 2026.1003.1 → 2026.1004.1

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.
@@ -13,7 +13,57 @@ var o = {
13
13
  attachments: "/VibeControls/AI Assistant/Attachments",
14
14
  artifacts: "/VibeControls/AI Assistant/Artifacts"
15
15
  },
16
- localUiCommands: { themeWords: "mode-only" },
16
+ localUiCommands: {
17
+ themeWords: "mode-only",
18
+ entityRoutes: [
19
+ {
20
+ id: "vibecontrols-vibe",
21
+ pattern: "/vibecontrols/vibes/:id",
22
+ params: { id: {
23
+ kind: "id",
24
+ exclude: ["new"]
25
+ } },
26
+ visibleWith: "vibecontrols-vibes",
27
+ noun: {
28
+ key: "entity.vibe",
29
+ fallback: "vibe"
30
+ }
31
+ },
32
+ {
33
+ id: "vibecontrols-agent",
34
+ pattern: "/vibecontrols/agents/:id",
35
+ params: { id: {
36
+ kind: "id",
37
+ exclude: ["new"]
38
+ } },
39
+ visibleWith: "vibecontrols-agents",
40
+ noun: {
41
+ key: "entity.agent",
42
+ fallback: "agent"
43
+ }
44
+ },
45
+ {
46
+ id: "vibecontrols-session",
47
+ pattern: "/vibecontrols/sessions/:id",
48
+ params: { id: { kind: "id" } },
49
+ visibleWith: "vibecontrols-sessions",
50
+ noun: {
51
+ key: "entity.session",
52
+ fallback: "session"
53
+ }
54
+ },
55
+ {
56
+ id: "vibecontrols-connections",
57
+ pattern: "/integrations/connections",
58
+ params: {},
59
+ visibleWith: "integrations",
60
+ noun: {
61
+ key: "entity.connections",
62
+ fallback: "Connected Systems"
63
+ }
64
+ }
65
+ ]
66
+ },
17
67
  reportTurnTimings: !0,
18
68
  writeApproval: "proposal-v1"
19
69
  };
@@ -1 +1 @@
1
- {"version":3,"file":"assistantProduct.js","names":[],"sources":["../../../src/components/assistant/assistantProduct.ts"],"sourcesContent":["/**\n * VibeControls' answers to the shared assistant's product seam, as one value.\n *\n * ★★ Every field of fe-libs' `AssistantProductProvider` is OPTIONAL, and a\n * missing one does not throw — it silently degrades. No `gatherPageContext`\n * sends every turn with no page context; no `buildPrompt` drops the\n * confirm-before-mutate guardrail and the language directive; no\n * `reportTokenUsage` meters nothing; no `storageFolders` starts a second folder\n * beside the one holding every saved attachment; no `writeApproval` keeps the\n * legacy write flow.\n *\n * So the value is typed `Required<…>` over the provider's own props: forgetting\n * a field, or fe-libs adding one, is a type error in this file rather than a\n * quiet regression in production. The wrapper spreads it into the provider, and\n * `assistantProduct.test.tsx` reads it back through fe-libs' real context.\n */\n\nimport type { LocalUiCommandOptions } from '@burdenoff/fe-libs/shared/assistant/localUiCommands';\nimport type { AssistantProductProviderProps } from '@burdenoff/fe-libs/shared/assistant/product';\nimport { ASSISTANT_MODES } from '@/types/assistant';\nimport { PRODUCT, reportAssistantTokenUsage } from '@/services/assistantApi';\nimport { describeCurrentPage, gatherPageContext } from '@/utils/pageContext';\nimport { buildPromptForMode } from '@/utils/assistantPrompt';\n\n/**\n * Where this product's assistant files live.\n *\n * ★★ These strings are LOAD-BEARING once users have saved anything. They are the\n * real folders in the workspace file library holding every attachment and\n * artifact. Changing them does not fail — it starts a second folder beside the\n * first and orphans everything in the original.\n *\n * ★ Note the capitalisation: `/VibeControls/…`, while the product id is\n * `vibecontrols`. That mismatch is exactly why fe-libs cannot derive this and\n * why it is supplied here.\n */\nexport const STORAGE_FOLDERS = {\n attachments: '/VibeControls/AI Assistant/Attachments',\n artifacts: '/VibeControls/AI Assistant/Artifacts',\n} as const;\n\n/**\n * How the panel reads a UI command it carries out itself (BOFF-7802), for\n * VibeControls.\n *\n * ★★ `mode-only` because VibeControls' CONTENT has themes of its own. A docs\n * site has a Light/Dark theme (`DocsSettingsPanel`), and a VibeDeck has a colour\n * theme (`VibeDeckForm`). So \"use the dark theme\" on either page is a request\n * about that site or deck, and must reach the model rather than flip the app.\n * fe-libs names this exact case (`LocalUiCommandOptions.themeWords`). A miss\n * costs only a turn; a false match changes the app when the user meant their\n * content. \"<name> mode\" (\"dark mode\"), and a theme qualified as the app's\n * (\"change the app theme to dark\"), still change the app locally, with no turn.\n *\n * ★ Its own exported constant so the seam test can pin the field by identity,\n * like every other field on the seam value.\n */\nexport const LOCAL_UI_COMMANDS: LocalUiCommandOptions = { themeWords: 'mode-only' };\n\n/**\n * Every seam field, required: the provider's props minus its children, and\n * minus the one answer VibeControls deliberately does not give.\n *\n * ★★ `confirmWrite` is omitted so a HIGH-risk write proposal is confirmed in\n * fe-libs' shared `AssistantWriteConfirmDialog` (BOFF-7803 5a). That is the\n * documented default when a product has no dialog of its own, and VibeControls\n * has none. A product dialog must meet the whole contract fe-libs sets for it\n * (readback, target count, consequences, the exact confirm label, sensitive\n * values masked, never `window.confirm`). The shared one already does, so a\n * VibeControls copy would only be a second place for that to go wrong.\n */\nexport type VibeControlsAssistantProduct = Required<\n Omit<AssistantProductProviderProps, 'children' | 'confirmWrite'>\n>;\n\nexport const VIBECONTROLS_ASSISTANT_PRODUCT: VibeControlsAssistantProduct = {\n product: PRODUCT,\n modes: ASSISTANT_MODES,\n describeCurrentPage,\n gatherPageContext,\n buildPrompt: buildPromptForMode,\n reportTokenUsage: reportAssistantTokenUsage,\n storageFolders: STORAGE_FOLDERS,\n localUiCommands: LOCAL_UI_COMMANDS,\n /**\n * ★ On. That is fe-libs' own default (on unless `false`, BOFF-7801) and what\n * VibeControls does today: this seam never set the field, so its turns have\n * been reported by default. The reports carry timings and coarse dimensions\n * only, and the proposal flow's latency is what the rollout measures.\n */\n reportTurnTimings: true,\n /**\n * ★★★ VibeControls asks for the proposal flow (BOFF-7803 5a). At\n * `'proposal-v1'` fe-libs provisions Ask and Take action (`api-calls`)\n * through the `assistant:use`-gated `provisionAssistantSandbox`, requests the\n * proposal flow for Take action, and lets a spoken Take-action turn through\n * when the sandbox's own `/health` also says v1. Every change then waits for\n * the button on its card; the sandbox and wspace-sandbox-svc decide whether\n * it runs. Build (`vibe-plugins`) is not affected: it is still provisioned\n * through `createSandbox`, exactly as before.\n *\n * ★★ That provisioning path has NO `createSandbox` fallback: while\n * wspace-sandbox-svc answers it with 503 (its session relay switched off),\n * the assistant cannot start Ask or Take action at all. This flag reaches\n * users only when vibecontrols-app pins the microfe version that carries it,\n * so the app bump is the release switch.\n */\n writeApproval: 'proposal-v1',\n};\n"],"mappings":";;;;AA2EA,IAAa,IAA+D;CAC1E,SAAS;CACT,OAAO;CACP;CACA;CACA,aAAa;CACb,kBAAkB;CAClB,gBA9C6B;EAC7B,aAAa;EACb,WAAW;EACZ;CA4CC,iBA1BsD,EAAE,YAAY,aAAa;CAiCjF,mBAAmB;CAiBnB,eAAe;CAChB"}
1
+ {"version":3,"file":"assistantProduct.js","names":[],"sources":["../../../src/components/assistant/assistantProduct.ts"],"sourcesContent":["/**\n * VibeControls' answers to the shared assistant's product seam, as one value.\n *\n * ★★ Every field of fe-libs' `AssistantProductProvider` is OPTIONAL, and a\n * missing one does not throw — it silently degrades. No `gatherPageContext`\n * sends every turn with no page context; no `buildPrompt` drops the\n * confirm-before-mutate guardrail and the language directive; no\n * `reportTokenUsage` meters nothing; no `storageFolders` starts a second folder\n * beside the one holding every saved attachment; no `writeApproval` keeps the\n * legacy write flow.\n *\n * So the value is typed `Required<…>` over the provider's own props: forgetting\n * a field, or fe-libs adding one, is a type error in this file rather than a\n * quiet regression in production. The wrapper spreads it into the provider, and\n * `assistantProduct.test.tsx` reads it back through fe-libs' real context.\n */\n\nimport type { LocalUiCommandOptions } from '@burdenoff/fe-libs/shared/assistant/localUiCommands';\nimport type { AssistantEntityRoute } from '@burdenoff/fe-libs/shared/assistant/uiActions/entityRoutes';\nimport type { AssistantProductProviderProps } from '@burdenoff/fe-libs/shared/assistant/product';\nimport { ASSISTANT_MODES } from '@/types/assistant';\nimport { PRODUCT, reportAssistantTokenUsage } from '@/services/assistantApi';\nimport { describeCurrentPage, gatherPageContext } from '@/utils/pageContext';\nimport { buildPromptForMode } from '@/utils/assistantPrompt';\n\n/**\n * Where this product's assistant files live.\n *\n * ★★ These strings are LOAD-BEARING once users have saved anything. They are the\n * real folders in the workspace file library holding every attachment and\n * artifact. Changing them does not fail — it starts a second folder beside the\n * first and orphans everything in the original.\n *\n * ★ Note the capitalisation: `/VibeControls/…`, while the product id is\n * `vibecontrols`. That mismatch is exactly why fe-libs cannot derive this and\n * why it is supplied here.\n */\nexport const STORAGE_FOLDERS = {\n attachments: '/VibeControls/AI Assistant/Attachments',\n artifacts: '/VibeControls/AI Assistant/Artifacts',\n} as const;\n\n/**\n * The pages of ONE record that no sidebar entry names (BOFF-8060 N1b), so \"take me\n * there\" can open them from a link in the assistant's own reply.\n *\n * ★★ `exclude` lists the router's STATIC siblings of each `:id` segment\n * (`VibeControlsRoutes.tsx`): `vibes/new` redirects to the create dialog, so it is\n * not a vibe. `entityRoutes.test.ts` renders every template through the real\n * route table and checks every static sibling against them.\n *\n * ★ `/integrations/connections` is the app shell's Connected Systems page, shown\n * to whoever can see the Integrations entry.\n */\nexport const ENTITY_ROUTES: readonly AssistantEntityRoute[] = [\n {\n id: 'vibecontrols-vibe',\n pattern: '/vibecontrols/vibes/:id',\n params: { id: { kind: 'id', exclude: ['new'] } },\n visibleWith: 'vibecontrols-vibes',\n noun: { key: 'entity.vibe', fallback: 'vibe' },\n },\n {\n id: 'vibecontrols-agent',\n pattern: '/vibecontrols/agents/:id',\n params: { id: { kind: 'id', exclude: ['new'] } },\n visibleWith: 'vibecontrols-agents',\n noun: { key: 'entity.agent', fallback: 'agent' },\n },\n {\n id: 'vibecontrols-session',\n pattern: '/vibecontrols/sessions/:id',\n params: { id: { kind: 'id' } },\n visibleWith: 'vibecontrols-sessions',\n noun: { key: 'entity.session', fallback: 'session' },\n },\n {\n id: 'vibecontrols-connections',\n pattern: '/integrations/connections',\n params: {},\n visibleWith: 'integrations',\n noun: { key: 'entity.connections', fallback: 'Connected Systems' },\n },\n];\n\n/**\n * How the panel reads a UI command it carries out itself (BOFF-7802), for\n * VibeControls.\n *\n * ★★ `mode-only` because VibeControls' CONTENT has themes of its own. A docs\n * site has a Light/Dark theme (`DocsSettingsPanel`), and a VibeDeck has a colour\n * theme (`VibeDeckForm`). So \"use the dark theme\" on either page is a request\n * about that site or deck, and must reach the model rather than flip the app.\n * fe-libs names this exact case (`LocalUiCommandOptions.themeWords`). A miss\n * costs only a turn; a false match changes the app when the user meant their\n * content. \"<name> mode\" (\"dark mode\"), and a theme qualified as the app's\n * (\"change the app theme to dark\"), still change the app locally, with no turn.\n *\n * ★ Its own exported constant so the seam test can pin the field by identity,\n * like every other field on the seam value.\n */\nexport const LOCAL_UI_COMMANDS: LocalUiCommandOptions = {\n themeWords: 'mode-only',\n entityRoutes: ENTITY_ROUTES,\n};\n\n/**\n * Every seam field, required: the provider's props minus its children, and\n * minus the one answer VibeControls deliberately does not give.\n *\n * ★★ `confirmWrite` is omitted so a HIGH-risk write proposal is confirmed in\n * fe-libs' shared `AssistantWriteConfirmDialog` (BOFF-7803 5a). That is the\n * documented default when a product has no dialog of its own, and VibeControls\n * has none. A product dialog must meet the whole contract fe-libs sets for it\n * (readback, target count, consequences, the exact confirm label, sensitive\n * values masked, never `window.confirm`). The shared one already does, so a\n * VibeControls copy would only be a second place for that to go wrong.\n */\nexport type VibeControlsAssistantProduct = Required<\n Omit<AssistantProductProviderProps, 'children' | 'confirmWrite'>\n>;\n\nexport const VIBECONTROLS_ASSISTANT_PRODUCT: VibeControlsAssistantProduct = {\n product: PRODUCT,\n modes: ASSISTANT_MODES,\n describeCurrentPage,\n gatherPageContext,\n buildPrompt: buildPromptForMode,\n reportTokenUsage: reportAssistantTokenUsage,\n storageFolders: STORAGE_FOLDERS,\n localUiCommands: LOCAL_UI_COMMANDS,\n /**\n * ★ On. That is fe-libs' own default (on unless `false`, BOFF-7801) and what\n * VibeControls does today: this seam never set the field, so its turns have\n * been reported by default. The reports carry timings and coarse dimensions\n * only, and the proposal flow's latency is what the rollout measures.\n */\n reportTurnTimings: true,\n /**\n * ★★★ VibeControls asks for the proposal flow (BOFF-7803 5a). At\n * `'proposal-v1'` fe-libs provisions Ask and Take action (`api-calls`)\n * through the `assistant:use`-gated `provisionAssistantSandbox`, requests the\n * proposal flow for Take action, and lets a spoken Take-action turn through\n * when the sandbox's own `/health` also says v1. Every change then waits for\n * the button on its card; the sandbox and wspace-sandbox-svc decide whether\n * it runs. Build (`vibe-plugins`) is not affected: it is still provisioned\n * through `createSandbox`, exactly as before.\n *\n * ★★ That provisioning path has NO `createSandbox` fallback: while\n * wspace-sandbox-svc answers it with 503 (its session relay switched off),\n * the assistant cannot start Ask or Take action at all. This flag reaches\n * users only when vibecontrols-app pins the microfe version that carries it,\n * so the app bump is the release switch.\n */\n writeApproval: 'proposal-v1',\n};\n"],"mappings":";;;;AA0HA,IAAa,IAA+D;CAC1E,SAAS;CACT,OAAO;CACP;CACA;CACA,aAAa;CACb,kBAAkB;CAClB,gBA5F6B;EAC7B,aAAa;EACb,WAAW;EACZ;CA0FC,iBA7BsD;EACtD,YAAY;EACZ,cAjD4D;GAC5D;IACE,IAAI;IACJ,SAAS;IACT,QAAQ,EAAE,IAAI;KAAE,MAAM;KAAM,SAAS,CAAC,MAAM;KAAE,EAAE;IAChD,aAAa;IACb,MAAM;KAAE,KAAK;KAAe,UAAU;KAAQ;IAC/C;GACD;IACE,IAAI;IACJ,SAAS;IACT,QAAQ,EAAE,IAAI;KAAE,MAAM;KAAM,SAAS,CAAC,MAAM;KAAE,EAAE;IAChD,aAAa;IACb,MAAM;KAAE,KAAK;KAAgB,UAAU;KAAS;IACjD;GACD;IACE,IAAI;IACJ,SAAS;IACT,QAAQ,EAAE,IAAI,EAAE,MAAM,MAAM,EAAE;IAC9B,aAAa;IACb,MAAM;KAAE,KAAK;KAAkB,UAAU;KAAW;IACrD;GACD;IACE,IAAI;IACJ,SAAS;IACT,QAAQ,EAAE;IACV,aAAa;IACb,MAAM;KAAE,KAAK;KAAsB,UAAU;KAAqB;IACnE;GACF;EAqBA;CAiCC,mBAAmB;CAiBnB,eAAe;CAChB"}
@@ -7,11 +7,11 @@ import { SearchInput as a } from "../components/shared/SearchInput.js";
7
7
  import { FilterDropdown as o } from "../components/shared/FilterDropdown.js";
8
8
  import { GetHelpButton as s } from "../components/shared/GetHelpButton.js";
9
9
  import "../components/shared/index.js";
10
- import { VIBE_PERMISSIONS as c } from "../constants/permissions.js";
11
- import { PageSkeleton as l } from "../components/shared/PageSkeleton.js";
12
- import { TemplateCard as u } from "../components/templates/TemplateCard.js";
10
+ import { VIBE_PERMISSIONS as ee } from "../constants/permissions.js";
11
+ import { PageSkeleton as c } from "../components/shared/PageSkeleton.js";
12
+ import { TemplateCard as l } from "../components/templates/TemplateCard.js";
13
13
  import "../components/templates/index.js";
14
- import { useCallback as d, useEffect as f, useState as p } from "react";
14
+ import { useCallback as u, useEffect as d, useMemo as f, useState as p } from "react";
15
15
  import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
16
16
  import { FileCode as h, RefreshCw as g } from "lucide-react";
17
17
  import { usePermissions as _ } from "@burdenoff/fe-libs/shared/providers/shell";
@@ -19,63 +19,82 @@ import { AnimatePresence as v } from "framer-motion";
19
19
  import { jsx as y, jsxs as b } from "react/jsx-runtime";
20
20
  import { PagePurpose as x } from "@burdenoff/fe-libs/ui";
21
21
  import { MotionItem as S, MotionList as C, MotionPage as w } from "@burdenoff/fe-libs/shared/motion";
22
+ import { useAssistantPageControls as T } from "@burdenoff/fe-libs/shared/assistant/appControls/useAssistantControl";
23
+ import { enumFilter as E, filtersOf as D, searchOf as O } from "@burdenoff/fe-libs/shared/assistant/appControls/pageAdapters";
22
24
  //#region src/pages/TemplatesPage.tsx
23
- function T() {
24
- let T = t(), { t: E } = m(), { hasPermission: D, isLoading: O } = _(), k = D(c.TEMPLATE_READ), A = d((e, t, n) => {
25
- let r = E(e, n), i = r === e ? t : r;
25
+ function k() {
26
+ let k = t(), { t: A } = m(), { hasPermission: j, isLoading: M } = _(), N = j(ee.TEMPLATE_READ), P = u((e, t, n) => {
27
+ let r = A(e, n), i = r === e ? t : r;
26
28
  return n ? Object.entries(n).reduce((e, [t, n]) => e.replace(`{{${t}}}`, String(n)), i) : i;
27
- }, [E]), j = [
29
+ }, [A]), F = f(() => [
28
30
  {
29
31
  value: "all",
30
- label: A("template.allCategories", "All Categories")
32
+ label: P("template.allCategories", "All Categories")
31
33
  },
32
34
  {
33
35
  value: "STARTER",
34
- label: A("template.categoryStarter", "Starter")
36
+ label: P("template.categoryStarter", "Starter")
35
37
  },
36
38
  {
37
39
  value: "MICROSERVICE",
38
- label: A("template.categoryMicroservice", "Microservice")
40
+ label: P("template.categoryMicroservice", "Microservice")
39
41
  },
40
42
  {
41
43
  value: "FRONTEND",
42
- label: A("template.categoryFrontend", "Frontend")
44
+ label: P("template.categoryFrontend", "Frontend")
43
45
  },
44
46
  {
45
47
  value: "BACKEND",
46
- label: A("template.categoryBackend", "Backend")
48
+ label: P("template.categoryBackend", "Backend")
47
49
  },
48
50
  {
49
51
  value: "FULLSTACK",
50
- label: A("template.categoryFullstack", "Fullstack")
52
+ label: P("template.categoryFullstack", "Fullstack")
51
53
  },
52
54
  {
53
55
  value: "INFRASTRUCTURE",
54
- label: A("template.categoryInfrastructure", "Infrastructure")
56
+ label: P("template.categoryInfrastructure", "Infrastructure")
55
57
  },
56
58
  {
57
59
  value: "LIBRARY",
58
- label: A("template.categoryLibrary", "Library")
60
+ label: P("template.categoryLibrary", "Library")
59
61
  },
60
62
  {
61
63
  value: "CUSTOM",
62
- label: A("template.categoryCustom", "Custom")
64
+ label: P("template.categoryCustom", "Custom")
63
65
  }
64
- ], [M, N] = p(""), [P, F] = p("all"), [I, L] = p([]), [R, z] = p(!1), { data: B, loading: V, error: H, refetch: U, fetchMore: W } = e({
66
+ ], [P]), [I, L] = p(""), [R, z] = p("all"), [B, V] = p([]), [H, U] = p(!1);
67
+ T(f(() => ({
68
+ page: P("template.assistantPage", "Templates"),
69
+ filters: D([E({
70
+ id: "category",
71
+ label: P("template.assistantCategory", "Category"),
72
+ options: F,
73
+ neutral: "all",
74
+ value: R,
75
+ set: z
76
+ })]),
77
+ search: O(L)
78
+ }), [
79
+ P,
80
+ F,
81
+ R
82
+ ]));
83
+ let { data: W, loading: G, error: K, refetch: q, fetchMore: J } = e({
65
84
  variables: {
66
85
  filter: {
67
- category: P === "all" ? void 0 : P,
68
- search: M || void 0
86
+ category: R === "all" ? void 0 : R,
87
+ search: I || void 0
69
88
  },
70
89
  pagination: {
71
90
  limit: 50,
72
91
  offset: 0
73
92
  }
74
93
  },
75
- skip: O || !k,
94
+ skip: M || !N,
76
95
  fetchPolicy: "cache-and-network",
77
96
  notifyOnNetworkStatusChange: !0
78
- }), G = d((e) => e.map((e) => ({
97
+ }), Y = u((e) => e.map((e) => ({
79
98
  id: e.id,
80
99
  name: e.name,
81
100
  description: e.description || null,
@@ -86,17 +105,17 @@ function T() {
86
105
  createdAt: e.createdAt,
87
106
  updatedAt: e.updatedAt
88
107
  })), []);
89
- f(() => {
90
- L(G(B?.vibecontrolsTemplates?.items ?? []));
91
- }, [B?.vibecontrolsTemplates?.items, G]);
92
- let K = B?.vibecontrolsTemplates?.total ?? 0, q = I.length < K, J = I, Y = I, X = d(async () => {
93
- if (!(R || !q)) {
94
- z(!0);
108
+ d(() => {
109
+ V(Y(W?.vibecontrolsTemplates?.items ?? []));
110
+ }, [W?.vibecontrolsTemplates?.items, Y]);
111
+ let X = W?.vibecontrolsTemplates?.total ?? 0, Z = B.length < X, Q = B, $ = B, te = u(async () => {
112
+ if (!(H || !Z)) {
113
+ U(!0);
95
114
  try {
96
- await W({
115
+ await J({
97
116
  variables: { pagination: {
98
117
  limit: 50,
99
- offset: I.length
118
+ offset: B.length
100
119
  } },
101
120
  updateQuery: (e, { fetchMoreResult: t }) => {
102
121
  if (!t?.vibecontrolsTemplates) return e;
@@ -113,49 +132,49 @@ function T() {
113
132
  }
114
133
  });
115
134
  } finally {
116
- z(!1);
135
+ U(!1);
117
136
  }
118
137
  }
119
138
  }, [
120
- W,
121
- I.length,
122
- R,
123
- q
124
- ]), Z = (e) => {
125
- T(`/templates/${e.id}`);
139
+ J,
140
+ B.length,
141
+ H,
142
+ Z
143
+ ]), ne = (e) => {
144
+ k(`/templates/${e.id}`);
126
145
  };
127
- return O ? /* @__PURE__ */ y("div", {
146
+ return M ? /* @__PURE__ */ y("div", {
128
147
  className: "flex items-center justify-center min-h-[400px]",
129
148
  children: /* @__PURE__ */ y("p", {
130
149
  className: "text-text-secondary",
131
150
  children: "Loading permissions…"
132
151
  })
133
- }) : k ? H && J.length === 0 ? /* @__PURE__ */ y(w, {
152
+ }) : N ? K && Q.length === 0 ? /* @__PURE__ */ y(w, {
134
153
  className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen",
135
154
  children: /* @__PURE__ */ y(r, {
136
- title: A("template.failedLoad", "Failed to load templates"),
137
- message: H.message,
138
- onRetry: () => U()
155
+ title: P("template.failedLoad", "Failed to load templates"),
156
+ message: K.message,
157
+ onRetry: () => q()
139
158
  })
140
159
  }) : /* @__PURE__ */ b(w, {
141
160
  className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen",
142
161
  children: [
143
162
  /* @__PURE__ */ y(n, {
144
- title: A("template.title", "Templates"),
145
- description: K > 0 ? A("template.description", "{{total}} ready-made blueprints — pick one to scaffold a new component instantly", { total: J.length }) : A("template.descriptionEmpty", "Reusable blueprints your team publishes from components — pick one to scaffold a new component in seconds"),
163
+ title: P("template.title", "Templates"),
164
+ description: X > 0 ? P("template.description", "{{total}} ready-made blueprints — pick one to scaffold a new component instantly", { total: Q.length }) : P("template.descriptionEmpty", "Reusable blueprints your team publishes from components — pick one to scaffold a new component in seconds"),
146
165
  actions: /* @__PURE__ */ y("button", {
147
166
  type: "button",
148
- onClick: () => U(),
149
- disabled: V,
167
+ onClick: () => q(),
168
+ disabled: G,
150
169
  className: "flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50",
151
- title: A("common.refresh", "Refresh"),
152
- "aria-label": A("common.refresh", "Refresh"),
153
- children: /* @__PURE__ */ y(g, { className: `size-4 ${V ? "animate-spin" : ""}` })
170
+ title: P("common.refresh", "Refresh"),
171
+ "aria-label": P("common.refresh", "Refresh"),
172
+ children: /* @__PURE__ */ y(g, { className: `size-4 ${G ? "animate-spin" : ""}` })
154
173
  })
155
174
  }),
156
175
  /* @__PURE__ */ y(x, {
157
176
  icon: /* @__PURE__ */ y(h, { className: "size-4" }),
158
- children: A("template.purpose", "Templates are reusable blueprints your team publishes from working components — a saved starting point complete with structure and config. Pick a template to scaffold a new component in seconds instead of wiring it up by hand, so everyone starts from the same proven setup.")
177
+ children: P("template.purpose", "Templates are reusable blueprints your team publishes from working components — a saved starting point complete with structure and config. Pick a template to scaffold a new component in seconds instead of wiring it up by hand, so everyone starts from the same proven setup.")
159
178
  }),
160
179
  /* @__PURE__ */ b("div", {
161
180
  className: "flex items-center gap-2 sm:gap-3 flex-wrap",
@@ -163,60 +182,60 @@ function T() {
163
182
  "data-tour": "vibecontrols-template-search",
164
183
  className: "flex-1 sm:w-64 sm:flex-none",
165
184
  children: /* @__PURE__ */ y(a, {
166
- value: M,
167
- onChange: N,
168
- placeholder: A("template.searchPlaceholder", "Search templates..."),
185
+ value: I,
186
+ onChange: L,
187
+ placeholder: P("template.searchPlaceholder", "Search templates..."),
169
188
  className: "w-full"
170
189
  })
171
190
  }), /* @__PURE__ */ y("div", {
172
191
  "data-tour": "vibecontrols-template-category-filter",
173
192
  children: /* @__PURE__ */ y(o, {
174
- label: A("template.filterCategory", "Category"),
175
- value: P,
176
- options: j,
177
- onChange: F
193
+ label: P("template.filterCategory", "Category"),
194
+ value: R,
195
+ options: F,
196
+ onChange: z
178
197
  })
179
198
  })]
180
199
  }),
181
200
  /* @__PURE__ */ b("p", {
182
201
  className: "text-sm text-text-secondary",
183
- children: [A("template.showingResults", "Showing {{filtered}} of {{total}} templates", {
184
- filtered: Y.length,
185
- total: K
186
- }), V && /* @__PURE__ */ y("span", {
202
+ children: [P("template.showingResults", "Showing {{filtered}} of {{total}} templates", {
203
+ filtered: $.length,
204
+ total: X
205
+ }), G && /* @__PURE__ */ y("span", {
187
206
  className: "ml-2 text-text-muted",
188
- children: A("common.refreshing", "(refreshing...)")
207
+ children: P("common.refreshing", "(refreshing...)")
189
208
  })]
190
209
  }),
191
- V && J.length === 0 ? /* @__PURE__ */ y(l, {
210
+ G && Q.length === 0 ? /* @__PURE__ */ y(c, {
192
211
  variant: "cards",
193
212
  count: 6
194
- }) : Y.length > 0 ? /* @__PURE__ */ y(v, {
213
+ }) : $.length > 0 ? /* @__PURE__ */ y(v, {
195
214
  mode: "wait",
196
215
  children: /* @__PURE__ */ y(C, {
197
216
  stagger: .04,
198
217
  className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
199
218
  "data-tour": "vibecontrols-template-grid",
200
- children: Y.map((e) => /* @__PURE__ */ y(S, { children: /* @__PURE__ */ y(u, {
219
+ children: $.map((e) => /* @__PURE__ */ y(S, { children: /* @__PURE__ */ y(l, {
201
220
  template: e,
202
- onOpen: Z
221
+ onOpen: ne
203
222
  }) }, e.id))
204
223
  }, "template-grid")
205
224
  }) : /* @__PURE__ */ y(i, {
206
225
  icon: /* @__PURE__ */ y(h, { className: "size-8 text-text-secondary" }),
207
- illustration: M || P !== "all" ? "empty-search" : "empty-data",
208
- title: M || P !== "all" ? A("template.emptyNoResults", "No templates found") : A("template.emptyNoTemplates", "No templates yet"),
209
- description: M || P !== "all" ? A("template.emptyAdjustSearch", "Try adjusting your search or filters") : A("template.emptyCreate", "No templates yet. To create one: open any component → click the ⋯ menu → \"Publish as Template\". It will appear here and anyone in your workspace can use it as a starting point."),
210
- action: !M && P === "all" ? /* @__PURE__ */ y(s, { prompt: "What is a Template in VibeControls and how do I create one?" }) : void 0
226
+ illustration: I || R !== "all" ? "empty-search" : "empty-data",
227
+ title: I || R !== "all" ? P("template.emptyNoResults", "No templates found") : P("template.emptyNoTemplates", "No templates yet"),
228
+ description: I || R !== "all" ? P("template.emptyAdjustSearch", "Try adjusting your search or filters") : P("template.emptyCreate", "No templates yet. To create one: open any component → click the ⋯ menu → \"Publish as Template\". It will appear here and anyone in your workspace can use it as a starting point."),
229
+ action: !I && R === "all" ? /* @__PURE__ */ y(s, { prompt: "What is a Template in VibeControls and how do I create one?" }) : void 0
211
230
  }),
212
- q && Y.length > 0 && /* @__PURE__ */ y("div", {
231
+ Z && $.length > 0 && /* @__PURE__ */ y("div", {
213
232
  className: "flex justify-center pt-2",
214
233
  children: /* @__PURE__ */ y("button", {
215
234
  type: "button",
216
- onClick: X,
217
- disabled: R,
235
+ onClick: te,
236
+ disabled: H,
218
237
  className: "flex items-center gap-2 px-4 py-2 bg-bg-sunken text-text-primary rounded-button hover:bg-action-secondary-bg-hover transition-colors disabled:opacity-50",
219
- children: R ? A("common.loading", "Loading...") : A("common.loadMore", "Load more")
238
+ children: H ? P("common.loading", "Loading...") : P("common.loadMore", "Load more")
220
239
  })
221
240
  })
222
241
  ]
@@ -235,6 +254,6 @@ function T() {
235
254
  });
236
255
  }
237
256
  //#endregion
238
- export { T as TemplatesPage };
257
+ export { k as TemplatesPage };
239
258
 
240
259
  //# sourceMappingURL=TemplatesPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TemplatesPage.js","names":[],"sources":["../../src/pages/TemplatesPage.tsx"],"sourcesContent":["import { useState, useCallback, useEffect } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport { FileCode, RefreshCw } from 'lucide-react';\nimport { TemplateCard } from '@/components/templates';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport type { TemplateCardData } from '@/components/templates';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n EmptyState,\n ErrorState,\n GetHelpButton,\n} from '@/components/shared';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { AnimatePresence } from 'framer-motion';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useListTemplatesQuery } from '@/generated/wspace-operations';\n\nexport function TemplatesPage() {\n const navigate = useVibeNavigate();\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canReadTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_READ);\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n if (!params) return template;\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const categoryOptions = [\n { value: 'all', label: tr('template.allCategories', 'All Categories') },\n { value: 'STARTER', label: tr('template.categoryStarter', 'Starter') },\n { value: 'MICROSERVICE', label: tr('template.categoryMicroservice', 'Microservice') },\n { value: 'FRONTEND', label: tr('template.categoryFrontend', 'Frontend') },\n { value: 'BACKEND', label: tr('template.categoryBackend', 'Backend') },\n { value: 'FULLSTACK', label: tr('template.categoryFullstack', 'Fullstack') },\n { value: 'INFRASTRUCTURE', label: tr('template.categoryInfrastructure', 'Infrastructure') },\n { value: 'LIBRARY', label: tr('template.categoryLibrary', 'Library') },\n { value: 'CUSTOM', label: tr('template.categoryCustom', 'Custom') },\n ];\n\n const PAGE_SIZE = 50;\n const [searchQuery, setSearchQuery] = useState('');\n const [categoryFilter, setCategoryFilter] = useState('all');\n const [accumulated, setAccumulated] = useState<TemplateCardData[]>([]);\n const [loadingMore, setLoadingMore] = useState(false);\n\n const { data, loading, error, refetch, fetchMore } = useListTemplatesQuery({\n variables: {\n filter: {\n category: categoryFilter !== 'all' ? categoryFilter : undefined,\n search: searchQuery || undefined,\n },\n pagination: { limit: PAGE_SIZE, offset: 0 },\n },\n skip: permissionsLoading || !canReadTemplate,\n fetchPolicy: 'cache-and-network',\n notifyOnNetworkStatusChange: true,\n });\n\n const mapItems = useCallback(\n (items: ReadonlyArray<Record<string, unknown>>): TemplateCardData[] =>\n items.map((item) => ({\n id: item.id as string,\n name: item.name as string,\n description: (item.description as string) || null,\n category: (item.category as string) || null,\n cloneCount: (item.cloneCount as number) || 0,\n variableCount: Array.isArray(item.variables) ? (item.variables as unknown[]).length : 0,\n tags: (item.tags as string[]) || [],\n createdAt: item.createdAt as string,\n updatedAt: item.updatedAt as string,\n })),\n []\n );\n\n useEffect(() => {\n const items = data?.vibecontrolsTemplates?.items ?? [];\n setAccumulated(mapItems(items as ReadonlyArray<Record<string, unknown>>));\n }, [data?.vibecontrolsTemplates?.items, mapItems]);\n\n const total = data?.vibecontrolsTemplates?.total ?? 0;\n const hasMore = accumulated.length < total;\n const templates = accumulated;\n const filteredTemplates = accumulated;\n\n const handleLoadMore = useCallback(async () => {\n if (loadingMore || !hasMore) return;\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { limit: PAGE_SIZE, offset: accumulated.length } },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult?.vibecontrolsTemplates) return prev;\n const prevList = prev.vibecontrolsTemplates;\n const nextList = fetchMoreResult.vibecontrolsTemplates;\n return {\n ...prev,\n vibecontrolsTemplates: {\n ...nextList,\n items: [...(prevList?.items ?? []), ...nextList.items],\n total: nextList.total,\n hasMore: nextList.hasMore,\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n }, [fetchMore, accumulated.length, loadingMore, hasMore]);\n\n const handleTemplateSelect = (tmpl: TemplateCardData) => {\n navigate(`/templates/${tmpl.id}`);\n };\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">Loading permissions…</p>\n </div>\n );\n if (!canReadTemplate)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary\">\n You don't have permission to access this page.\n </p>\n </div>\n </div>\n );\n\n if (error && templates.length === 0) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <ErrorState\n title={tr('template.failedLoad', 'Failed to load templates')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <PageHeader\n title={tr('template.title', 'Templates')}\n description={\n total > 0\n ? tr(\n 'template.description',\n '{{total}} ready-made blueprints — pick one to scaffold a new component instantly',\n {\n total: templates.length,\n }\n )\n : tr(\n 'template.descriptionEmpty',\n 'Reusable blueprints your team publishes from components — pick one to scaffold a new component in seconds'\n )\n }\n actions={\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n aria-label={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n }\n />\n\n <PagePurpose icon={<FileCode className=\"size-4\" />}>\n {tr(\n 'template.purpose',\n 'Templates are reusable blueprints your team publishes from working components — a saved starting point complete with structure and config. Pick a template to scaffold a new component in seconds instead of wiring it up by hand, so everyone starts from the same proven setup.'\n )}\n </PagePurpose>\n\n {/* Filters — SearchInput / FilterDropdown are custom components that don't\n forward rest props onto a DOM node, so the tour anchors live on\n wrapper <div>s. */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <div data-tour=\"vibecontrols-template-search\" className=\"flex-1 sm:w-64 sm:flex-none\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('template.searchPlaceholder', 'Search templates...')}\n className=\"w-full\"\n />\n </div>\n <div data-tour=\"vibecontrols-template-category-filter\">\n <FilterDropdown\n label={tr('template.filterCategory', 'Category')}\n value={categoryFilter}\n options={categoryOptions}\n onChange={setCategoryFilter}\n />\n </div>\n </div>\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {tr('template.showingResults', 'Showing {{filtered}} of {{total}} templates', {\n filtered: filteredTemplates.length,\n total,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">{tr('common.refreshing', '(refreshing...)')}</span>\n )}\n </p>\n\n {/* Grid */}\n {loading && templates.length === 0 ? (\n <PageSkeleton variant=\"cards\" count={6} />\n ) : filteredTemplates.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n <MotionList\n key=\"template-grid\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n data-tour=\"vibecontrols-template-grid\"\n >\n {filteredTemplates.map((tmpl) => (\n <MotionItem key={tmpl.id}>\n <TemplateCard template={tmpl} onOpen={handleTemplateSelect} />\n </MotionItem>\n ))}\n </MotionList>\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<FileCode className=\"size-8 text-text-secondary\" />}\n illustration={searchQuery || categoryFilter !== 'all' ? 'empty-search' : 'empty-data'}\n title={\n searchQuery || categoryFilter !== 'all'\n ? tr('template.emptyNoResults', 'No templates found')\n : tr('template.emptyNoTemplates', 'No templates yet')\n }\n description={\n searchQuery || categoryFilter !== 'all'\n ? tr('template.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'template.emptyCreate',\n 'No templates yet. To create one: open any component → click the ⋯ menu → \"Publish as Template\". It will appear here and anyone in your workspace can use it as a starting point.'\n )\n }\n action={\n !searchQuery && categoryFilter === 'all' ? (\n <GetHelpButton prompt=\"What is a Template in VibeControls and how do I create one?\" />\n ) : undefined\n }\n />\n )}\n\n {/* Load more */}\n {hasMore && filteredTemplates.length > 0 && (\n <div className=\"flex justify-center pt-2\">\n <button\n type=\"button\"\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"flex items-center gap-2 px-4 py-2 bg-bg-sunken text-text-primary rounded-button hover:bg-action-secondary-bg-hover transition-colors disabled:opacity-50\"\n >\n {loadingMore ? tr('common.loading', 'Loading...') : tr('common.loadMore', 'Load more')}\n </button>\n </div>\n )}\n </MotionPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TemplatesPage.js","names":[],"sources":["../../src/pages/TemplatesPage.tsx"],"sourcesContent":["import { useAssistantPageControls } from '@burdenoff/fe-libs/shared/assistant/appControls/useAssistantControl';\nimport {\n enumFilter,\n filtersOf,\n searchOf,\n} from '@burdenoff/fe-libs/shared/assistant/appControls/pageAdapters';\nimport { useState, useCallback, useEffect, useMemo } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport { FileCode, RefreshCw } from 'lucide-react';\nimport { TemplateCard } from '@/components/templates';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport type { TemplateCardData } from '@/components/templates';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n EmptyState,\n ErrorState,\n GetHelpButton,\n} from '@/components/shared';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { AnimatePresence } from 'framer-motion';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useListTemplatesQuery } from '@/generated/wspace-operations';\n\nexport function TemplatesPage() {\n const navigate = useVibeNavigate();\n const { t } = useI18n();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canReadTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_READ);\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n if (!params) return template;\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const categoryOptions = useMemo(\n () => [\n { value: 'all', label: tr('template.allCategories', 'All Categories') },\n { value: 'STARTER', label: tr('template.categoryStarter', 'Starter') },\n { value: 'MICROSERVICE', label: tr('template.categoryMicroservice', 'Microservice') },\n { value: 'FRONTEND', label: tr('template.categoryFrontend', 'Frontend') },\n { value: 'BACKEND', label: tr('template.categoryBackend', 'Backend') },\n { value: 'FULLSTACK', label: tr('template.categoryFullstack', 'Fullstack') },\n { value: 'INFRASTRUCTURE', label: tr('template.categoryInfrastructure', 'Infrastructure') },\n { value: 'LIBRARY', label: tr('template.categoryLibrary', 'Library') },\n { value: 'CUSTOM', label: tr('template.categoryCustom', 'Custom') },\n ],\n [tr]\n );\n\n const PAGE_SIZE = 50;\n const [searchQuery, setSearchQuery] = useState('');\n const [categoryFilter, setCategoryFilter] = useState('all');\n const [accumulated, setAccumulated] = useState<TemplateCardData[]>([]);\n const [loadingMore, setLoadingMore] = useState(false);\n\n // BOFF-8060: what the assistant may do on this page — the SAME setters the\n // category dropdown and the search box call.\n const assistantControls = useMemo(\n () => ({\n page: tr('template.assistantPage', 'Templates'),\n filters: filtersOf([\n enumFilter<string>({\n id: 'category',\n label: tr('template.assistantCategory', 'Category'),\n options: categoryOptions,\n neutral: 'all',\n value: categoryFilter,\n set: setCategoryFilter,\n }),\n ]),\n search: searchOf(setSearchQuery),\n }),\n [tr, categoryOptions, categoryFilter]\n );\n useAssistantPageControls(assistantControls);\n\n const { data, loading, error, refetch, fetchMore } = useListTemplatesQuery({\n variables: {\n filter: {\n category: categoryFilter !== 'all' ? categoryFilter : undefined,\n search: searchQuery || undefined,\n },\n pagination: { limit: PAGE_SIZE, offset: 0 },\n },\n skip: permissionsLoading || !canReadTemplate,\n fetchPolicy: 'cache-and-network',\n notifyOnNetworkStatusChange: true,\n });\n\n const mapItems = useCallback(\n (items: ReadonlyArray<Record<string, unknown>>): TemplateCardData[] =>\n items.map((item) => ({\n id: item.id as string,\n name: item.name as string,\n description: (item.description as string) || null,\n category: (item.category as string) || null,\n cloneCount: (item.cloneCount as number) || 0,\n variableCount: Array.isArray(item.variables) ? (item.variables as unknown[]).length : 0,\n tags: (item.tags as string[]) || [],\n createdAt: item.createdAt as string,\n updatedAt: item.updatedAt as string,\n })),\n []\n );\n\n useEffect(() => {\n const items = data?.vibecontrolsTemplates?.items ?? [];\n setAccumulated(mapItems(items as ReadonlyArray<Record<string, unknown>>));\n }, [data?.vibecontrolsTemplates?.items, mapItems]);\n\n const total = data?.vibecontrolsTemplates?.total ?? 0;\n const hasMore = accumulated.length < total;\n const templates = accumulated;\n const filteredTemplates = accumulated;\n\n const handleLoadMore = useCallback(async () => {\n if (loadingMore || !hasMore) return;\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { limit: PAGE_SIZE, offset: accumulated.length } },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult?.vibecontrolsTemplates) return prev;\n const prevList = prev.vibecontrolsTemplates;\n const nextList = fetchMoreResult.vibecontrolsTemplates;\n return {\n ...prev,\n vibecontrolsTemplates: {\n ...nextList,\n items: [...(prevList?.items ?? []), ...nextList.items],\n total: nextList.total,\n hasMore: nextList.hasMore,\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n }, [fetchMore, accumulated.length, loadingMore, hasMore]);\n\n const handleTemplateSelect = (tmpl: TemplateCardData) => {\n navigate(`/templates/${tmpl.id}`);\n };\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">Loading permissions…</p>\n </div>\n );\n if (!canReadTemplate)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary\">\n You don't have permission to access this page.\n </p>\n </div>\n </div>\n );\n\n if (error && templates.length === 0) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <ErrorState\n title={tr('template.failedLoad', 'Failed to load templates')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <PageHeader\n title={tr('template.title', 'Templates')}\n description={\n total > 0\n ? tr(\n 'template.description',\n '{{total}} ready-made blueprints — pick one to scaffold a new component instantly',\n {\n total: templates.length,\n }\n )\n : tr(\n 'template.descriptionEmpty',\n 'Reusable blueprints your team publishes from components — pick one to scaffold a new component in seconds'\n )\n }\n actions={\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n aria-label={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n }\n />\n\n <PagePurpose icon={<FileCode className=\"size-4\" />}>\n {tr(\n 'template.purpose',\n 'Templates are reusable blueprints your team publishes from working components — a saved starting point complete with structure and config. Pick a template to scaffold a new component in seconds instead of wiring it up by hand, so everyone starts from the same proven setup.'\n )}\n </PagePurpose>\n\n {/* Filters — SearchInput / FilterDropdown are custom components that don't\n forward rest props onto a DOM node, so the tour anchors live on\n wrapper <div>s. */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <div data-tour=\"vibecontrols-template-search\" className=\"flex-1 sm:w-64 sm:flex-none\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('template.searchPlaceholder', 'Search templates...')}\n className=\"w-full\"\n />\n </div>\n <div data-tour=\"vibecontrols-template-category-filter\">\n <FilterDropdown\n label={tr('template.filterCategory', 'Category')}\n value={categoryFilter}\n options={categoryOptions}\n onChange={setCategoryFilter}\n />\n </div>\n </div>\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {tr('template.showingResults', 'Showing {{filtered}} of {{total}} templates', {\n filtered: filteredTemplates.length,\n total,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">{tr('common.refreshing', '(refreshing...)')}</span>\n )}\n </p>\n\n {/* Grid */}\n {loading && templates.length === 0 ? (\n <PageSkeleton variant=\"cards\" count={6} />\n ) : filteredTemplates.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n <MotionList\n key=\"template-grid\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n data-tour=\"vibecontrols-template-grid\"\n >\n {filteredTemplates.map((tmpl) => (\n <MotionItem key={tmpl.id}>\n <TemplateCard template={tmpl} onOpen={handleTemplateSelect} />\n </MotionItem>\n ))}\n </MotionList>\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<FileCode className=\"size-8 text-text-secondary\" />}\n illustration={searchQuery || categoryFilter !== 'all' ? 'empty-search' : 'empty-data'}\n title={\n searchQuery || categoryFilter !== 'all'\n ? tr('template.emptyNoResults', 'No templates found')\n : tr('template.emptyNoTemplates', 'No templates yet')\n }\n description={\n searchQuery || categoryFilter !== 'all'\n ? tr('template.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'template.emptyCreate',\n 'No templates yet. To create one: open any component → click the ⋯ menu → \"Publish as Template\". It will appear here and anyone in your workspace can use it as a starting point.'\n )\n }\n action={\n !searchQuery && categoryFilter === 'all' ? (\n <GetHelpButton prompt=\"What is a Template in VibeControls and how do I create one?\" />\n ) : undefined\n }\n />\n )}\n\n {/* Load more */}\n {hasMore && filteredTemplates.length > 0 && (\n <div className=\"flex justify-center pt-2\">\n <button\n type=\"button\"\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"flex items-center gap-2 px-4 py-2 bg-bg-sunken text-text-primary rounded-button hover:bg-action-secondary-bg-hover transition-colors disabled:opacity-50\"\n >\n {loadingMore ? tr('common.loading', 'Loading...') : tr('common.loadMore', 'Load more')}\n </button>\n </div>\n )}\n </MotionPage>\n );\n}\n"],"mappings":"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"}