@testgorilla/tgo-ui 8.12.0 → 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" (
|
|
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
|
|
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 ?? []);
|