All files / src/utils navigation.ts

76.85% Statements 93/121
92.85% Branches 26/28
57.14% Functions 8/14
76.85% Lines 93/121

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214              1x 1x               1x                                 1x 3x 3x     1x 5x 5x 5x           1x 10x 10x 10x 10x 10x 10x 10x 10x 5x 5x   1x 5x 4x 4x 10x 3x 3x 7x 7x     1x 2x 1x 1x 2x       1x 1x     1x 5x 5x 5x 5x 5x 5x 4x 4x 5x 2x 2x 5x 5x 2x 2x 2x 5x 5x     1x 1x 1x 1x     1x     1x     1x 4x 4x     1x         1x     1x     1x     1x     1x     1x     1x 2x 2x           1x             1x         1x     1x     1x         1x     1x     1x     1x     1x     1x     1x     1x     1x     1x     1x     1x     1x      
/**
 * Centralized URL builder functions and navigation helpers for all application routes.
 *
 * Every component that needs to navigate should import from here
 * instead of hardcoding URL strings.
 */
 
import { useCallback } from "react";
import { useNavigate, type NavigateOptions, type To } from "react-router";
 
/**
 * Wrapper around react-router's `useNavigate` that returns a fire-and-forget
 * navigate function. This avoids lint conflicts between `no-floating-promises`
 * (which wants the returned `Promise<void> | void` handled) and `no-void`
 * (which forbids the `void` operator).
 */
export function useAppNavigate(): (to: To | number, options?: NavigateOptions) => void {
  const nav = useNavigate();
  return useCallback(
    (to: To | number, options?: NavigateOptions) => {
      if (typeof to === "number") {
        // eslint-disable-next-line @typescript-eslint/no-floating-promises
        nav(to);
      } else {
        // eslint-disable-next-line @typescript-eslint/no-floating-promises
        nav(to, options);
      }
    },
    [nav],
  );
}
 
/** Build URL for a session detail page. */
export function sessionUrl(sessionId: string): string {
  return `/sessions/${encodeURIComponent(sessionId)}`;
}
 
/** Build URL for a workspace overview page, nested under its environment when available. */
export function workspaceUrl(workspaceId: string, environmentId?: string): string {
  if (environmentId) {
    return `/environments/${encodeURIComponent(environmentId)}/workspaces/${encodeURIComponent(workspaceId)}`;
  }
  // Fallback to legacy route so WorkspaceRedirect can resolve the environment.
  return `/workspaces/${encodeURIComponent(workspaceId)}`;
}
 
/** Build URL for a task detail page, optionally targeting a specific tab and workspace/environment scope. */
export function taskUrl(
  taskId: string,
  tab?: "stream",
  workspaceId?: string,
  environmentId?: string,
): string {
  const encodedTaskId = encodeURIComponent(taskId);
  let base: string;
  if (workspaceId && environmentId) {
    base = `/environments/${encodeURIComponent(environmentId)}/workspaces/${encodeURIComponent(workspaceId)}/tasks/${encodedTaskId}`;
  } else if (workspaceId) {
    // Fallback to legacy route so WorkspaceRedirect can resolve the environment.
    base = `/workspaces/${encodeURIComponent(workspaceId)}/tasks/${encodedTaskId}`;
  } else {
    base = `/tasks/${encodedTaskId}`;
  }
  if (tab) {
    return `${base}/${tab}`;
  }
  return base;
}
 
/** Build URL for the task edit page. */
export function taskEditUrl(taskId: string, workspaceId?: string, environmentId?: string): string {
  if (workspaceId && environmentId) {
    return `/environments/${encodeURIComponent(environmentId)}/workspaces/${encodeURIComponent(workspaceId)}/tasks/${encodeURIComponent(taskId)}/edit`;
  }
  if (workspaceId) {
    // Fallback to legacy route so WorkspaceRedirect can resolve the environment.
    return `/workspaces/${encodeURIComponent(workspaceId)}/tasks/${encodeURIComponent(taskId)}/edit`;
  }
  return `/tasks/${encodeURIComponent(taskId)}/edit`;
}
 
