figma-console-mcp 1.22.2 → 1.22.3

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.
@@ -3289,6 +3289,7 @@ figma.ui.onmessage = async (msg) => {
3289
3289
  'wcag-non-text-contrast', 'wcag-color-only', 'wcag-focus-indicator',
3290
3290
  'wcag-letter-spacing', 'wcag-paragraph-spacing', 'wcag-image-alt',
3291
3291
  'wcag-heading-hierarchy', 'wcag-reflow', 'wcag-reading-order',
3292
+ 'wcag-disabled-no-context', 'token-misuse',
3292
3293
  'hardcoded-color', 'no-text-style', 'default-name', 'detached-component',
3293
3294
  'no-autolayout', 'empty-container'
3294
3295
  ];
@@ -3299,9 +3300,10 @@ figma.ui.onmessage = async (msg) => {
3299
3300
  'wcag-contrast', 'wcag-text-size', 'wcag-target-size', 'wcag-line-height',
3300
3301
  'wcag-non-text-contrast', 'wcag-color-only', 'wcag-focus-indicator',
3301
3302
  'wcag-letter-spacing', 'wcag-paragraph-spacing', 'wcag-image-alt',
3302
- 'wcag-heading-hierarchy', 'wcag-reflow', 'wcag-reading-order'
3303
+ 'wcag-heading-hierarchy', 'wcag-reflow', 'wcag-reading-order',
3304
+ 'wcag-disabled-no-context'
3303
3305
  ],
3304
- 'design-system': ['hardcoded-color', 'no-text-style', 'default-name', 'detached-component'],
3306
+ 'design-system': ['hardcoded-color', 'no-text-style', 'default-name', 'detached-component', 'token-misuse'],
3305
3307
  'layout': ['no-autolayout', 'empty-container']
3306
3308
  };
3307
3309
 
