From 40aacb6ba241e43c9a9c6034db4956ad5a34c784 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Fri, 27 Feb 2026 17:57:21 -0700 Subject: [PATCH 1/3] Remove double resolve from routeFor fns --- src/lib/utilities/route-for-base-path.test.ts | 230 ++++++++++++++++++ src/lib/utilities/route-for.ts | 97 +++----- 2 files changed, 270 insertions(+), 57 deletions(-) create mode 100644 src/lib/utilities/route-for-base-path.test.ts diff --git a/src/lib/utilities/route-for-base-path.test.ts b/src/lib/utilities/route-for-base-path.test.ts new file mode 100644 index 0000000000..693c940ae2 --- /dev/null +++ b/src/lib/utilities/route-for-base-path.test.ts @@ -0,0 +1,230 @@ +import { describe, expect, it, vi } from 'vitest'; + +const BASE_PATH = '/temporal'; + +vi.mock('$app/paths', () => ({ + resolve: (route: string, params?: Record) => { + let resolved = route; + if (params) { + resolved = Object.entries(params).reduce( + (path, [key, value]) => path.replace(`[${key}]`, value), + resolved, + ); + } + return `${BASE_PATH}${resolved}`; + }, +})); + +import * as routeForModule from './route-for'; +import { + routeForArchivalEventHistory, + routeForArchivalWorkflows, + routeForAuthentication, + routeForBatchOperation, + routeForBatchOperations, + routeForCallStack, + routeForEventHistory, + routeForEventHistoryEvent, + routeForEventHistoryImport, + routeForLoginPage, + routeForNamespace, + routeForNamespaces, + routeForNamespaceSelector, + routeForNexus, + routeForNexusEndpoint, + routeForNexusEndpointCreate, + routeForNexusEndpointEdit, + routeForNexusLinks, + routeForPendingActivities, + routeForRelationships, + routeForSchedule, + routeForScheduleCreate, + routeForScheduleEdit, + routeForSchedules, + routeForStandaloneActivities, + routeForStandaloneActivitiesWithQuery, + routeForStandaloneActivityDetails, + routeForStandaloneActivityMetadata, + routeForStandaloneActivitySearchAttributes, + routeForStandaloneActivityWorkers, + routeForStartStandaloneActivity, + routeForTaskQueue, + routeForTimeline, + routeForUserMetadata, + routeForWorkerDeployment, + routeForWorkerDeployments, + routeForWorkerDeploymentVersion, + routeForWorkers, + routeForWorkflow, + routeForWorkflowMemo, + routeForWorkflowQuery, + routeForWorkflows, + routeForWorkflowSearchAttributes, + routeForWorkflowStart, + routeForWorkflowsWithQuery, + routeForWorkflowUpdate, +} from './route-for'; + +describe('routeFor functions should resolve the base path exactly once', () => { + const namespaceParams = { namespace: 'default' }; + const workflowParams = { + namespace: 'default', + workflow: 'wf-id', + run: 'run-id', + }; + const scheduleParams = { namespace: 'default', scheduleId: 'sched-1' }; + + const activityParams = { + namespace: 'default', + activityId: 'act-1', + runId: 'run-1', + }; + + const cases: [string, () => string][] = [ + ['routeForNamespaces', () => routeForNamespaces()], + ['routeForNexus', () => routeForNexus()], + ['routeForNexusEndpoint', () => routeForNexusEndpoint('ep-1')], + ['routeForNexusEndpointEdit', () => routeForNexusEndpointEdit('ep-1')], + ['routeForNexusEndpointCreate', () => routeForNexusEndpointCreate()], + ['routeForNamespace', () => routeForNamespace(namespaceParams)], + ['routeForNamespaceSelector', () => routeForNamespaceSelector()], + ['routeForWorkflows', () => routeForWorkflows(namespaceParams)], + [ + 'routeForArchivalWorkflows', + () => routeForArchivalWorkflows(namespaceParams), + ], + ['routeForWorkflow', () => routeForWorkflow(workflowParams)], + ['routeForSchedules', () => routeForSchedules(namespaceParams)], + ['routeForScheduleCreate', () => routeForScheduleCreate(namespaceParams)], + ['routeForSchedule', () => routeForSchedule(scheduleParams)], + ['routeForScheduleEdit', () => routeForScheduleEdit(scheduleParams)], + [ + 'routeForArchivalEventHistory', + () => routeForArchivalEventHistory(workflowParams), + ], + [ + 'routeForEventHistoryEvent', + () => routeForEventHistoryEvent({ ...workflowParams, eventId: '1' }), + ], + ['routeForEventHistory', () => routeForEventHistory(workflowParams)], + ['routeForTimeline', () => routeForTimeline(workflowParams)], + ['routeForWorkers', () => routeForWorkers(workflowParams)], + [ + 'routeForWorkerDeployments', + () => routeForWorkerDeployments(namespaceParams), + ], + [ + 'routeForWorkerDeployment', + () => + routeForWorkerDeployment({ + namespace: 'default', + deployment: 'dep-1', + }), + ], + [ + 'routeForWorkerDeploymentVersion', + () => + routeForWorkerDeploymentVersion({ + namespace: 'default', + deployment: 'dep-1', + version: 'v1', + }), + ], + ['routeForRelationships', () => routeForRelationships(workflowParams)], + [ + 'routeForTaskQueue', + () => routeForTaskQueue({ namespace: 'default', queue: 'q-1' }), + ], + ['routeForCallStack', () => routeForCallStack(workflowParams)], + ['routeForWorkflowQuery', () => routeForWorkflowQuery(workflowParams)], + ['routeForUserMetadata', () => routeForUserMetadata(workflowParams)], + [ + 'routeForWorkflowSearchAttributes', + () => routeForWorkflowSearchAttributes(workflowParams), + ], + ['routeForWorkflowMemo', () => routeForWorkflowMemo(workflowParams)], + ['routeForWorkflowUpdate', () => routeForWorkflowUpdate(workflowParams)], + [ + 'routeForPendingActivities', + () => routeForPendingActivities(workflowParams), + ], + ['routeForNexusLinks', () => routeForNexusLinks(workflowParams)], + ['routeForEventHistoryImport', () => routeForEventHistoryImport()], + ['routeForBatchOperations', () => routeForBatchOperations(namespaceParams)], + [ + 'routeForBatchOperation', + () => routeForBatchOperation({ namespace: 'default', jobId: 'job-1' }), + ], + [ + 'routeForStandaloneActivities', + () => routeForStandaloneActivities(namespaceParams), + ], + [ + 'routeForStandaloneActivitiesWithQuery', + () => + routeForStandaloneActivitiesWithQuery(namespaceParams, 'test-query'), + ], + [ + 'routeForStartStandaloneActivity', + () => routeForStartStandaloneActivity(namespaceParams), + ], + [ + 'routeForStandaloneActivityDetails', + () => routeForStandaloneActivityDetails(activityParams), + ], + [ + 'routeForStandaloneActivityWorkers', + () => routeForStandaloneActivityWorkers(activityParams), + ], + [ + 'routeForStandaloneActivitySearchAttributes', + () => routeForStandaloneActivitySearchAttributes(activityParams), + ], + [ + 'routeForStandaloneActivityMetadata', + () => routeForStandaloneActivityMetadata(activityParams), + ], + [ + 'routeForWorkflowStart', + () => routeForWorkflowStart({ namespace: 'default' }), + ], + [ + 'routeForWorkflowsWithQuery', + () => routeForWorkflowsWithQuery({ namespace: 'default', query: 'test' }), + ], + [ + 'routeForAuthentication', + () => + routeForAuthentication({ + settings: { auth: {}, baseUrl: 'https://example.com' }, + searchParams: new URLSearchParams(), + }), + ], + ['routeForLoginPage', () => routeForLoginPage('', false)], + ]; + + it.each(cases)('%s should resolve the base path', (_name, fn) => { + const result = fn(); + expect(result).toMatch(new RegExp(`${BASE_PATH}`)); + expect(result).not.toMatch(new RegExp(`${BASE_PATH}${BASE_PATH}`)); + }); + + it('should have a test case for every exported routeFor function', () => { + const testedNames = new Set(cases.map(([name]) => name)); + const exportedRouteForFunctions = Object.keys(routeForModule).filter( + (key) => + key.startsWith('routeFor') && + typeof routeForModule[key as keyof typeof routeForModule] === + 'function', + ); + + const missing = exportedRouteForFunctions.filter( + (name) => !testedNames.has(name), + ); + if (missing.length > 0) { + throw new Error( + `Missing base path test cases for: ${missing.join(', ')}. Add them to the cases array above.`, + ); + } + }); +}); diff --git a/src/lib/utilities/route-for.ts b/src/lib/utilities/route-for.ts index d0ce22f3b8..8c6a7b02a4 100644 --- a/src/lib/utilities/route-for.ts +++ b/src/lib/utilities/route-for.ts @@ -102,19 +102,19 @@ export const routeForNamespaceSelector = (): ResolvedPathname => { export const routeForWorkflows = ( parameters: NamespaceParameter, ): ResolvedPathname => { - return resolve(`${routeForNamespace(parameters)}/workflows`, {}); + return `${routeForNamespace(parameters)}/workflows`; }; export const routeForStandaloneActivities = ( parameters: NamespaceParameter, -): string => { +): ResolvedPathname => { return `${routeForNamespace(parameters)}/activities`; }; export const routeForStandaloneActivitiesWithQuery = ( parameters: NamespaceParameter, queryString: string, -): string => { +): ResolvedPathname => { const params = new URLSearchParams(); params.set('query', queryString); @@ -123,7 +123,7 @@ export const routeForStandaloneActivitiesWithQuery = ( export const routeForStartStandaloneActivity = ( parameters: NamespaceParameter & Partial, -): string => { +): ResolvedPathname => { const params = { activityId: parameters.activityId ?? '', activityType: parameters.activityType ?? '', @@ -136,39 +136,33 @@ export const routeForStartStandaloneActivity = ( const routeForStandaloneActivityBase = ( parameters: NamespaceParameter & { activityId: string; runId: string }, -) => { +): ResolvedPathname => { const activityId = encodeURIForSvelte(parameters.activityId); - return resolve( - `${routeForStandaloneActivities(parameters)}/[activityId]/[runId]`, - { - activityId, - runId: parameters.runId, - }, - ); + return `${routeForStandaloneActivities(parameters)}/${activityId}/${parameters.runId}`; }; export const routeForStandaloneActivityDetails = ( parameters: NamespaceParameter & { activityId: string; runId: string }, -) => { +): ResolvedPathname => { return `${routeForStandaloneActivityBase(parameters)}/details`; }; export const routeForStandaloneActivityWorkers = ( parameters: NamespaceParameter & { activityId: string; runId: string }, -) => { +): ResolvedPathname => { return `${routeForStandaloneActivityBase(parameters)}/workers`; }; export const routeForStandaloneActivitySearchAttributes = ( parameters: NamespaceParameter & { activityId: string; runId: string }, -) => { +): ResolvedPathname => { return `${routeForStandaloneActivityBase(parameters)}/search-attributes`; }; export const routeForStandaloneActivityMetadata = ( parameters: NamespaceParameter & { activityId: string; runId: string }, -) => { +): ResolvedPathname => { return `${routeForStandaloneActivityBase(parameters)}/metadata`; }; @@ -185,7 +179,7 @@ export const routeForWorkflowStart = ({ runId, taskQueue, workflowType, -}: StartWorkflowParameters): string => { +}: StartWorkflowParameters): ResolvedPathname => { return toURL(`${routeForNamespace({ namespace })}/workflows/start-workflow`, { workflowId: workflowId || '', runId: runId || '', @@ -198,7 +192,7 @@ export const routeForWorkflowsWithQuery = ({ namespace, query, page, -}: WorkflowsParameter): string | undefined => { +}: WorkflowsParameter): ResolvedPathname | undefined => { if (!BROWSER) { return undefined; } @@ -212,7 +206,7 @@ export const routeForWorkflowsWithQuery = ({ export const routeForArchivalWorkflows = ( parameters: NamespaceParameter, ): ResolvedPathname => { - return resolve(`${routeForNamespace(parameters)}/archival`, {}); + return `${routeForNamespace(parameters)}/archival`; }; export const routeForWorkflow = ({ @@ -222,19 +216,19 @@ export const routeForWorkflow = ({ }: WorkflowParameters): ResolvedPathname => { const id = encodeURIForSvelte(workflow); - return resolve(`${routeForWorkflows(parameters)}/[id]/[run]`, { id, run }); + return `${routeForWorkflows(parameters)}/${id}/${run}`; }; export const routeForSchedules = ( parameters: NamespaceParameter, ): ResolvedPathname => { - return resolve(`${routeForNamespace(parameters)}/schedules`, {}); + return `${routeForNamespace(parameters)}/schedules`; }; export const routeForScheduleCreate = ({ namespace, }: NamespaceParameter): ResolvedPathname => { - return resolve(`${routeForSchedules({ namespace })}/create`, {}); + return `${routeForSchedules({ namespace })}/create`; }; export const routeForSchedule = ({ @@ -243,7 +237,7 @@ export const routeForSchedule = ({ }: ScheduleParameters): ResolvedPathname => { const id = encodeURIForSvelte(scheduleId); - return resolve(`${routeForSchedules({ namespace })}/[id]`, { id }); + return `${routeForSchedules({ namespace })}/${id}`; }; export const routeForScheduleEdit = ({ @@ -252,7 +246,7 @@ export const routeForScheduleEdit = ({ }: ScheduleParameters): ResolvedPathname => { const id = encodeURIForSvelte(scheduleId); - return resolve(`${routeForSchedules({ namespace })}/[id]/edit`, { id }); + return `${routeForSchedules({ namespace })}/${id}/edit`; }; export const routeForArchivalEventHistory = ({ @@ -261,17 +255,14 @@ export const routeForArchivalEventHistory = ({ ...parameters }: WorkflowParameters): ResolvedPathname => { const id = encodeURIForSvelte(workflow); - return resolve( - `${routeForArchivalWorkflows(parameters)}/[id]/[run]/history`, - { id, run }, - ); + return `${routeForArchivalWorkflows(parameters)}/${id}/${run}/history`; }; export const routeForEventHistory = ({ queryParams, archival, ...parameters -}: EventHistoryParameters & { archival?: boolean }): string => { +}: EventHistoryParameters & { archival?: boolean }): ResolvedPathname => { if (archival) return toURL(routeForArchivalEventHistory(parameters)); const eventHistoryPath = `${routeForWorkflow(parameters)}/history`; return toURL(`${eventHistoryPath}`, queryParams); @@ -282,9 +273,7 @@ export const routeForEventHistoryEvent = ({ requestId, ...parameters }: EventParameters): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/history/events/[id]`, { - id: eventId || requestId, - }); + return `${routeForWorkflow(parameters)}/history/events/${eventId || requestId}`; }; export const routeForTimeline = ({ @@ -294,7 +283,7 @@ export const routeForTimeline = ({ }: WorkflowParameters & { queryParams?: Record; archival?: boolean; -}): string => { +}): ResolvedPathname => { if (archival) return toURL(routeForArchivalEventHistory(parameters)); const path = `${routeForWorkflow(parameters)}/timeline`; return toURL(path, queryParams); @@ -303,7 +292,7 @@ export const routeForTimeline = ({ export const routeForWorkers = ( parameters: WorkflowParameters, ): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/workers`, {}); + return `${routeForWorkflow(parameters)}/workers`; }; export const routeForWorkerDeployments = ({ @@ -337,19 +326,16 @@ export const routeForWorkerDeploymentVersion = ({ deployment: string; version: string; }): ResolvedPathname => { - return resolve( - `${routeForWorkerDeployment({ - namespace, - deployment, - })}/version/[version]`, - { version }, - ); + return `${routeForWorkerDeployment({ + namespace, + deployment, + })}/version/${version}`; }; export const routeForRelationships = ( parameters: WorkflowParameters, ): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/relationships`, {}); + return `${routeForWorkflow(parameters)}/relationships`; }; export const routeForTaskQueue = ( @@ -357,65 +343,62 @@ export const routeForTaskQueue = ( ): ResolvedPathname => { const queue = encodeURIForSvelte(parameters.queue); - return resolve( - `${routeForNamespace({ - namespace: parameters.namespace, - })}/task-queues/[queue]`, - { queue }, - ); + return `${routeForNamespace({ + namespace: parameters.namespace, + })}/task-queues/${queue}`; }; export const routeForCallStack = ( parameters: WorkflowParameters, ): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/call-stack`, {}); + return `${routeForWorkflow(parameters)}/call-stack`; }; export const routeForWorkflowQuery = ( parameters: WorkflowParameters, ): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/query`, {}); + return `${routeForWorkflow(parameters)}/query`; }; export const routeForUserMetadata = ( parameters: WorkflowParameters, ): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/user-metadata`, {}); + return `${routeForWorkflow(parameters)}/user-metadata`; }; export const routeForWorkflowSearchAttributes = ( parameters: WorkflowParameters, ): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/search-attributes`, {}); + return `${routeForWorkflow(parameters)}/search-attributes`; }; export const routeForWorkflowMemo = ( parameters: WorkflowParameters, ): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/memo`, {}); + return `${routeForWorkflow(parameters)}/memo`; }; export const routeForWorkflowUpdate = ( parameters: WorkflowParameters, ): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/update`, {}); + return `${routeForWorkflow(parameters)}/update`; }; export const routeForPendingActivities = ( parameters: WorkflowParameters, ): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/pending-activities`, {}); + return `${routeForWorkflow(parameters)}/pending-activities`; }; export const routeForNexusLinks = ( parameters: WorkflowParameters, ): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/nexus-links`, {}); + return `${routeForWorkflow(parameters)}/nexus-links`; }; export const routeForAuthentication = ( parameters: AuthenticationParameters, -): string => { +): ResolvedPathname => { const { settings, searchParams: currentSearchParams, originUrl } = parameters; const login = new URL(resolve('/auth/sso', {}), settings.baseUrl); From 93d7d370c1666da997489af603f4276bc9171713 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Fri, 27 Feb 2026 18:25:12 -0700 Subject: [PATCH 2/3] Add gotoResolved to fix eslint error and keep type safety --- .../components/command-palette/index.svelte | 22 +- .../import/event-history-file-import.svelte | 5 +- src/lib/components/login-button.svelte | 5 +- .../activity-actions.svelte | 5 +- src/lib/components/workflow-actions.svelte | 4 +- src/lib/pages/schedule-view.svelte | 4 +- src/lib/pages/schedules.svelte | 5 +- src/lib/pages/workflow-history.svelte | 8 +- src/lib/stores/schedules.ts | 8 +- src/lib/utilities/goto-resolved.ts | 11 + src/lib/utilities/route-for-base-path.test.ts | 4 +- src/lib/utilities/route-for.ts | 210 ++++++++++-------- src/routes/(app)/+layout.svelte | 5 +- src/routes/(app)/+page.svelte | 9 +- src/routes/(app)/nexus/[id]/edit/+page.svelte | 18 +- src/routes/(app)/nexus/create/+page.svelte | 4 +- .../(app)/select-namespace/+page.svelte | 4 +- 17 files changed, 178 insertions(+), 153 deletions(-) create mode 100644 src/lib/utilities/goto-resolved.ts diff --git a/src/lib/components/command-palette/index.svelte b/src/lib/components/command-palette/index.svelte index 77a529bad6..e57e288fe8 100644 --- a/src/lib/components/command-palette/index.svelte +++ b/src/lib/components/command-palette/index.svelte @@ -1,12 +1,12 @@ diff --git a/src/lib/components/standalone-activities/activity-actions.svelte b/src/lib/components/standalone-activities/activity-actions.svelte index 7d184792b4..c8412168dc 100644 --- a/src/lib/components/standalone-activities/activity-actions.svelte +++ b/src/lib/components/standalone-activities/activity-actions.svelte @@ -1,6 +1,4 @@ diff --git a/src/routes/(app)/nexus/[id]/edit/+page.svelte b/src/routes/(app)/nexus/[id]/edit/+page.svelte index c79737d025..40056e801b 100644 --- a/src/routes/(app)/nexus/[id]/edit/+page.svelte +++ b/src/routes/(app)/nexus/[id]/edit/+page.svelte @@ -1,6 +1,5 @@ {#if endpoint}
- + {translate('nexus.back-to-endpoint')}
{/if} diff --git a/src/routes/(app)/nexus/create/+page.svelte b/src/routes/(app)/nexus/create/+page.svelte index cb40286a71..f1a9a07b76 100644 --- a/src/routes/(app)/nexus/create/+page.svelte +++ b/src/routes/(app)/nexus/create/+page.svelte @@ -1,5 +1,4 @@ diff --git a/src/lib/components/standalone-activities/activity-actions.svelte b/src/lib/components/standalone-activities/activity-actions.svelte index c8412168dc..7d184792b4 100644 --- a/src/lib/components/standalone-activities/activity-actions.svelte +++ b/src/lib/components/standalone-activities/activity-actions.svelte @@ -1,4 +1,6 @@ diff --git a/src/routes/(app)/nexus/[id]/edit/+page.svelte b/src/routes/(app)/nexus/[id]/edit/+page.svelte index 40056e801b..f4c44f50bb 100644 --- a/src/routes/(app)/nexus/[id]/edit/+page.svelte +++ b/src/routes/(app)/nexus/[id]/edit/+page.svelte @@ -1,4 +1,5 @@