Skip to content

Commit 92de535

Browse files
committed
fix(variables): parse stringified VARIABLE_ALIAS for FLOAT vars (go #22 last edge)
coerceToResolvedType only rescued stringified objects on the COLOR branch (JSON.parse), so a FLOAT variable aliased to another FLOAT — when the client stringified the alias object in transit — hit Number()→NaN and was rejected. That is the exact asymmetry behind figma-mcp-go #22 (color aliases survived, float aliases got stripped). Lift the stringified-object rescue ahead of the per-type scalar coercion so it applies to every resolvedType, deduping the COLOR JSON.parse in the process. Pure widening: bare-string FLOAT ("abc") still rejects, COLOR bare string still errors with the same message, native-object aliases unchanged.
1 parent 28405b2 commit 92de535

2 files changed

Lines changed: 28 additions & 5 deletions

File tree

packages/plugin/src/handlers/set-variable-value.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,26 @@ import { toFigmaVariableValue } from './convert.js';
1212
*/
1313
const coerceToResolvedType = (raw: unknown, resolvedType: VariableResolvedDataType): unknown => {
1414
if (typeof raw !== 'string') return raw; // native type survived transit — nothing to fix
15+
// A stringified object is an alias ({ type: 'VARIABLE_ALIAS', id }) or RGBA color. Parse it back
16+
// before any per-type scalar coercion so it survives for *every* resolvedType — notably a FLOAT
17+
// variable aliased to another FLOAT, which the Number() branch below would otherwise NaN and strip
18+
// (the asymmetry behind figma-mcp-go #22: COLOR aliases survived, float aliases didn't).
19+
if (raw.trimStart().startsWith('{')) {
20+
try {
21+
return JSON.parse(raw) as unknown;
22+
} catch {
23+
throw new TypeError(`set_variable_value: "${raw}" is not valid JSON`);
24+
}
25+
}
1526
if (resolvedType === 'FLOAT') {
1627
const n = Number(raw);
1728
if (Number.isNaN(n)) throw new TypeError(`set_variable_value: "${raw}" is not a number`);
1829
return n;
1930
}
2031
if (resolvedType === 'BOOLEAN') return raw === 'true';
2132
if (resolvedType === 'COLOR') {
22-
try {
23-
return JSON.parse(raw) as unknown; // an alias / RGBA object that was stringified
24-
} catch {
25-
throw new TypeError(`set_variable_value: COLOR value "${raw}" is not valid JSON`);
26-
}
33+
// A COLOR value must arrive as a JSON object (RGBA or alias); a bare string is invalid.
34+
throw new TypeError(`set_variable_value: COLOR value "${raw}" is not valid JSON`);
2735
}
2836
return raw; // STRING
2937
};

packages/plugin/test/handlers/set-variable-value.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,21 @@ describe('set_variable_value handler', () => {
7373
expect(setValueForMode).toHaveBeenCalledWith('M:0', { type: 'VARIABLE_ALIAS', id: 'V:9' });
7474
});
7575

76+
// The same go#22 alias, but stringified in transit (the client that stringifies COLOR's RGBA does
77+
// it to alias objects too). The FLOAT branch must JSON.parse it back rather than Number()→NaN it.
78+
it('parses a stringified VARIABLE_ALIAS back for a FLOAT variable', async () => {
79+
const setValueForMode = vi.fn<() => void>();
80+
const handler = createSetVariableValueHandler(
81+
fakeFigma({ id: 'V:0', name: 'radius/md', resolvedType: 'FLOAT', setValueForMode }),
82+
);
83+
await handler({
84+
variableId: 'V:0',
85+
modeId: 'M:0',
86+
value: '{"type":"VARIABLE_ALIAS","id":"V:9"}',
87+
});
88+
expect(setValueForMode).toHaveBeenCalledWith('M:0', { type: 'VARIABLE_ALIAS', id: 'V:9' });
89+
});
90+
7691
it('rejects a stringified FLOAT that is not a number', async () => {
7792
const variable = {
7893
id: 'V:0',

0 commit comments

Comments
 (0)