@testgorilla/tgo-ui 8.11.4 → 8.13.0

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.
package/mcp/server.mjs CHANGED
@@ -21634,7 +21634,7 @@ function registerListStoriesWithDesignLinksTool(server, store) {
21634
21634
  function registerGetIconForFigmaNameTool(server, resolver) {
21635
21635
  server.tool(
21636
21636
  "get_icon_for_figma_name",
21637
- 'Resolve a Figma icon name (as exported by Figma Dev Mode) to a Canopy `<ui-icon>` name + svg path. Returns null when no match is found at any level. The `match` field tells you how the resolution happened: "exact" (after case/punctuation normalization), "alias" (manual entry in mcp/icon-aliases.json), or "fuzzy" (substring match \u2014 verify visually before relying on it).',
21637
+ 'Resolve a Figma icon name (as exported by Figma Dev Mode, e.g. "Assessment/Timer") to a Canopy `<ui-icon>` name + svg path. The Figma "Category/" prefix is stripped automatically. Returns null when no match is found at any level. The `match` field tells you how the resolution happened: "mapped" (curated entry in mcp/icon-figma-map.json), "exact" (after prefix/case/punctuation normalization), "alias" (generic synonym in the same file), or "fuzzy" (substring match). The `confident` boolean is true for exact/alias and false for fuzzy \u2014 treat a non-confident result as "needs a human mapping decision" and verify visually before relying on it.',
21638
21638
  {
21639
21639
  figmaIconName: external_exports.string().describe('Icon name from Figma (e.g., "checkmark", "Arrow-down", "Eye Open"). Case- and punctuation-insensitive.')
21640
21640
  },
@@ -21647,6 +21647,41 @@ function registerGetIconForFigmaNameTool(server, resolver) {
21647
21647
  );
21648
21648
  }
21649
21649
 
21650
+ // src/icons/icon-report.ts
21651
+ function buildIconNameReport(resolver, names) {
21652
+ const seen = /* @__PURE__ */ new Set();
21653
+ const unique = names.filter((n) => {
21654
+ const key = n.trim().toLowerCase();
21655
+ if (!key || seen.has(key)) return false;
21656
+ seen.add(key);
21657
+ return true;
21658
+ });
21659
+ const verdicts = unique.map((n) => resolver.classify(n));
21660
+ return {
21661
+ total: verdicts.length,
21662
+ confident: verdicts.filter((v) => v.status === "confident").length,
21663
+ knownGaps: verdicts.filter((v) => v.status === "known-gap").length,
21664
+ review: verdicts.filter((v) => v.status === "review")
21665
+ };
21666
+ }
21667
+
21668
+ // src/tools/check-icon-names.ts
21669
+ function registerCheckIconNamesTool(server, resolver) {
21670
+ server.tool(
21671
+ "check_icon_names",
21672
+ 'Batch-audit icon names and return only the ones that need a human mapping decision. Input is a list of names \u2014 Figma export names (e.g. "Assessment/Timer") and/or free-form / natural-language icon words (e.g. "trash", "gear"). The response has counts for `confident` (safe to use) and `knownGaps` (intentionally no code icon \u2014 already decided), plus a `review` array of the actionable names: low-confidence fuzzy guesses and outright misses, each with a `reason`. Use this to flag unresolved icons for review rather than rendering a wrong guess. To resolve a single name to a concrete `<ui-icon>`, use get_icon_for_figma_name instead.',
21673
+ {
21674
+ names: external_exports.array(external_exports.string()).describe("Icon names to check (Figma export names and/or natural-language icon words).")
21675
+ },
21676
+ async ({ names }) => {
21677
+ const report = buildIconNameReport(resolver, names);
21678
+ return {
21679
+ content: [{ type: "text", text: JSON.stringify(report, null, 2) }]
21680
+ };
21681
+ }
21682
+ );
21683
+ }
21684
+
21650
21685
  // src/tools/get-design-tokens.ts
21651
21686
  function handleGetDesignTokens(tokens, params) {
21652
21687
  if (!params.category) return tokens;
@@ -21708,41 +21743,113 @@ var IconResolver = class {
21708
21743
  byNormalized = /* @__PURE__ */ new Map();
21709
21744
  aliasMap;
21710
21745
  unmapped;
21746
+ figmaMap;
21711
21747
  icons;
21712
- constructor(icons, aliases = {}, unmapped = []) {
21748
+ constructor(icons, aliases = {}, unmapped = [], figmaMap = {}) {
21713
21749
  this.icons = icons;
21714
21750
  for (const icon of icons) {
21715
21751
  this.byNormalized.set(normalize(icon.name), icon);
21716
21752
  }
21753
+ for (const icon of icons) {
21754
+ const base = normalize(stripVariantSuffix(icon.name));
21755
+ if (!this.byNormalized.has(base)) {
21756
+ this.byNormalized.set(base, icon);
21757
+ }
21758
+ }
21717
21759
  this.aliasMap = /* @__PURE__ */ new Map();
21718
21760
  for (const [from, to] of Object.entries(aliases)) {
21719
21761
  this.aliasMap.set(normalize(from), to);
21720
21762
  }
21721
21763
  this.unmapped = new Set(unmapped.map(normalize));
21764
+ this.figmaMap = /* @__PURE__ */ new Map();
21765
+ for (const [figmaName, entry] of Object.entries(figmaMap)) {
21766
+ this.figmaMap.set(normalize(figmaName), entry);
21767
+ }
21722
21768
  }
21723
21769
  resolve(figmaName) {
21724
- const normalized = normalize(figmaName);
21770
+ const curated = this.figmaMap.get(normalize(figmaName));
21771
+ if (curated) {
21772
+ if (curated.status === "mapped" && curated.canopy) {
21773
+ const target = this.byNormalized.get(normalize(curated.canopy));
21774
+ if (target) {
21775
+ return { canopyIconName: target.name, svgPath: target.svgPath, match: "mapped", confident: true };
21776
+ }
21777
+ }
21778
+ return null;
21779
+ }
21780
+ const normalized = normalize(stripCategoryPrefix(figmaName));
21725
21781
  if (!normalized) return null;
21726
21782
  const exact = this.byNormalized.get(normalized);
21727
21783
  if (exact) {
21728
- return { canopyIconName: exact.name, svgPath: exact.svgPath, match: "exact" };
21784
+ return { canopyIconName: exact.name, svgPath: exact.svgPath, match: "exact", confident: true };
21729
21785
  }
21730
21786
  const aliasTarget = this.aliasMap.get(normalized);
21731
21787
  if (aliasTarget) {
21732
21788
  const target = this.byNormalized.get(normalize(aliasTarget));
21733
21789
  if (target) {
21734
- return { canopyIconName: target.name, svgPath: target.svgPath, match: "alias" };
21790
+ return { canopyIconName: target.name, svgPath: target.svgPath, match: "alias", confident: true };
21735
21791
  }
21736
21792
  }
21737
21793
  if (this.unmapped.has(normalized)) return null;
21738
21794
  if (normalized.length >= MIN_FUZZY_QUERY_LENGTH) {
21739
21795
  const best = this.bestFuzzyMatch(normalized);
21740
21796
  if (best) {
21741
- return { canopyIconName: best.name, svgPath: best.svgPath, match: "fuzzy" };
21797
+ return { canopyIconName: best.name, svgPath: best.svgPath, match: "fuzzy", confident: false };
21742
21798
  }
21743
21799
  }
21744
21800
  return null;
21745
21801
  }
21802
+ /**
21803
+ * Classify a name for Layer-1 reporting: is it safe to use, does it need a
21804
+ * human mapping decision, or is it an already-decided gap? Used by
21805
+ * `check_icon_names` to surface only the names worth a human's attention —
21806
+ * works for Figma export names AND free-form / natural-language icon words.
21807
+ */
21808
+ classify(figmaName) {
21809
+ const curated = this.figmaMap.get(normalize(figmaName));
21810
+ if (curated) {
21811
+ if (curated.status === "mapped" && curated.canopy) {
21812
+ const target = this.byNormalized.get(normalize(curated.canopy));
21813
+ if (target) {
21814
+ return {
21815
+ name: figmaName,
21816
+ status: "confident",
21817
+ match: "mapped",
21818
+ canopyIconName: target.name,
21819
+ svgPath: target.svgPath,
21820
+ reason: "curated map"
21821
+ };
21822
+ }
21823
+ }
21824
+ const reason = curated.status === "figma-only" ? "Figma-only by design (no code icon)" : curated.status === "add-to-canopy" ? "not built in Canopy yet" : "curated entry has no resolvable target";
21825
+ return { name: figmaName, status: "known-gap", canopyIconName: null, svgPath: null, reason };
21826
+ }
21827
+ const r = this.resolve(figmaName);
21828
+ if (r && r.confident) {
21829
+ return {
21830
+ name: figmaName,
21831
+ status: "confident",
21832
+ match: r.match,
21833
+ canopyIconName: r.canopyIconName,
21834
+ svgPath: r.svgPath,
21835
+ reason: r.match
21836
+ };
21837
+ }
21838
+ if (r && r.match === "fuzzy") {
21839
+ return {
21840
+ name: figmaName,
21841
+ status: "review",
21842
+ match: "fuzzy",
21843
+ canopyIconName: r.canopyIconName,
21844
+ svgPath: r.svgPath,
21845
+ reason: `low-confidence guess (${r.canopyIconName}) \u2014 verify or add a mapping`
21846
+ };
21847
+ }
21848
+ if (this.unmapped.has(normalize(stripCategoryPrefix(figmaName)))) {
21849
+ return { name: figmaName, status: "known-gap", canopyIconName: null, svgPath: null, reason: "no Canopy equivalent (unmapped)" };
21850
+ }
21851
+ return { name: figmaName, status: "review", canopyIconName: null, svgPath: null, reason: "no match found \u2014 needs a mapping or a new icon" };
21852
+ }
21746
21853
  /**
21747
21854
  * Pick the best fuzzy candidate rather than the first one in iteration order.
21748
21855
  * Among icons whose normalized name contains (or is contained by) the query,
@@ -21773,6 +21880,13 @@ function compareFuzzyRank(a, b) {
21773
21880
  function stripSvgExtension(name) {
21774
21881
  return name.toLowerCase().endsWith(".svg") ? name.slice(0, -".svg".length) : name;
21775
21882
  }
21883
+ function stripCategoryPrefix(name) {
21884
+ const slash = name.lastIndexOf("/");
21885
+ return slash >= 0 ? name.slice(slash + 1) : name;
21886
+ }
21887
+ function stripVariantSuffix(name) {
21888
+ return name.replace(/-(filled|in-line)$/i, "");
21889
+ }
21776
21890
  function normalize(name) {
21777
21891
  return stripSvgExtension(name).toLowerCase().replace(/[^a-z0-9]/g, "");
21778
21892
  }
@@ -21796,6 +21910,12 @@ var CatalogSchema = external_exports.object({
21796
21910
  icons: external_exports.array(external_exports.object({ name: external_exports.string(), svgPath: external_exports.string() })).optional().default([]),
21797
21911
  iconAliases: external_exports.record(external_exports.string()).optional().default({}),
21798
21912
  iconUnmapped: external_exports.array(external_exports.string()).optional().default([]),
21913
+ iconFigmaMap: external_exports.record(external_exports.object({
21914
+ canopy: external_exports.string().nullable(),
21915
+ node: external_exports.string().optional(),
21916
+ status: external_exports.enum(["mapped", "figma-only", "add-to-canopy"]),
21917
+ note: external_exports.string().optional()
21918
+ })).optional().default({}),
21799
21919
  tokens: external_exports.array(external_exports.object({ name: external_exports.string(), value: external_exports.string(), category: external_exports.string() })).optional().default([]),
21800
21920
  // Foundations additions (Phase G.1.d). Same default-to-empty pattern.
21801
21921
  typography: external_exports.array(external_exports.object({
@@ -21830,8 +21950,9 @@ function createStaticMcpServer(catalog) {
21830
21950
  registerGetComponentTypesTool(server, store);
21831
21951
  registerGetQueryGuideTool(server, store);
21832
21952
  registerListStoriesWithDesignLinksTool(server, store);
21833
- const iconResolver = new IconResolver(catalog.icons ?? [], catalog.iconAliases ?? {}, catalog.iconUnmapped ?? []);
21953
+ const iconResolver = new IconResolver(catalog.icons ?? [], catalog.iconAliases ?? {}, catalog.iconUnmapped ?? [], catalog.iconFigmaMap ?? {});
21834
21954
  registerGetIconForFigmaNameTool(server, iconResolver);
21955
+ registerCheckIconNamesTool(server, iconResolver);
21835
21956
  registerGetDesignTokensTool(server, catalog.tokens ?? []);
21836
21957
  registerListTypographyClassesTool(server, catalog.typography ?? []);
21837
21958
  registerListColorPalettesTool(server, catalog.colorPalettes ?? []);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testgorilla/tgo-ui",
3
- "version": "8.11.4",
3
+ "version": "8.13.0",
4
4
  "license": "proprietary-license",
5
5
  "lint-staged": {
6
6
  "{projects,components}/**/*.ts": [
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M2 21V3H12V7H22V21H2ZM4 19H10V17H4V19ZM4 15H10V13H4V15ZM4 11H10V9H4V11ZM4 7H10V5H4V7ZM12 19H20V9H12V19ZM14 13V11H18V13H14ZM14 17V15H18V17H14Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M2 21V3H12V7H22V21H2ZM4 19H10V17H4V19ZM4 15H10V13H4V15ZM4 11H10V9H4V11ZM4 7H10V5H4V7ZM12 19H20V9H12V19ZM14 13V11H18V13H14ZM14 17V15H18V17H14Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M4 17V19H20V17H4ZM2 6H6.2C6.11667 5.85 6.0625 5.69167 6.0375 5.525C6.0125 5.35833 6 5.18333 6 5C6 4.16667 6.29167 3.45833 6.875 2.875C7.45833 2.29167 8.16667 2 9 2C9.5 2 9.9625 2.12917 10.3875 2.3875C10.8125 2.64583 11.1833 2.96667 11.5 3.35L12 4L12.5 3.35C12.8 2.95 13.1667 2.625 13.6 2.375C14.0333 2.125 14.5 2 15 2C15.8333 2 16.5417 2.29167 17.125 2.875C17.7083 3.45833 18 4.16667 18 5C18 5.18333 17.9875 5.35833 17.9625 5.525C17.9375 5.69167 17.8833 5.85 17.8 6H22V21H2V6ZM4 14H20V8H14.9L17 10.85L15.4 12L12 7.4L8.6 12L7 10.85L9.05 8H4V14ZM9.7125 5.7125C9.90417 5.52083 10 5.28333 10 5C10 4.71667 9.90417 4.47917 9.7125 4.2875C9.52083 4.09583 9.28333 4 9 4C8.71667 4 8.47917 4.09583 8.2875 4.2875C8.09583 4.47917 8 4.71667 8 5C8 5.28333 8.09583 5.52083 8.2875 5.7125C8.47917 5.90417 8.71667 6 9 6C9.28333 6 9.52083 5.90417 9.7125 5.7125ZM15 6C15.2833 6 15.5208 5.90417 15.7125 5.7125C15.9042 5.52083 16 5.28333 16 5C16 4.71667 15.9042 4.47917 15.7125 4.2875C15.5208 4.09583 15.2833 4 15 4C14.7167 4 14.4792 4.09583 14.2875 4.2875C14.0958 4.47917 14 4.71667 14 5C14 5.28333 14.0958 5.52083 14.2875 5.7125C14.4792 5.90417 14.7167 6 15 6Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M4 17V19H20V17H4ZM2 6H6.2C6.11667 5.85 6.0625 5.69167 6.0375 5.525C6.0125 5.35833 6 5.18333 6 5C6 4.16667 6.29167 3.45833 6.875 2.875C7.45833 2.29167 8.16667 2 9 2C9.5 2 9.9625 2.12917 10.3875 2.3875C10.8125 2.64583 11.1833 2.96667 11.5 3.35L12 4L12.5 3.35C12.8 2.95 13.1667 2.625 13.6 2.375C14.0333 2.125 14.5 2 15 2C15.8333 2 16.5417 2.29167 17.125 2.875C17.7083 3.45833 18 4.16667 18 5C18 5.18333 17.9875 5.35833 17.9625 5.525C17.9375 5.69167 17.8833 5.85 17.8 6H22V21H2V6ZM4 14H20V8H14.9L17 10.85L15.4 12L12 7.4L8.6 12L7 10.85L9.05 8H4V14ZM9.7125 5.7125C9.90417 5.52083 10 5.28333 10 5C10 4.71667 9.90417 4.47917 9.7125 4.2875C9.52083 4.09583 9.28333 4 9 4C8.71667 4 8.47917 4.09583 8.2875 4.2875C8.09583 4.47917 8 4.71667 8 5C8 5.28333 8.09583 5.52083 8.2875 5.7125C8.47917 5.90417 8.71667 6 9 6C9.28333 6 9.52083 5.90417 9.7125 5.7125ZM15 6C15.2833 6 15.5208 5.90417 15.7125 5.7125C15.9042 5.52083 16 5.28333 16 5C16 4.71667 15.9042 4.47917 15.7125 4.2875C15.5208 4.09583 15.2833 4 15 4C14.7167 4 14.4792 4.09583 14.2875 4.2875C14.0958 4.47917 14 4.71667 14 5C14 5.28333 14.0958 5.52083 14.2875 5.7125C14.4792 5.90417 14.7167 6 15 6Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M14.4 19.9998L13 18.5998L15.6 15.9998L13 13.3998L14.4 11.9998L17 14.5998L19.6 11.9998L21 13.3998L18.4 15.9998L21 18.5998L19.6 19.9998L17 17.3998L14.4 19.9998ZM16.375 10.9998L12.825 7.4498L14.225 6.0498L16.35 8.1748L20.6 3.9248L22 5.3498L16.375 10.9998ZM2 16.9998V14.9998H11V16.9998H2ZM2 8.9998V6.9998H11V8.9998H2Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M14.4 19.9998L13 18.5998L15.6 15.9998L13 13.3998L14.4 11.9998L17 14.5998L19.6 11.9998L21 13.3998L18.4 15.9998L21 18.5998L19.6 19.9998L17 17.3998L14.4 19.9998ZM16.375 10.9998L12.825 7.4498L14.225 6.0498L16.35 8.1748L20.6 3.9248L22 5.3498L16.375 10.9998ZM2 16.9998V14.9998H11V16.9998H2ZM2 8.9998V6.9998H11V8.9998H2Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3.175 19.825C2.39167 19.0417 2 18.1 2 17C2 15.9 2.39167 14.9583 3.175 14.175C3.95833 13.3917 4.9 13 6 13C7.1 13 8.04167 13.3917 8.825 14.175C9.60833 14.9583 10 15.9 10 17C10 18.1 9.60833 19.0417 8.825 19.825C8.04167 20.6083 7.1 21 6 21C4.9 21 3.95833 20.6083 3.175 19.825ZM15.175 19.825C14.3917 19.0417 14 18.1 14 17C14 15.9 14.3917 14.9583 15.175 14.175C15.9583 13.3917 16.9 13 18 13C19.1 13 20.0417 13.3917 20.825 14.175C21.6083 14.9583 22 15.9 22 17C22 18.1 21.6083 19.0417 20.825 19.825C20.0417 20.6083 19.1 21 18 21C16.9 21 15.9583 20.6083 15.175 19.825ZM9.175 9.825C8.39167 9.04167 8 8.1 8 7C8 5.9 8.39167 4.95833 9.175 4.175C9.95833 3.39167 10.9 3 12 3C13.1 3 14.0417 3.39167 14.825 4.175C15.6083 4.95833 16 5.9 16 7C16 8.1 15.6083 9.04167 14.825 9.825C14.0417 10.6083 13.1 11 12 11C10.9 11 9.95833 10.6083 9.175 9.825Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3.175 19.825C2.39167 19.0417 2 18.1 2 17C2 15.9 2.39167 14.9583 3.175 14.175C3.95833 13.3917 4.9 13 6 13C7.1 13 8.04167 13.3917 8.825 14.175C9.60833 14.9583 10 15.9 10 17C10 18.1 9.60833 19.0417 8.825 19.825C8.04167 20.6083 7.1 21 6 21C4.9 21 3.95833 20.6083 3.175 19.825ZM15.175 19.825C14.3917 19.0417 14 18.1 14 17C14 15.9 14.3917 14.9583 15.175 14.175C15.9583 13.3917 16.9 13 18 13C19.1 13 20.0417 13.3917 20.825 14.175C21.6083 14.9583 22 15.9 22 17C22 18.1 21.6083 19.0417 20.825 19.825C20.0417 20.6083 19.1 21 18 21C16.9 21 15.9583 20.6083 15.175 19.825ZM7.4125 18.4125C7.80417 18.0208 8 17.55 8 17C8 16.45 7.80417 15.9792 7.4125 15.5875C7.02083 15.1958 6.55 15 6 15C5.45 15 4.97917 15.1958 4.5875 15.5875C4.19583 15.9792 4 16.45 4 17C4 17.55 4.19583 18.0208 4.5875 18.4125C4.97917 18.8042 5.45 19 6 19C6.55 19 7.02083 18.8042 7.4125 18.4125ZM19.4125 18.4125C19.8042 18.0208 20 17.55 20 17C20 16.45 19.8042 15.9792 19.4125 15.5875C19.0208 15.1958 18.55 15 18 15C17.45 15 16.9792 15.1958 16.5875 15.5875C16.1958 15.9792 16 16.45 16 17C16 17.55 16.1958 18.0208 16.5875 18.4125C16.9792 18.8042 17.45 19 18 19C18.55 19 19.0208 18.8042 19.4125 18.4125ZM9.175 9.825C8.39167 9.04167 8 8.1 8 7C8 5.9 8.39167 4.95833 9.175 4.175C9.95833 3.39167 10.9 3 12 3C13.1 3 14.0417 3.39167 14.825 4.175C15.6083 4.95833 16 5.9 16 7C16 8.1 15.6083 9.04167 14.825 9.825C14.0417 10.6083 13.1 11 12 11C10.9 11 9.95833 10.6083 9.175 9.825ZM13.4125 8.4125C13.8042 8.02083 14 7.55 14 7C14 6.45 13.8042 5.97917 13.4125 5.5875C13.0208 5.19583 12.55 5 12 5C11.45 5 10.9792 5.19583 10.5875 5.5875C10.1958 5.97917 10 6.45 10 7C10 7.55 10.1958 8.02083 10.5875 8.4125C10.9792 8.80417 11.45 9 12 9C12.55 9 13.0208 8.80417 13.4125 8.4125Z" fill="currentColor"/>
3
+ </svg>