@@ -3317,8 +3319,10 @@ figma.ui.onmessage = async (msg) => {
3317
3319
  'wcag-heading-hierarchy': 'warning',
3318
3320
  'wcag-reflow': 'warning',
3319
3321
  'wcag-reading-order': 'warning',
3322
+ 'wcag-disabled-no-context': 'warning',
3320
3323
  'wcag-line-height': 'info',
3321
3324
  'wcag-paragraph-spacing': 'info',
3325
+ 'token-misuse': 'warning',
3322
3326
  'hardcoded-color': 'warning',
3323
3327
  'no-text-style': 'warning',
3324
3328
  'default-name': 'warning',
@@ -3342,6 +3346,8 @@ figma.ui.onmessage = async (msg) => {
3342
3346
  'wcag-heading-hierarchy': 'a', // 1.3.1 Info and Relationships — Level A
3343
3347
  'wcag-reflow': 'aa', // 1.4.10 Reflow — Level AA
3344
3348
  'wcag-reading-order': 'a', // 1.3.2 Meaningful Sequence — Level A
3349
+ 'wcag-disabled-no-context': 'aa', // 4.1.2 Name, Role, Value — disabled elements need ARIA context
3350
+ 'token-misuse': 'design-system',
3345
3351
  'hardcoded-color': 'design-system',
3346
3352
  'no-text-style': 'design-system',
3347
3353
  'default-name': 'design-system',
@@ -3364,6 +3370,8 @@ figma.ui.onmessage = async (msg) => {
3364
3370
  'wcag-heading-hierarchy': 'Heading levels skip a level (e.g., H1 followed by H3). Use H1 through H6 sequentially without skipping levels (WCAG 1.3.1)',
3365
3371
  'wcag-reflow': 'Frame uses fixed positioning without auto-layout. Content must support 400% zoom on 1280px viewport (equivalent to 320px minimum width) without horizontal scrolling or loss of content (WCAG 1.4.10)',
3366
3372
  'wcag-reading-order': 'Visual position of elements does not match layer order. Keyboard navigation and screen reader order must follow a logical sequence (WCAG 1.3.2)',
3373
+ 'wcag-disabled-no-context': 'Disabled variant has no tooltip, helper text, or annotation explaining why the element is disabled. Use aria-disabled (not HTML disabled) to keep the element focusable for screen readers, and add a tooltip so all users understand the disabled reason.',
3374
+ 'token-misuse': 'Variable name prefix does not match its usage context (e.g., a bg/* token used as a text fill, or a text/* token used as a background). This may cause contrast issues and indicates a misbound token.',
3367
3375
  'hardcoded-color': 'Fill color is not bound to a variable or style',
3368
3376
  'no-text-style': 'Text node is not using a text style',
3369
3377
  'default-name': 'Node has a default Figma name (e.g., "Frame 1")',
@@ -4018,8 +4026,131 @@ figma.ui.onmessage = async (msg) => {
4018
4026
  } catch (e) { /* slot sublayer */ }
4019
4027
  }
4020
4028
 
4029
+ // wcag-disabled-no-context: Disabled variant without tooltip/helper text (Isabella's pattern)
4030
+ if (activeRuleSet['wcag-disabled-no-context'] && nodeType === 'COMPONENT_SET' && !truncated) {
4031
+ try {
4032
+ var csChildren = node.children;
4033
+ if (csChildren) {
4034
+ for (var dci = 0; dci < csChildren.length && !truncated; dci++) {
4035
+ var dcVariant = csChildren[dci];
4036
+ try {
4037
+ var dcName = dcVariant.name || '';
4038
+ if (/(disabled|inactive)/i.test(dcName)) {
4039
+ // Check if disabled variant has tooltip, helper text, or descriptive child
4040
+ var hasContextChild = false;
4041
+ try {
4042
+ if (dcVariant.children) {
4043
+ for (var dcci = 0; dcci < dcVariant.children.length; dcci++) {
4044
+ var dcChild = dcVariant.children[dcci];
4045
+ try {
4046
+ var dcChildName = (dcChild.name || '').toLowerCase();
4047
+ // Look for tooltip, helper text, hint, description, or error message children
4048
+ if (/tooltip|helper|hint|description|message|caption|note|info|why|reason/i.test(dcChildName)) {
4049
+ hasContextChild = true;
4050
+ break;
4051
+ }
4052
+ // Recurse one level for nested tooltip/helper
4053
+ if (dcChild.children) {
4054
+ for (var dcgci = 0; dcgci < dcChild.children.length; dcgci++) {
4055
+ var dcGrandchild = dcChild.children[dcgci];
4056
+ try {
4057
+ if (/tooltip|helper|hint|description|message/i.test(dcGrandchild.name || '')) {
4058
+ hasContextChild = true;
4059
+ break;
4060
+ }
4061
+ } catch (e) { /* skip */ }
4062
+ }
4063
+ if (hasContextChild) break;
4064
+ }
4065
+ } catch (e) { /* skip */ }
4066
+ }
4067
+ }
4068
+ } catch (e) { /* skip */ }
4069
+ // Also check component description for disabled guidance
4070
+ var hasDisabledAnnotation = false;
4071
+ try {
4072
+ var csDesc = (node.description || '').toLowerCase();
4073
+ if (/disabled.*tooltip|disabled.*helper|disabled.*hint|aria-disabled|why.*disabled|disabled.*reason/i.test(csDesc)) {
4074
+ hasDisabledAnnotation = true;
4075
+ }
4076
+ } catch (e) { /* skip */ }
4077
+ if (!hasContextChild && !hasDisabledAnnotation) {
4078
+ if (totalFindings < maxFindings) {
4079
+ findings['wcag-disabled-no-context'].push({
4080
+ id: dcVariant.id,
4081
+ name: nodeName + ' / ' + dcVariant.name,
4082
+ suggestion: 'Disabled elements should remain focusable (use aria-disabled, not HTML disabled). Add a tooltip or helper text explaining why the element is disabled so screen reader users understand the context.'
4083
+ });
4084
+ totalFindings++;
4085
+ } else { truncated = true; }
4086
+ }
4087
+ }
4088
+ } catch (e) { /* skip variant */ }
4089
+ }
4090
+ }
4091
+ } catch (e) { /* slot sublayer */ }
4092
+ }
4093
+
4021
4094
  // ---- Design System checks ----
4022
4095
 
4096
+ // token-misuse: Variable name prefix doesn't match usage context
4097
+ if (activeRuleSet['token-misuse'] && !isPage && !isSection && !truncated) {
4098
+ try {
4099
+ var tmFills = node.fills;
4100
+ if (tmFills && tmFills.length > 0) {
4101
+ for (var tmi = 0; tmi < tmFills.length; tmi++) {
4102
+ var tmFill = tmFills[tmi];
4103
+ if (tmFill.type === 'SOLID' && tmFill.visible !== false) {
4104
+ try {
4105
+ if (tmFill.boundVariables && tmFill.boundVariables.color) {
4106
+ var tmVarId = tmFill.boundVariables.color.id;
4107
+ // Resolve variable name
4108
+ try {
4109
+ var tmVar = figma.variables.getVariableById(tmVarId);
4110
+ if (tmVar) {
4111
+ var tmVarName = tmVar.name.toLowerCase();
4112
+ var isBgToken = /^(bg|background|surface|fill)[\/-]/.test(tmVarName);
4113
+ var isTextNode = nodeType === 'TEXT';
4114
+ // Flag: bg/surface token used as text fill
4115
+ if (isTextNode && isBgToken) {
4116
+ if (totalFindings < maxFindings) {
4117
+ findings['token-misuse'].push({
4118
+ id: nodeId,
4119
+ name: nodeName,
4120
+ variable: tmVar.name,
4121
+ usage: 'text fill',
4122
+ expectedPrefix: 'text/*, fg/*, foreground/*',
4123
+ suggestion: 'This text node uses a background/surface token ("' + tmVar.name + '") as its fill color. This is likely a misbound token — use a text/foreground token instead.'
4124
+ });
4125
+ totalFindings++;
4126
+ } else { truncated = true; }
4127
+ }
4128
+ // Flag: text/foreground token used as frame/shape background
4129
+ var isTextToken = /^(text|fg|foreground|font)[\/-]/.test(tmVarName);
4130
+ var isContainerNode = nodeType === 'FRAME' || nodeType === 'COMPONENT' || nodeType === 'INSTANCE' || nodeType === 'RECTANGLE';
4131
+ if (isContainerNode && isTextToken && !truncated) {
4132
+ if (totalFindings < maxFindings) {
4133
+ findings['token-misuse'].push({
4134
+ id: nodeId,
4135
+ name: nodeName,
4136
+ variable: tmVar.name,
4137
+ usage: 'background fill',
4138
+ expectedPrefix: 'bg/*, background/*, surface/*',
4139
+ suggestion: 'This container uses a text/foreground token ("' + tmVar.name + '") as its background fill. This is likely a misbound token — use a background/surface token instead.'
4140
+ });
4141
+ totalFindings++;
4142
+ } else { truncated = true; }
4143
+ }
4144
+ }
4145
+ } catch (e) { /* can't resolve variable */ }
4146
+ }
4147
+ } catch (e) { /* no bound vars */ }
4148
+ }
4149
+ }
4150
+ }
4151
+ } catch (e) { /* slot sublayer */ }
4152
+ }
4153
+
4023
4154
  // hardcoded-color: Solid fills without variable binding or style
4024
4155
  if (activeRuleSet['hardcoded-color'] && !isPage && !isSection && !truncated) {
4025
4156
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "figma-console-mcp",
3
- "version": "1.22.2",
3
+ "version": "1.22.3",
4
4
  "description": "MCP server for accessing Figma plugin console logs and screenshots via Cloudflare Workers or local mode",
5
5
  "type": "module",
6
6
  "main": "dist/local.js",