Skip to content
This repository was archived by the owner on Sep 21, 2026. It is now read-only.

Commit bdfadb1

Browse files
cursor[bot]cursoragentChiragAgg5k
authored
fix: normalize smart quotes in function execution body (#3169)
* fix: normalize smart quotes in function execution body Safari substitutes curly quotes while typing, which makes JSON bodies invalid (console#2872). Normalize typographic quotes on submit and disable spellcheck on the body textarea. Co-authored-by: chiragaggarwal5k <chiragaggarwal5k@gmail.com> * fix: only repair smart quotes when they invalidate JSON Address Greptile P1: unconditional normalization could rewrite intentional typographic quotes in valid JSON string values or plain-text bodies. Only normalize when the original body fails JSON.parse and the repaired body succeeds. Co-authored-by: chiragaggarwal5k <chiragaggarwal5k@gmail.com> * fix: repair only curly double quotes in JSON bodies Narrow Greptile follow-up: JSON delimiters are double quotes, so leave apostrophes/single smart quotes untouched. Still only rewrite when the original body is invalid JSON and the repaired body parses. Co-authored-by: chiragaggarwal5k <chiragaggarwal5k@gmail.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: chiragaggarwal5k <chiragaggarwal5k@gmail.com>
1 parent a723540 commit bdfadb1

3 files changed

Lines changed: 83 additions & 2 deletions

File tree

‎src/lib/helpers/string.test.ts‎

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { singular, camelize, capitalize } from '$lib/helpers/string';
1+
import {
2+
singular,
3+
camelize,
4+
capitalize,
5+
normalizeSmartQuotes,
6+
repairSmartQuotedJson
7+
} from '$lib/helpers/string';
28
import { expect, test } from 'vitest';
39

410
/*
@@ -97,3 +103,40 @@ test('capitalize should handle strings with no lowercase letters', () => {
97103
test('capitalize should handle strings with only one character', () => {
98104
expect(capitalize('a')).toBe('A');
99105
});
106+
107+
/*
108+
NORMALIZE SMART QUOTES
109+
*/
110+
111+
test('normalizeSmartQuotes replaces curly double quotes only', () => {
112+
const curly = '{ \u201Ctest\u201D: \u2018value\u2019 }';
113+
expect(normalizeSmartQuotes(curly)).toBe('{ "test": \u2018value\u2019 }');
114+
});
115+
116+
test('normalizeSmartQuotes leaves ASCII quotes unchanged', () => {
117+
expect(normalizeSmartQuotes('{ "test": \'value\' }')).toBe('{ "test": \'value\' }');
118+
});
119+
120+
test('normalizeSmartQuotes handles empty input', () => {
121+
expect(normalizeSmartQuotes('')).toBe('');
122+
});
123+
124+
test('repairSmartQuotedJson fixes curly structural quotes', () => {
125+
const curly = '{ \u201Ctest\u201D: \u201Cvalue\u201D }';
126+
expect(repairSmartQuotedJson(curly)).toBe('{ "test": "value" }');
127+
});
128+
129+
test('repairSmartQuotedJson leaves valid JSON with curly quotes in values', () => {
130+
const valid = '{ "quote": "He said \u201Chello\u201D" }';
131+
expect(repairSmartQuotedJson(valid)).toBe(valid);
132+
});
133+
134+
test('repairSmartQuotedJson preserves apostrophes when repairing delimiters', () => {
135+
const curly = '{ \u201Cname\u201D: \u201CO\u2019Brien\u201D }';
136+
expect(repairSmartQuotedJson(curly)).toBe('{ "name": "O\u2019Brien" }');
137+
});
138+
139+
test('repairSmartQuotedJson leaves non-JSON bodies unchanged', () => {
140+
const plain = 'Hello \u201Cworld\u201D';
141+
expect(repairSmartQuotedJson(plain)).toBe(plain);
142+
});

‎src/lib/helpers/string.ts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,36 @@ export function hash(input: string | string[], delimiter: string = ','): string
127127
}
128128
return Math.abs(hash).toString(36);
129129
}
130+
131+
/**
132+
* Replace typographic / “smart” double quotes with ASCII `"`.
133+
* Safari (and some other editors) substitute curly quotes while typing, which
134+
* breaks JSON request bodies. Single quotes / apostrophes are left alone —
135+
* JSON string delimiters are always double quotes.
136+
*/
137+
export function normalizeSmartQuotes(str: string): string {
138+
if (!str) return str;
139+
return str.replace(/[\u201C\u201D\u201E\u201F\u2033\u2036]/g, '"');
140+
}
141+
142+
/**
143+
* Fix Safari-style smart double quotes when they make an otherwise-JSON body
144+
* invalid. Leaves already-valid JSON and non-JSON bodies unchanged so
145+
* intentional typographic characters in string values or plain text are
146+
* preserved. Only double quotes are rewritten (JSON delimiters).
147+
*/
148+
export function repairSmartQuotedJson(str: string): string {
149+
if (!str) return str;
150+
try {
151+
JSON.parse(str);
152+
return str;
153+
} catch {
154+
const normalized = normalizeSmartQuotes(str);
155+
try {
156+
JSON.parse(normalized);
157+
return normalized;
158+
} catch {
159+
return str;
160+
}
161+
}
162+
}

‎src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/execute-function/+page.svelte‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
toLocaleTimeISO
2626
} from '$lib/helpers/date';
2727
import { last } from '$lib/helpers/array';
28+
import { repairSmartQuotedJson } from '$lib/helpers/string';
2829
import {
2930
Accordion,
3031
Alert,
@@ -100,7 +101,9 @@
100101
.forProject(page.params.region, page.params.project)
101102
.functions.createExecution({
102103
functionId: func.$id,
103-
body,
104+
// Safari may substitute curly/smart quotes while typing JSON.
105+
// Only rewrite when that made the body invalid JSON.
106+
body: repairSmartQuotedJson(body),
104107
async: true,
105108
xpath: path,
106109
method,
@@ -179,6 +182,7 @@
179182
<InputTextarea
180183
placeholder="Enter request body here..."
181184
id="body"
185+
spellcheck={false}
182186
bind:value={body} />
183187
</Layout.Stack>
184188
</Accordion>
@@ -294,6 +298,7 @@
294298
<InputTextarea
295299
placeholder="Enter request body here..."
296300
id="body"
301+
spellcheck={false}
297302
bind:value={body} />
298303
</Layout.Stack>
299304
</Accordion>

0 commit comments

Comments
 (0)