Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/app-store/routing-forms/lib/substituteVariables.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,37 @@ describe("substituteVariables", () => {
expect(result).toBe("/event/marketing-pr?type=meeting&priority=high");
});

it("should preserve original formatting for variables in query parameters", () => {
const fields = [
createTextField("field1", "name", "Name"),
createTextField("field2", "org", "Organization"),
];
const routeValue = "automation-training?name={name}&station={org}";
const response: FormResponse = {
...createFormResponse("field1", "Sean Paul", "Name"),
...createFormResponse("field2", "Introduction Meeting", "Organization"),
};

const result = substituteVariables(routeValue, response, fields);
// Query parameters should preserve original formatting (URL-encoded)
expect(result).toBe("automation-training?name=Sean%20Paul&station=Introduction%20Meeting");
// Should not contain slugified versions
expect(result).not.toContain("sean-paul");
expect(result).not.toContain("introduction-meeting");
});

it("should slugify path variables but preserve query parameter variables", () => {
const fields = [createTextField("field1", "name", "Name")];
const routeValue = "/user/{name}/profile?name={name}";
const response = createFormResponse("field1", "Sean Paul", "Name");

const result = substituteVariables(routeValue, response, fields);
// Path variable should be slugified
expect(result).toContain("/user/sean-paul/profile");
// Query parameter should preserve original formatting
expect(result).toContain("name=Sean%20Paul");
});

it("should substitute text field values directly", () => {
const fields = [createTextField("field1", "username", "Username")];
const routeValue = "/user/{username}/profile";
Expand Down
22 changes: 19 additions & 3 deletions packages/app-store/routing-forms/lib/substituteVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const substituteVariables = (

let eventTypeUrl = routeValue;

const queryStringIndex = routeValue.indexOf("?");

variables.forEach((variable) => {
for (const key in response) {
const field = fields.find((field) => field.id === key);
Expand All @@ -35,9 +37,23 @@ export const substituteVariables = (
field,
value: response[key].value,
});
// ['abc', 'def'] ----toString---> 'abc,def' ----slugify---> 'abc-def'
const valueToSubstitute = slugify(humanReadableValues.toString());
eventTypeUrl = eventTypeUrl.replace(`{${variable}}`, valueToSubstitute);
const humanReadableString = humanReadableValues.toString();

const variablePattern = `{${variable}}`;
const variableIndex = eventTypeUrl.indexOf(variablePattern);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Computing variableIndex from the mutated URL without updating the query-string index can misclassify remaining path variables as query parameters and URL-encode them instead of slugifying.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/app-store/routing-forms/lib/substituteVariables.ts, line 43:

<comment>Computing `variableIndex` from the mutated URL without updating the query-string index can misclassify remaining path variables as query parameters and URL-encode them instead of slugifying.</comment>

<file context>
@@ -40,7 +40,7 @@ export const substituteVariables = (
 
         const variablePattern = `{${variable}}`;
-        const variableIndex = routeValue.indexOf(variablePattern);
+        const variableIndex = eventTypeUrl.indexOf(variablePattern);
         const isInQueryString = queryStringIndex !== -1 &amp;&amp; variableIndex &gt; queryStringIndex;
 
</file context>
Fix with Cubic

const isInQueryString = queryStringIndex !== -1 && variableIndex > queryStringIndex;

let valueToSubstitute: string;
if (isInQueryString) {
// For query parameters, preserve original formatting with proper URL encoding
valueToSubstitute = encodeURIComponent(humanReadableString);
} else {
// For URL path segments, slugify the value for URL safety
// ['abc', 'def'] ----toString---> 'abc,def' ----slugify---> 'abc-def'
valueToSubstitute = slugify(humanReadableString);
}

eventTypeUrl = eventTypeUrl.replace(variablePattern, valueToSubstitute);
}
}
});
Expand Down
Loading