summaryrefslogtreecommitdiffhomepage
path: root/src/features/workspaces/logic
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-26 22:21:55 +0900
committerAdam Malczewski <[email protected]>2026-06-26 22:23:39 +0900
commitc333fcec32b1f90bf0da6bb14d2609c20e38a74f (patch)
tree0db3ec77a6838c4f800c362df0de3c6cd8544431 /src/features/workspaces/logic
parent1285564f12238b22f6b39b9f3fbcecaca8456911 (diff)
downloaddispatch-web-c333fcec32b1f90bf0da6bb14d2609c20e38a74f.tar.gz
dispatch-web-c333fcec32b1f90bf0da6bb14d2609c20e38a74f.zip
style: switch from tabs to 2-space indentation (incl. svelte)
Diffstat (limited to 'src/features/workspaces/logic')
-rw-r--r--src/features/workspaces/logic/route.test.ts112
-rw-r--r--src/features/workspaces/logic/route.ts26
-rw-r--r--src/features/workspaces/logic/view-model.test.ts98
-rw-r--r--src/features/workspaces/logic/view-model.ts32
4 files changed, 134 insertions, 134 deletions
diff --git a/src/features/workspaces/logic/route.test.ts b/src/features/workspaces/logic/route.test.ts
index a6da3e1..96e0ff3 100644
--- a/src/features/workspaces/logic/route.test.ts
+++ b/src/features/workspaces/logic/route.test.ts
@@ -1,77 +1,77 @@
import { describe, expect, it } from "vitest";
import {
- DEFAULT_WORKSPACE_ID,
- isValidSlug,
- parsePath,
- WORKSPACE_SLUG_RE,
- workspacePath,
+ DEFAULT_WORKSPACE_ID,
+ isValidSlug,
+ parsePath,
+ WORKSPACE_SLUG_RE,
+ workspacePath,
} from "./route";
describe("parsePath", () => {
- it("treats the root path as home", () => {
- expect(parsePath("/")).toEqual({ kind: "home" });
- expect(parsePath("")).toEqual({ kind: "home" });
- });
+ it("treats the root path as home", () => {
+ expect(parsePath("/")).toEqual({ kind: "home" });
+ expect(parsePath("")).toEqual({ kind: "home" });
+ });
- it("trims surrounding slashes", () => {
- expect(parsePath("//")).toEqual({ kind: "home" });
- expect(parsePath("/my-ws/")).toEqual({ kind: "workspace", id: "my-ws" });
- });
+ it("trims surrounding slashes", () => {
+ expect(parsePath("//")).toEqual({ kind: "home" });
+ expect(parsePath("/my-ws/")).toEqual({ kind: "workspace", id: "my-ws" });
+ });
- it("parses a single segment as a workspace id", () => {
- expect(parsePath("/default")).toEqual({ kind: "workspace", id: "default" });
- expect(parsePath("/my-workspace")).toEqual({ kind: "workspace", id: "my-workspace" });
- expect(parsePath("/ws1")).toEqual({ kind: "workspace", id: "ws1" });
- });
+ it("parses a single segment as a workspace id", () => {
+ expect(parsePath("/default")).toEqual({ kind: "workspace", id: "default" });
+ expect(parsePath("/my-workspace")).toEqual({ kind: "workspace", id: "my-workspace" });
+ expect(parsePath("/ws1")).toEqual({ kind: "workspace", id: "ws1" });
+ });
- it("takes only the first segment of a deeper path", () => {
- expect(parsePath("/foo/bar")).toEqual({ kind: "workspace", id: "foo" });
- expect(parsePath("/foo/bar/baz")).toEqual({ kind: "workspace", id: "foo" });
- });
+ it("takes only the first segment of a deeper path", () => {
+ expect(parsePath("/foo/bar")).toEqual({ kind: "workspace", id: "foo" });
+ expect(parsePath("/foo/bar/baz")).toEqual({ kind: "workspace", id: "foo" });
+ });
- it("URL-decodes the segment", () => {
- expect(parsePath("/my%20ws")).toEqual({ kind: "workspace", id: "my ws" });
- });
+ it("URL-decodes the segment", () => {
+ expect(parsePath("/my%20ws")).toEqual({ kind: "workspace", id: "my ws" });
+ });
- it("does not validate the slug — an invalid id is still a workspace route", () => {
- expect(parsePath("/UPPER")).toEqual({ kind: "workspace", id: "UPPER" });
- expect(parsePath("/has space")).toEqual({ kind: "workspace", id: "has space" });
- });
+ it("does not validate the slug — an invalid id is still a workspace route", () => {
+ expect(parsePath("/UPPER")).toEqual({ kind: "workspace", id: "UPPER" });
+ expect(parsePath("/has space")).toEqual({ kind: "workspace", id: "has space" });
+ });
});
describe("isValidSlug", () => {
- it("accepts lowercase alphanumeric + internal hyphens", () => {
- expect(isValidSlug("default")).toBe(true);
- expect(isValidSlug("my-workspace")).toBe(true);
- expect(isValidSlug("a")).toBe(true);
- expect(isValidSlug("ws-1")).toBe(true);
- });
+ it("accepts lowercase alphanumeric + internal hyphens", () => {
+ expect(isValidSlug("default")).toBe(true);
+ expect(isValidSlug("my-workspace")).toBe(true);
+ expect(isValidSlug("a")).toBe(true);
+ expect(isValidSlug("ws-1")).toBe(true);
+ });
- it("accepts up to 40 chars", () => {
- expect(isValidSlug("a".repeat(40))).toBe(true);
- });
+ it("accepts up to 40 chars", () => {
+ expect(isValidSlug("a".repeat(40))).toBe(true);
+ });
- it("rejects empty and too-long", () => {
- expect(isValidSlug("")).toBe(false);
- expect(isValidSlug("a".repeat(41))).toBe(false);
- });
+ it("rejects empty and too-long", () => {
+ expect(isValidSlug("")).toBe(false);
+ expect(isValidSlug("a".repeat(41))).toBe(false);
+ });
- it("rejects uppercase, spaces, and leading/trailing hyphens", () => {
- expect(isValidSlug("MyWS")).toBe(false);
- expect(isValidSlug("has space")).toBe(false);
- expect(isValidSlug("-leading")).toBe(false);
- expect(isValidSlug("trailing-")).toBe(false);
- expect(isValidSlug("double--hyphen")).toBe(true); // internal doubles are allowed by the regex
- });
+ it("rejects uppercase, spaces, and leading/trailing hyphens", () => {
+ expect(isValidSlug("MyWS")).toBe(false);
+ expect(isValidSlug("has space")).toBe(false);
+ expect(isValidSlug("-leading")).toBe(false);
+ expect(isValidSlug("trailing-")).toBe(false);
+ expect(isValidSlug("double--hyphen")).toBe(true); // internal doubles are allowed by the regex
+ });
- it("WORKSPACE_SLUG_RE matches the default id", () => {
- expect(WORKSPACE_SLUG_RE.test(DEFAULT_WORKSPACE_ID)).toBe(true);
- });
+ it("WORKSPACE_SLUG_RE matches the default id", () => {
+ expect(WORKSPACE_SLUG_RE.test(DEFAULT_WORKSPACE_ID)).toBe(true);
+ });
});
describe("workspacePath", () => {
- it("builds the URL path for a workspace id", () => {
- expect(workspacePath("default")).toBe("/default");
- expect(workspacePath("my-ws")).toBe("/my-ws");
- });
+ it("builds the URL path for a workspace id", () => {
+ expect(workspacePath("default")).toBe("/default");
+ expect(workspacePath("my-ws")).toBe("/my-ws");
+ });
});
diff --git a/src/features/workspaces/logic/route.ts b/src/features/workspaces/logic/route.ts
index e30dc16..015c6d6 100644
--- a/src/features/workspaces/logic/route.ts
+++ b/src/features/workspaces/logic/route.ts
@@ -30,12 +30,12 @@ export const DEFAULT_WORKSPACE_ID = "default";
* 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 };
+ 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 };
}
/**
@@ -43,7 +43,7 @@ export function parsePath(pathname: string): Route {
* Used by the home view's "new workspace" input before navigating.
*/
export function isValidSlug(slug: string): boolean {
- return WORKSPACE_SLUG_RE.test(slug);
+ return WORKSPACE_SLUG_RE.test(slug);
}
/**
@@ -51,13 +51,13 @@ export function isValidSlug(slug: string): boolean {
* workspace. Pure: id in, path string out.
*/
export function workspacePath(id: string): string {
- return `/${id}`;
+ return `/${id}`;
}
function safeDecode(segment: string): string {
- try {
- return decodeURIComponent(segment);
- } catch {
- return segment;
- }
+ try {
+ return decodeURIComponent(segment);
+ } catch {
+ return segment;
+ }
}
diff --git a/src/features/workspaces/logic/view-model.test.ts b/src/features/workspaces/logic/view-model.test.ts
index a4ed19c..86342e7 100644
--- a/src/features/workspaces/logic/view-model.test.ts
+++ b/src/features/workspaces/logic/view-model.test.ts
@@ -3,66 +3,66 @@ import { describe, expect, it } from "vitest";
import { pageTitle, relativeTime } from "./view-model";
describe("relativeTime", () => {
- const now = 1_000_000_000_000; // 2001-09-09
+ const now = 1_000_000_000_000; // 2001-09-09
- it("is 'now' within a minute", () => {
- expect(relativeTime(now, now)).toBe("now");
- expect(relativeTime(now - 59_000, now)).toBe("now");
- });
+ it("is 'now' within a minute", () => {
+ expect(relativeTime(now, now)).toBe("now");
+ expect(relativeTime(now - 59_000, now)).toBe("now");
+ });
- it("is minutes under an hour", () => {
- expect(relativeTime(now - 5 * 60_000, now)).toBe("5m");
- expect(relativeTime(now - 59 * 60_000, now)).toBe("59m");
- });
+ it("is minutes under an hour", () => {
+ expect(relativeTime(now - 5 * 60_000, now)).toBe("5m");
+ expect(relativeTime(now - 59 * 60_000, now)).toBe("59m");
+ });
- it("is hours under a day", () => {
- expect(relativeTime(now - 2 * 60 * 60_000, now)).toBe("2h");
- });
+ it("is hours under a day", () => {
+ expect(relativeTime(now - 2 * 60 * 60_000, now)).toBe("2h");
+ });
- it("is days under a week", () => {
- expect(relativeTime(now - 3 * 24 * 60 * 60_000, now)).toBe("3d");
- });
+ it("is days under a week", () => {
+ expect(relativeTime(now - 3 * 24 * 60 * 60_000, now)).toBe("3d");
+ });
- it("is a short date beyond a week", () => {
- // 7+ days ago: just check it is a MM/DD string.
- const s = relativeTime(now - 10 * 24 * 60 * 60_000, now);
- expect(s).toMatch(/^\d{2}\/\d{2}$/);
- });
+ it("is a short date beyond a week", () => {
+ // 7+ days ago: just check it is a MM/DD string.
+ const s = relativeTime(now - 10 * 24 * 60 * 60_000, now);
+ expect(s).toMatch(/^\d{2}\/\d{2}$/);
+ });
});
describe("pageTitle", () => {
- // Minimal valid WorkspaceEntry (the irrelevant metadata is zeroed).
- const ws = (id: string, title: string): WorkspaceEntry => ({
- id,
- title,
- defaultCwd: null,
- defaultComputerId: null,
- createdAt: 0,
- lastActivityAt: 0,
- conversationCount: 0,
- });
+ // Minimal valid WorkspaceEntry (the irrelevant metadata is zeroed).
+ const ws = (id: string, title: string): WorkspaceEntry => ({
+ id,
+ title,
+ defaultCwd: null,
+ defaultComputerId: null,
+ createdAt: 0,
+ lastActivityAt: 0,
+ conversationCount: 0,
+ });
- it("is 'Dispatch' for the home route", () => {
- expect(pageTitle({ kind: "home" }, [])).toBe("Dispatch");
- expect(pageTitle({ kind: "home" }, [ws("default", "Default")])).toBe("Dispatch");
- });
+ it("is 'Dispatch' for the home route", () => {
+ expect(pageTitle({ kind: "home" }, [])).toBe("Dispatch");
+ expect(pageTitle({ kind: "home" }, [ws("default", "Default")])).toBe("Dispatch");
+ });
- it("is 'Dispatch: {title}' for a workspace with a display title", () => {
- const list = [ws("default", "Default"), ws("my-ws", "My Workspace")];
- expect(pageTitle({ kind: "workspace", id: "my-ws" }, list)).toBe("Dispatch: My Workspace");
- });
+ it("is 'Dispatch: {title}' for a workspace with a display title", () => {
+ const list = [ws("default", "Default"), ws("my-ws", "My Workspace")];
+ expect(pageTitle({ kind: "workspace", id: "my-ws" }, list)).toBe("Dispatch: My Workspace");
+ });
- it("falls back to the slug (id) until the list has loaded the workspace", () => {
- expect(pageTitle({ kind: "workspace", id: "pending" }, [])).toBe("Dispatch: pending");
- });
+ it("falls back to the slug (id) until the list has loaded the workspace", () => {
+ expect(pageTitle({ kind: "workspace", id: "pending" }, [])).toBe("Dispatch: pending");
+ });
- it("uses the id as the title when it was never customized (defaults to id)", () => {
- const list = [ws("default", "default")];
- expect(pageTitle({ kind: "workspace", id: "default" }, list)).toBe("Dispatch: default");
- });
+ it("uses the id as the title when it was never customized (defaults to id)", () => {
+ const list = [ws("default", "default")];
+ expect(pageTitle({ kind: "workspace", id: "default" }, list)).toBe("Dispatch: default");
+ });
- it("matches by id, not title", () => {
- const list = [ws("a", "shared-title"), ws("b", "shared-title")];
- expect(pageTitle({ kind: "workspace", id: "b" }, list)).toBe("Dispatch: shared-title");
- });
+ it("matches by id, not title", () => {
+ const list = [ws("a", "shared-title"), ws("b", "shared-title")];
+ expect(pageTitle({ kind: "workspace", id: "b" }, list)).toBe("Dispatch: shared-title");
+ });
});
diff --git a/src/features/workspaces/logic/view-model.ts b/src/features/workspaces/logic/view-model.ts
index e904514..6dd64c6 100644
--- a/src/features/workspaces/logic/view-model.ts
+++ b/src/features/workspaces/logic/view-model.ts
@@ -14,9 +14,9 @@ import type { Route } from "./route";
* workspaces in, string out.
*/
export function pageTitle(route: Route, workspaces: readonly WorkspaceEntry[]): string {
- if (route.kind === "home") return "Dispatch";
- const ws = workspaces.find((w) => w.id === route.id);
- return `Dispatch: ${ws?.title ?? route.id}`;
+ if (route.kind === "home") return "Dispatch";
+ const ws = workspaces.find((w) => w.id === route.id);
+ return `Dispatch: ${ws?.title ?? route.id}`;
}
/**
@@ -25,17 +25,17 @@ export function pageTitle(route: Route, workspaces: readonly WorkspaceEntry[]):
* (a workspace just created) read as "now".
*/
export function relativeTime(then: number, now: number): string {
- const diff = now - then;
- if (diff < 60_000) return "now";
- const mins = Math.floor(diff / 60_000);
- if (mins < 60) return `${mins}m`;
- const hours = Math.floor(mins / 60);
- if (hours < 24) return `${hours}h`;
- const days = Math.floor(hours / 24);
- if (days < 7) return `${days}d`;
- // Beyond a week: a short date (MM/DD). Uses UTC parts for determinism in tests.
- const d = new Date(then);
- const month = String(d.getUTCMonth() + 1).padStart(2, "0");
- const day = String(d.getUTCDate()).padStart(2, "0");
- return `${month}/${day}`;
+ const diff = now - then;
+ if (diff < 60_000) return "now";
+ const mins = Math.floor(diff / 60_000);
+ if (mins < 60) return `${mins}m`;
+ const hours = Math.floor(mins / 60);
+ if (hours < 24) return `${hours}h`;
+ const days = Math.floor(hours / 24);
+ if (days < 7) return `${days}d`;
+ // Beyond a week: a short date (MM/DD). Uses UTC parts for determinism in tests.
+ const d = new Date(then);
+ const month = String(d.getUTCMonth() + 1).padStart(2, "0");
+ const day = String(d.getUTCDate()).padStart(2, "0");
+ return `${month}/${day}`;
}