Skip to content
Merged
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
6 changes: 3 additions & 3 deletions apps/web/src/chat/artifact-chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//
// Opening a chip is a callback the host supplies (mirrors `onOpenProfile`
// and `onOpenThread` in `timeline.tsx`): this package owns no router. A
// second, artifactId-only affordance — "Open in Files" — hands the host a
// second, artifactId-only affordance — "Open in Artifacts" — hands the host a
// separate callback so it can navigate there directly; it only
// ever renders when `artifactId` is present, since a blob-only part has no
// Library row to deep-link to.
Expand Down Expand Up @@ -52,8 +52,8 @@ export function ArtifactChip({
<button
type="button"
className="chat-artifact-chip-library"
aria-label="Open in Files"
title="Open in Files"
aria-label="Open in Artifacts"
title="Open in Artifacts"
onClick={() => onOpenInLibrary(part)}
>
<FolderOpen />
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/chat/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const CHAT_STRINGS = {
composerHelpTitle: "Slash commands",
composerHelpNote: "Not sent as a message",
composerHelpClose: "Close",
runRoutineUnavailable: "Open Routines to create one",
runRoutineUnavailable: "Open Workflows to create one",
unnamedWorkbench: "Untitled conversation",
newWorkbenchTitle: NEW_WORKBENCH_LABEL,
unnamedRun: "Untitled agent",
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/command-palette-actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,14 @@ describe("runActionCommand", () => {
expect(consumePendingNewSkill()).toBe(true);
});

test("upload-artifact navigates to /files when off-route", async () => {
test("upload-artifact navigates to /artifacts when off-route", async () => {
const { ctx, navigated } = context({ path: "/agents" });
await runActionCommand("upload-artifact", ctx);
expect(navigated).toEqual(["/files"]);
expect(navigated).toEqual(["/artifacts"]);
});

test("upload-artifact does not navigate when already on /files", async () => {
const { ctx, navigated } = context({ path: "/files" });
test("upload-artifact does not navigate when already on /artifacts", async () => {
const { ctx, navigated } = context({ path: "/artifacts" });
await runActionCommand("upload-artifact", ctx);
expect(navigated).toEqual([]);
});
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/command-palette-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const ACTION_COMMANDS: readonly ActionCommand[] = [
{
id: "upload-artifact",
title: "Upload artifact",
subtitle: "Files · open dialog",
subtitle: "Artifacts · open dialog",
},
{ id: "toggle-theme", title: "Toggle theme", subtitle: "Light / dark" },
{
Expand All @@ -90,7 +90,7 @@ export const ACTION_COMMANDS: readonly ActionCommand[] = [
{
id: "go-insights",
title: "Go to insights",
subtitle: "Not in the nav · still routable",
subtitle: "Settings · still routable",
},
];

Expand Down Expand Up @@ -128,8 +128,8 @@ export async function runActionCommand(
}
case "upload-artifact": {
requestLibraryUpload({
alreadyOnLibrary: ctx.path === "/files" || ctx.path.startsWith("/files/"),
navigateToLibrary: () => ctx.navigate("/files"),
alreadyOnLibrary: ctx.path === "/artifacts" || ctx.path.startsWith("/artifacts/"),
navigateToLibrary: () => ctx.navigate("/artifacts"),
});
return;
}
Expand Down
18 changes: 9 additions & 9 deletions apps/web/src/command-palette-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,8 @@ export function CommandPaletteProvider({
);

// Order matches the mock's buildCmdkEntries: Commands, Agents &
// Channels, Pages, then the unscoped catalogs (Runs, Routines,
// Skills, Library), with People & agents last among the palette's
// Channels, Pages, then the unscoped catalogs (Runs, Workflows,
// Skills, Artifacts), with People & agents last among the palette's
// groups.
const sources = useMemo<readonly PaletteSource[]>(
() => [
Expand All @@ -373,9 +373,9 @@ export function CommandPaletteProvider({
items: workbenchItems,
},
{ id: "pages", heading: "Pages", kind: "pages", items: pageItems },
{ id: "routines", heading: "Routines", items: routineItems },
{ id: "routines", heading: "Workflows", items: routineItems },
{ id: "skills", heading: "Skills", items: skillItems },
{ id: "library", heading: "Files", items: libraryItems },
{ id: "library", heading: "Artifacts", items: libraryItems },
{
id: "people",
heading: "People & agents",
Expand Down Expand Up @@ -427,7 +427,7 @@ export function CommandPaletteProvider({
}
})();
}
navigate(`/routines/${encodeURIComponent(routineId)}`);
navigate(`/workflows/${encodeURIComponent(routineId)}`);
} else if (id.startsWith("action:")) {
void runActionCommand(id.slice("action:".length) as ActionCommandId, {
path,
Expand Down Expand Up @@ -459,8 +459,8 @@ export function CommandPaletteProvider({
} else if (id.startsWith("entity:routines:")) {
const routineId = id.slice("entity:routines:".length);
const title = routineItems.find((item) => item.id === id)?.title ?? routineId;
navigate(`/routines/${encodeURIComponent(routineId)}`);
pushRecent({ kind: "routines", id, title, subtitle: "Routine" });
navigate(`/workflows/${encodeURIComponent(routineId)}`);
pushRecent({ kind: "routines", id, title, subtitle: "Workflow" });
} else if (id.startsWith("entity:skills:")) {
const skillId = id.slice("entity:skills:".length);
const title = skillItems.find((item) => item.id === id)?.title ?? skillId;
Expand All @@ -469,9 +469,9 @@ export function CommandPaletteProvider({
pushRecent({ kind: "skills", id, title, subtitle: "Skill" });
} else if (id.startsWith("entity:library:")) {
const artifactId = id.slice("entity:library:".length);
const title = libraryItems.find((item) => item.id === id)?.title ?? "Files";
const title = libraryItems.find((item) => item.id === id)?.title ?? "Artifacts";
navigate(libraryArtifactPath(artifactId));
pushRecent({ kind: "library", id, title, subtitle: "Files" });
pushRecent({ kind: "library", id, title, subtitle: "Artifacts" });
}
setCommandPaletteOpen(false);
},
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/global-routines.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Scheduled workflow definitions across every bench the signed-in account
// belongs to — the aggregation both Routines surfaces read from: the
// roster at `/routines` and the detail page at `/routines/<definitionId>`.
// belongs to — the aggregation both Workflows surfaces read from: the
// roster at `/workflows` and the detail page at `/workflows/<definitionId>`.
import { toast } from "@corbits/react-ui";
import { reportError } from "@corbits/error-sink";
import { useMemo } from "react";
Expand All @@ -10,7 +10,7 @@ import type { APIQuery } from "@/lib/api-query";

import type { Principal } from "./api";
import { isBenchMembership, useBench } from "./bench-context";
import { ROUTINES_PATH_PREFIX } from "./path-ids";
import { WORKFLOWS_PATH_PREFIX } from "./path-ids";
import { tenantKeys } from "./query-client";
import {
listScheduledWorkflows,
Expand All @@ -28,7 +28,7 @@ export type GlobalRoutineRow = {
};

export function routineDetailPath(definitionId: string): string {
return `${ROUTINES_PATH_PREFIX}/${encodeURIComponent(definitionId)}`;
return `${WORKFLOWS_PATH_PREFIX}/${encodeURIComponent(definitionId)}`;
}

function useMemberBenches(): {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/last-workbench.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Files' workbench-first lens needs to know which workbench, if
// any, the user was just inside — a signal `/files` can't carry itself,
// any, the user was just inside — a signal `/artifacts` can't carry itself,
// since it's reached from the sidebar footer, not a workbench sub-route.
// Recorded per bench (a switch to a different bench must not default to a
// foreign bench's workbench) and read back once on mount — never a live
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/library-upload.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Library pageBand Upload is a cross-route action: the shell may fire it from
// another path before LibraryPage is mounted. When already on /files, open
// another path before LibraryPage is mounted. When already on /artifacts, open
// the picker immediately; otherwise record a one-shot pending flag the page
// consumes on mount. Keeps shell ↔ page coupling to this tiny module.

Expand Down
28 changes: 14 additions & 14 deletions apps/web/src/library/kind-filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import {

describe("libraryKindSegmentFromPath", () => {
test("returns empty for the bare library path", () => {
expect(libraryKindSegmentFromPath("/files")).toBe("");
expect(libraryKindSegmentFromPath("/files/")).toBe("");
expect(libraryKindSegmentFromPath("/artifacts")).toBe("");
expect(libraryKindSegmentFromPath("/artifacts/")).toBe("");
});

test("returns the first segment under /files", () => {
expect(libraryKindSegmentFromPath("/files/document")).toBe("document");
expect(libraryKindSegmentFromPath("/files/pdf/extra")).toBe("pdf");
test("returns the first segment under /artifacts", () => {
expect(libraryKindSegmentFromPath("/artifacts/document")).toBe("document");
expect(libraryKindSegmentFromPath("/artifacts/pdf/extra")).toBe("pdf");
});

test("returns empty for a path outside /files", () => {
expect(libraryKindSegmentFromPath("/routines")).toBe("");
test("returns empty for a path outside /artifacts", () => {
expect(libraryKindSegmentFromPath("/workflows")).toBe("");
});
});

Expand Down Expand Up @@ -78,7 +78,7 @@ describe("artifactMatchesLibraryKindSegment", () => {
describe("libraryArtifactPath / libraryArtifactIdFromPath", () => {
test("round-trips an artifact id through the deep link", () => {
const path = libraryArtifactPath("art_1");
expect(path).toBe("/files/a/art_1");
expect(path).toBe("/artifacts/a/art_1");
expect(libraryArtifactIdFromPath(path)).toBe("art_1");
});

Expand All @@ -88,17 +88,17 @@ describe("libraryArtifactPath / libraryArtifactIdFromPath", () => {
});

test("is null for a plain kind-nav path, never mistaken for a kind segment", () => {
expect(libraryArtifactIdFromPath("/files/document")).toBeNull();
expect(libraryArtifactIdFromPath("/files")).toBeNull();
expect(libraryArtifactIdFromPath("/routines")).toBeNull();
expect(libraryArtifactIdFromPath("/artifacts/document")).toBeNull();
expect(libraryArtifactIdFromPath("/artifacts")).toBeNull();
expect(libraryArtifactIdFromPath("/workflows")).toBeNull();
});

test("is null when the artifact segment is empty", () => {
expect(libraryArtifactIdFromPath("/files/a/")).toBeNull();
expect(libraryArtifactIdFromPath("/artifacts/a/")).toBeNull();
});

test("a malformed escape reads as no selection, not a throw", () => {
expect(() => libraryArtifactIdFromPath("/files/a/%E0%A4%A")).not.toThrow();
expect(libraryArtifactIdFromPath("/files/a/%E0%A4%A")).toBeNull();
expect(() => libraryArtifactIdFromPath("/artifacts/a/%E0%A4%A")).not.toThrow();
expect(libraryArtifactIdFromPath("/artifacts/a/%E0%A4%A")).toBeNull();
});
});
19 changes: 10 additions & 9 deletions apps/web/src/library/kind-filter.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
// Pure helpers for the Files kind nav (`/files`, `/files/document`, …) —
// this module's own names keep the "library" vocabulary (it's the
// internal name for the artifact store), but `LIBRARY_PATH` tracks the
// app's real mount point: `/library` stays routable only as a redirect
// to `/files` — see `legacy-settings-redirects.tsx`.
// Pure helpers for the Artifacts kind nav (`/artifacts`,
// `/artifacts/document`, …) — this module's own names keep the "library"
// vocabulary (it's the internal name for the artifact store), but
// `LIBRARY_PATH` tracks the app's real mount point: `/library` and
// `/files` both stay routable only as redirects to `/artifacts` — see
// `legacy-settings-redirects.tsx`.

import { decodedOrNull } from "@corbits/url-path";

import { titleExtension } from "./title-extension";
import type { ArtifactSummary } from "./artifact-summary";

const LIBRARY_PATH = "/files";
const LIBRARY_PATH = "/artifacts";

/** Kind nav segments in display order, excluding the implicit "all". */
export const LIBRARY_KIND_SEGMENTS = ["document", "sheet", "pdf", "routine"] as const;

export type LibraryKindSegment = (typeof LIBRARY_KIND_SEGMENTS)[number];

/** Path segment under `/files` used as a kind filter; empty means All. */
/** Path segment under `/artifacts` used as a kind filter; empty means All. */
export function libraryKindSegmentFromPath(path: string): string {
if (path === LIBRARY_PATH || path === `${LIBRARY_PATH}/`) return "";
if (!path.startsWith(`${LIBRARY_PATH}/`)) return "";
Expand All @@ -29,8 +30,8 @@ export function libraryKindSegmentFromPath(path: string): string {
* a kind filter, so the two never collide. */
const LIBRARY_ARTIFACT_SEGMENT = "a";

/** Deep link to one artifact selected in Files — the seam a chat
* artifact chip's "Open in Files" affordance navigates to. */
/** Deep link to one artifact selected in Artifacts — the seam a chat
* artifact chip's "Open in Artifacts" affordance navigates to. */
export function libraryArtifactPath(artifactId: string): string {
return `${LIBRARY_PATH}/${LIBRARY_ARTIFACT_SEGMENT}/${encodeURIComponent(artifactId)}`;
}
Expand Down
16 changes: 8 additions & 8 deletions apps/web/src/login-routing.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ function unmount(container: HTMLDivElement, root: Root): void {
}

describe("unauthenticated deep links redirect to /login with next=", () => {
for (const path of ["/files", "/agents", "/skills", "/insights"]) {
for (const path of ["/artifacts", "/agents", "/skills", "/insights"]) {
test(`${path} bounces to /login?next=${encodeURIComponent(path)}`, async () => {
const { container, root } = await mount(path, { kind: "signed-out" });
expect(window.location.pathname).toBe("/login");
Expand All @@ -124,9 +124,9 @@ describe("unauthenticated deep links redirect to /login with next=", () => {

describe("login round trip", () => {
test("signing in from a next= redirect returns to that path", async () => {
const { container, root } = await mount("/files", { kind: "signed-out" });
const { container, root } = await mount("/artifacts", { kind: "signed-out" });
expect(window.location.pathname).toBe("/login");
expect(window.location.search).toBe("?next=%2Ffiles");
expect(window.location.search).toBe("?next=%2Fartifacts");

// Drives the exact `handleSignedIn` wiring `main.tsx`'s `Root` gives
// `AuthScreen` — reading `next=` off the live URL — without simulating
Expand All @@ -135,12 +135,12 @@ describe("login round trip", () => {
capturedHandleSignedIn?.(user);
});
await flush();
expect(window.location.pathname).toBe("/files");
expect(window.location.pathname).toBe("/artifacts");
unmount(container, root);
});

test("already-authed visits to /login bounce home", async () => {
const { container, root } = await mount("/login?next=%2Ffiles", {
const { container, root } = await mount("/login?next=%2Fartifacts", {
kind: "signed-in",
user,
});
Expand All @@ -151,7 +151,7 @@ describe("login round trip", () => {

describe("authed deep links render their own page", () => {
const cases: readonly [string, string][] = [
["/files", "Files"],
["/artifacts", "Artifacts"],
["/agents", "Agents"],
["/skills", "Skills"],
["/insights", "Insights"],
Expand Down Expand Up @@ -180,7 +180,7 @@ describe("authed deep links render their own page", () => {

describe("back and forward move through real history", () => {
test("three navigations, then back/back/forward land on the right URL", async () => {
const { container, root } = await mount("/files", {
const { container, root } = await mount("/artifacts", {
kind: "signed-in",
user,
});
Expand All @@ -206,7 +206,7 @@ describe("back and forward move through real history", () => {
window.history.back();
});
await flush();
expect(window.location.pathname).toBe("/files");
expect(window.location.pathname).toBe("/artifacts");

await act(async () => {
window.history.forward();
Expand Down
8 changes: 5 additions & 3 deletions apps/web/src/pages/legacy-settings-redirects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
// `/settings/skills[/:id]`); / moved both back out to their
// own rail destinations, so any deep link into the old Settings home now
// bounces to the new one, preserving a deep-linked id. Library was renamed
// Files at the same time it was moved off `/library` — that old
// prefix bounces to `/files` the same way.
// Files, then Artifacts, moving off `/library` and `/files` in turn — both
// old prefixes bounce to `/artifacts` the same way.

import { useEffect } from "react";

Expand Down Expand Up @@ -74,5 +74,7 @@ export function LegacyLibraryRedirect({
readonly path: string;
readonly navigate: (to: string) => void;
}) {
return <LegacyRedirect path={path} navigate={navigate} oldPrefix="/library" newPrefix="/files" />;
return (
<LegacyRedirect path={path} navigate={navigate} oldPrefix="/library" newPrefix="/artifacts" />
);
}
Loading
Loading