/** Build URL for the new task form. */
export function newTaskUrl(
  workspaceId?: string,
  parentTaskId?: string,
  environmentId?: string,
): string {
  const params = new URLSearchParams();
  if (workspaceId) {
    params.set("workspace", workspaceId);
  }
  if (parentTaskId) {
    params.set("parent", parentTaskId);
  }
  const qs = params.toString();
  if (workspaceId && environmentId) {
    const base = `/environments/${encodeURIComponent(environmentId)}/workspaces/${encodeURIComponent(workspaceId)}/tasks/new`;
    return parentTaskId ? `${base}?parent=${encodeURIComponent(parentTaskId)}` : base;
  }
  return qs ? `/tasks/new?${qs}` : "/tasks/new";
}
 
/** Build URL for the new chat form. */
export function newChatUrl(environmentId: string): string {
  const params = new URLSearchParams({ env: environmentId });
  return `/sessions/new?${params.toString()}`;
}
 
/** URL for the environments landing page. */
export const ENVIRONMENTS_URL: string = "/environments";
 
/** URL for the new environment form. */
export const NEW_ENVIRONMENT_URL: string = "/environments/new";
 
/** Build URL for an environment detail page. */
export function environmentUrl(environmentId: string): string {
  return `/environments/${encodeURIComponent(environmentId)}`;
}
 
/** Build URL for the environment edit page. */
export function environmentEditUrl(environmentId: string): string {
  return `/environments/${encodeURIComponent(environmentId)}/edit`;
}
 
/** URL for the settings page. */
export const SETTINGS_URL: string = "/settings";
 
/** URL for the settings environments tab. */
export const SETTINGS_ENVIRONMENTS_URL: string = "/settings/environments";
 
/** URL for the settings credentials tab. */
export const SETTINGS_CREDENTIALS_URL: string = "/settings/credentials";
 
/** URL for the GitHub accounts settings tab. */
export const SETTINGS_GITHUB_ACCOUNTS_URL: string = "/settings/github-accounts";
 
/** URL for the top-level Persona Library page. */
export const PERSONAS_URL: string = "/personas";
 
/** URL for the new persona form. */
export const NEW_PERSONA_URL: string = "/personas/new";
 
/** Build URL for a persona detail page. */
export function personaUrl(personaId: string): string {
  return `/personas/${encodeURIComponent(personaId)}`;
}
 
/** Possible agent tab identifiers for URL construction (#1419). */
export type AgentTab = "chat" | "sessions" | "schedules" | "settings";
 
/** Build URL for an agent page, optionally targeting a specific tab. */
export function agentUrl(agentId: string, tab?: AgentTab): string {
  const base = `/agents/${encodeURIComponent(agentId)}`;
  if (!tab || tab === "chat") return base;
  return `${base}/${tab}`;
}
 
/** Routes that render their own nav bar and suppress the global AppNav. */
export function hasOwnNav(pathname: string): boolean {
  return /^\/agents\//.test(pathname);
}
 
/** URL for the top-level Schedules surface. */
export const SCHEDULES_URL: string = "/schedules";
 
/** URL for the new schedule form. */
export const NEW_SCHEDULE_URL: string = "/schedules/new";
 
/** Build URL for a schedule detail page. */
export function scheduleUrl(scheduleId: string): string {
  return `/schedules/${encodeURIComponent(scheduleId)}`;
}
 
/** URL for the settings appearance tab. */
export const SETTINGS_APPEARANCE_URL: string = "/settings/appearance";
 
/** URL for the settings about tab. */
export const SETTINGS_ABOUT_URL: string = "/settings/about";
 
/** URL for the keyboard shortcuts reference tab. */
export const SETTINGS_SHORTCUTS_URL: string = "/settings/shortcuts";
 
/** URL for the device pairing page. */
export const PAIR_PATH: string = "/pair";
 
/** URL for the root-task ("Root") chat page. */
export const CHAT_URL: string = "/chat";
 
/** URL for the Coordination page (read-only IPC stream inventory). */
export const COORDINATION_URL: string = "/coordination";
 
/** URL for the Sessions activity monitor (all sessions, grouped by environment). */
export const SESSIONS_URL: string = "/sessions";
 
/** URL for the home dashboard page. */
export const HOME_URL: string = "/";
 
/** URL for the tasks landing page. */
export const TASKS_URL: string = "/tasks";
 
/** URL for the workspaces landing page. */
export const WORKSPACES_URL: string = "/workspaces";
 
/** URL for the new workspace form. */
export const NEW_WORKSPACE_URL: string = "/workspaces/new";
 
/** URL for the knowledge graph explorer page. */
export const KNOWLEDGE_URL: string = "/knowledge";
 
/** Build URL for the root-task chat page. */
export function chatUrl(): string {
  return CHAT_URL;
}