summaryrefslogtreecommitdiffhomepage
path: root/src/features/workspaces/logic/route.ts
blob: 015c6d6892da5b686dbbc6d6212c66de09da6cd4 (plain)
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
/**
 * Pure routing logic for the workspaces feature — zero DOM, zero effects, zero Svelte.
 *
 * The app is URL-driven: the root path `/` is the workspaces HOME (lists all
 * workspaces); a single path segment `/<id>` opens the workspace with that id
 * (the slug). This module holds the pure mapping from a pathname to a `Route`,
 * plus the slug-validation rules mirrored from the backend's `PUT /workspaces/:id`.
 */

/**
 * The workspace slug regex (mirrors the backend's `PUT /workspaces/:id`
 * validation): 1–40 chars, lowercase alphanumeric with internal hyphens only
 * (no leading/trailing hyphen). `"default"` matches and is a valid (but
 * non-deletable) id.
 */
export const WORKSPACE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/;

/** A route derived from the URL path. */
export type Route = { readonly kind: "home" } | { readonly kind: "workspace"; readonly id: string };

/** The reserved id of the always-present fallback workspace. */
export const DEFAULT_WORKSPACE_ID = "default";

/**
 * Parse a pathname into a `Route`. `/` (or empty) → home; a leading segment →
 * the workspace with that id (URL-decoded, surrounding slashes trimmed). Deeper
 * paths take their FIRST segment (nested routes are not used in v1). Pure:
 * pathname in, route out. Does NOT validate the slug — an invalid id still
 * produces a `workspace` route; the backend's ensure call rejects it (the FE
 * surfaces the error).
 */
export function parsePath(pathname: string): Route {
  const trimmed = pathname.replace(/^\/+|\/+$/g, "");
  if (trimmed === "") return { kind: "home" };
  const first = trimmed.split("/")[0] ?? "";
  const id = safeDecode(first);
  if (id === "") return { kind: "home" };
  return { kind: "workspace", id };
}

/**
 * Whether a slug is valid for a NEW workspace (the form the backend accepts).
 * Used by the home view's "new workspace" input before navigating.
 */
export function isValidSlug(slug: string): boolean {
  return WORKSPACE_SLUG_RE.test(slug);
}

/**
 * Build the URL path for a workspace id. Used when navigating to / linking a
 * workspace. Pure: id in, path string out.
 */
export function workspacePath(id: string): string {
  return `/${id}`;
}

function safeDecode(segment: string): string {
  try {
    return decodeURIComponent(segment);
  } catch {
    return segment;
  }
}