diff --git a/apps/api/src/router.ts b/apps/api/src/router.ts index 30f89a4..97b7f3c 100644 --- a/apps/api/src/router.ts +++ b/apps/api/src/router.ts @@ -116,6 +116,8 @@ import { expandSkillReferencesInPrompt, hasMixedOneShotSchedule, isOneShotRoutineCrons, + isProjectColor, + isProjectIcon, isValidAvatarValue, nextCronDateAcrossStrict, } from "@rakazo/core"; @@ -168,6 +170,7 @@ import { selectSpaceModelPreference, selectSpaceVoicePreference, updateNotificationSettings, + updateSpaceForOwner, wireProjectWorkspace, } from "@rakazo/db"; import { getLogger } from "@rakazo/logging"; @@ -755,6 +758,7 @@ export function createRouter(deps: RouterDeps) { isDefault: false, hasContent: false, canDelete: true, + canEdit: true, workspace, bots: bots.map(spaceBotDto), groups: groups.map(spaceGroupDto), @@ -776,6 +780,27 @@ export function createRouter(deps: RouterDeps) { spaceId: conversation.spaceId, }; }), + update: authed.spaces.update.handler(async ({ context, input }) => { + if (input.icon !== undefined && !isProjectIcon(input.icon)) { + throw new ORPCError("BAD_REQUEST", { message: "Unknown project icon" }); + } + if (input.color !== undefined && !isProjectColor(input.color)) { + throw new ORPCError("BAD_REQUEST", { message: "Unknown project color" }); + } + try { + await updateSpaceForOwner(deps.prisma, { userId: context.actor.userId, ...input }); + } catch (error) { + if (error instanceof SpaceNotFoundError) { + throw new ORPCError("NOT_FOUND", { message: error.message }); + } + if (error instanceof IsolationError) throw new ORPCError("FORBIDDEN"); + if (error instanceof InvalidSpaceNameError) { + throw new ORPCError("BAD_REQUEST", { message: error.message }); + } + throw error; + } + return { ok: true as const }; + }), remove: authed.spaces.remove.handler(async ({ context, input }) => { let claimId: string | null = null; let claimActive = false; @@ -5444,6 +5469,8 @@ async function spaceNavigationDto( space: { select: { name: true, + icon: true, + color: true, isDefault: true, deletingAt: true, workspacePath: true, @@ -5524,6 +5551,8 @@ async function spaceNavigationDto( return { id: membership.spaceId, name: membership.space.name, + icon: membership.space.icon, + color: membership.space.color, isDefault: membership.space.isDefault, hasContent: spacesWithContent.has(membership.spaceId), canDelete: @@ -5532,6 +5561,7 @@ async function spaceNavigationDto( memberships.length > 1 && !spacesWithContent.has(membership.spaceId) && (membership.space.deletingAt === null || membership.space.deletingAt < staleClaimBefore), + canEdit: membership.role === "owner", // A linked folder is on the owner's host; other members see neither its path nor remote. workspace: actor.isDeploymentOwner ? projectWorkspaceDto(membership.space) : null, bots: spaceBots.map(spaceBotDto), diff --git a/apps/mobile/app/index.tsx b/apps/mobile/app/index.tsx index 91c69f1..fcb84a0 100644 --- a/apps/mobile/app/index.tsx +++ b/apps/mobile/app/index.tsx @@ -6,7 +6,7 @@ import { type SpaceBot, type SpaceGroup, } from "@rakazo/contracts"; -import { runChatTitle } from "@rakazo/core"; +import { projectLook, runChatTitle } from "@rakazo/core"; import { botColors } from "@rakazo/ui-tokens"; import { Redirect, useFocusEffect, useRouter } from "expo-router"; import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -599,6 +599,7 @@ export default function Home() { }} style={({ pressed }) => [styles.spaceSelect, pressed && styles.rowPressed]} > + {item.title} {canDeleteInboxSpace(item.space) ? ( @@ -907,6 +908,16 @@ function ConversationRow({ ); } +function ProjectTile({ space }: { space: Pick }) { + const styles = useThemedStyles(createHomeStyles); + const { icon, color } = projectLook(space); + return ( + + + + ); +} + function CircleButton({ children, onPress, @@ -1192,6 +1203,15 @@ function createHomeStyles() { spaceSelect: { flex: 1, minHeight: 44, + flexDirection: "row", + alignItems: "center", + gap: 10, + }, + projectTile: { + width: 28, + height: 28, + borderRadius: 8, + alignItems: "center", justifyContent: "center", }, spaceTitle: { diff --git a/apps/mobile/lib/inbox-spaces.ts b/apps/mobile/lib/inbox-spaces.ts index 3ee0bc3..532c081 100644 --- a/apps/mobile/lib/inbox-spaces.ts +++ b/apps/mobile/lib/inbox-spaces.ts @@ -12,7 +12,7 @@ import { export type InboxSpace = Pick< MobileSpace, - "id" | "name" | "isDefault" | "hasContent" | "canDelete" | "botSections" + "id" | "name" | "isDefault" | "hasContent" | "canDelete" | "botSections" | "icon" | "color" > & { bots: (MobileBot | SpaceBot)[]; groups: (MobileGroup | SpaceGroup)[]; diff --git a/apps/web/e2e/empty-project-state.spec.ts b/apps/web/e2e/empty-project-state.spec.ts index 821a391..3e06de0 100644 --- a/apps/web/e2e/empty-project-state.spec.ts +++ b/apps/web/e2e/empty-project-state.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { completeOnboarding, routePatchRpc, rpc, signup } from "./helpers"; +import { completeOnboarding, projectRow, routePatchRpc, rpc, signup } from "./helpers"; test("empty project space offers Start before the main conversation exists", async ({ page }) => { const stamp = Date.now(); @@ -40,8 +40,7 @@ test("empty project space offers Start before the main conversation exists", asy await expect(mainStart).toBeVisible({ timeout: 30_000 }); await expect(mainStart).toContainText(/Start|Starten/i); + // The sidebar lists the project as one row; opening it starts the main conversation. const projekte = page.locator(".sidebar-projekte"); - const sidebarStart = projekte.getByTestId(`project-start-${side.id}`); - await expect(sidebarStart).toBeVisible({ timeout: 30_000 }); - await expect(sidebarStart).toContainText(/Start|Starten/i); + await expect(projectRow(projekte, "Empty project")).toBeVisible({ timeout: 30_000 }); }); diff --git a/apps/web/e2e/helpers.ts b/apps/web/e2e/helpers.ts index 9913e10..4769201 100644 --- a/apps/web/e2e/helpers.ts +++ b/apps/web/e2e/helpers.ts @@ -12,6 +12,11 @@ export function unterhaltungenOnly(page: Page) { return page.locator(".sidebar-unterhaltungen").locator(":scope > *:not(.sidebar-projekte)"); } +/** A project's row in the sidebar; it opens the project's main conversation. */ +export function projectRow(scope: Page | Locator, name: string) { + return scope.getByTestId(/^project-row-/).filter({ hasText: name }); +} + export function isRealSandboxProvider(provider = process.env.SANDBOX_PROVIDER) { return provider === "e2b" || provider === "daytona" || provider === "box"; } diff --git a/apps/web/e2e/project-coordinator.spec.ts b/apps/web/e2e/project-coordinator.spec.ts new file mode 100644 index 0000000..1a720f8 --- /dev/null +++ b/apps/web/e2e/project-coordinator.spec.ts @@ -0,0 +1,77 @@ +import { expect, test } from "@playwright/test"; +import { + captureScreenshot, + completeOnboarding, + projectRow, + rpc, + signup, + waitForChatComposer, +} from "./helpers"; + +test("a project is one sidebar row with its own look, and its coordinator lists the delegated threads", async ({ + page, +}, testInfo) => { + const stamp = Date.now(); + await signup(page, `project-coordinator-${stamp}@rakazo.test`, "password12", "Coordinator"); + await completeOnboarding(page); + + const projekte = page.locator(".sidebar-projekte"); + await projekte.getByTestId("create-project-trigger").click(); + const dialog = page.getByRole("dialog", { name: "New project" }); + await dialog.getByLabel("Name").fill("Garden"); + await dialog.getByRole("button", { name: "Create project", exact: true }).click(); + await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 }); + await waitForChatComposer(page); + + const row = projectRow(projekte, "Garden"); + await expect(row).toBeVisible({ timeout: 30_000 }); + await expect(row).toHaveAttribute("aria-current", "page"); + await expect(row.locator("[data-project-icon]")).toHaveAttribute("data-project-icon", "folder"); + + // Name, icon and color change from the row's menu. + await row.click({ button: "right" }); + await page.getByRole("menuitem", { name: "Customize" }).click(); + const look = page.getByTestId("project-look-dialog"); + await expect(look).toBeVisible(); + await look.getByLabel("Name").fill("Garten"); + await look.getByRole("radio", { name: "green" }).click(); + await look.getByTestId("project-icon-leaf").click(); + await captureScreenshot(page, testInfo, "project-look"); + await look.getByRole("button", { name: "Save", exact: true }).click(); + await expect(look).toBeHidden(); + + const renamed = projectRow(projekte, "Garten"); + await expect(renamed.locator("[data-project-icon]")).toHaveAttribute("data-project-icon", "leaf"); + await expect(renamed.locator("[data-project-icon]")).toHaveAttribute( + "data-project-color", + "green", + ); + await page.reload({ waitUntil: "domcontentloaded" }); + await expect(renamed.locator("[data-project-icon]")).toHaveAttribute( + "data-project-icon", + "leaf", + { timeout: 30_000 }, + ); + + // The coordinator hands work to a specialist; the finished thread shows in its overview. + await waitForChatComposer(page); + await rpc(page, "bots/create", { + name: "Gardener", + title: "", + description: "", + instructions: "", + notifyOnFinish: true, + }); + const composer = page.getByRole("combobox", { name: "Message" }); + await composer.fill("delegate to the bot named Gardener: plan the spring beds"); + await composer.press("Enter"); + const card = page.getByTestId("transcript").getByTestId("delegated-task-card"); + await expect(card).toHaveAttribute("data-status", "done", { timeout: 60_000 }); + + const overview = page.getByTestId("project-overview"); + const done = overview.getByRole("button", { name: /Done/ }); + await expect(done).toBeVisible({ timeout: 30_000 }); + await done.click(); + await expect(overview.getByText("Gardener").first()).toBeVisible(); + await captureScreenshot(page, testInfo, "project-overview"); +}); diff --git a/apps/web/e2e/project-main-agent.spec.ts b/apps/web/e2e/project-main-agent.spec.ts index 7d0fedd..5d7ca20 100644 --- a/apps/web/e2e/project-main-agent.spec.ts +++ b/apps/web/e2e/project-main-agent.spec.ts @@ -1,9 +1,12 @@ -import { expect, type Locator, test } from "@playwright/test"; -import { captureScreenshot, completeOnboarding, rpc, signup, waitForChatComposer } from "./helpers"; - -function projectSpace(projekte: Locator, name: string) { - return projekte.locator('[data-sidebar-group^="project-space:"]').filter({ hasText: name }); -} +import { expect, test } from "@playwright/test"; +import { + captureScreenshot, + completeOnboarding, + projectRow, + rpc, + signup, + waitForChatComposer, +} from "./helpers"; test("a new project opens straight into the conversation with its own main agent", async ({ page, @@ -26,9 +29,7 @@ test("a new project opens straight into the conversation with its own main agent await waitForChatComposer(page); // The project is one conversation, led by a main agent of its own. - const projectEntry = projectSpace(projekte, "Launch").getByRole("button", { - name: "Chief, main conversation", - }); + const projectEntry = projectRow(projekte, "Launch"); await expect(projectEntry).toBeVisible({ timeout: 30_000 }); await expect(projekte.getByRole("button", { name: "Start", exact: true })).toHaveCount(0); const groupId = new URL(page.url()).pathname.split("/").at(-1)!; diff --git a/apps/web/e2e/spaces.spec.ts b/apps/web/e2e/spaces.spec.ts index e540978..628a1e2 100644 --- a/apps/web/e2e/spaces.spec.ts +++ b/apps/web/e2e/spaces.spec.ts @@ -1,21 +1,15 @@ -import { expect, type Locator, test } from "@playwright/test"; +import { expect, test } from "@playwright/test"; import { captureScreenshot, completeOnboarding, openNewSpace, + projectRow, routePatchRpc, rpc, signup, unterhaltungenOnly, } from "./helpers"; -function projectSpace(projekte: Locator, name: string) { - return projekte.locator('[data-sidebar-group^="project-space:"]').filter({ - hasText: name, - exact: true, - }); -} - test("Projects + opens new project dialog instead of group form", async ({ page }, testInfo) => { const stamp = Date.now(); await signup(page, `proj-create-${stamp}@rakazo.test`, "password12", "Project Create"); @@ -42,11 +36,7 @@ test("Projects + opens new project dialog instead of group form", async ({ page await expect(unterhaltungenOnly(page).getByText("Customer project", { exact: true })).toHaveCount( 0, ); - await expect( - projectSpace(projekte, "Customer project").getByRole("button", { - name: "Chief, main conversation", - }), - ).toBeVisible(); + await expect(projectRow(projekte, "Customer project")).toBeVisible(); await expect(page.getByTestId("project-start-main")).toHaveCount(0); await captureScreenshot(page, testInfo, "new-project-main-conversation"); }); @@ -118,14 +108,14 @@ test("spaces stay invisible by default and chat creation requires approval", asy ); await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1); - const supportSpace = projekte.locator('[data-sidebar-group^="project-space:"]').filter({ - hasText: "Customer support", - }); - const supportSpaceGroup = await supportSpace.getAttribute("data-sidebar-group"); - const supportSpaceId = supportSpaceGroup?.match(/^project-space:([^:]+)/)?.[1]; + const supportSpace = projectRow(projekte, "Customer support"); + const supportSpaceId = (await supportSpace.getAttribute("data-testid"))?.replace( + /^project-row-/, + "", + ); expect(supportSpaceId).toBeTruthy(); - // A project created from chat comes with its own main agent, reached through its conversation. - await supportSpace.getByRole("button", { name: "Chief, main conversation" }).click(); + // A project created from chat comes with its own main agent, reached through its row. + await supportSpace.click(); await page.waitForURL(/\/app\/g\/[^/]+$/); await expect .poll(() => page.evaluate(() => window.localStorage.getItem("rakazo:space-id"))) @@ -134,7 +124,7 @@ test("spaces stay invisible by default and chat creation requires approval", asy // The home main conversation stays the first entry while another space is open. await expect(projekte.getByText("Customer support", { exact: true })).toBeVisible(); await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1); - await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(1); + await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0); await expect(projekte.getByRole("button", { name: "Start", exact: true })).toHaveCount(0); await captureScreenshot(page, testInfo, "spaces-sidebar"); @@ -168,9 +158,7 @@ test("a new space opens its main conversation and can be deleted from its menu", const projekte = page.locator(".sidebar-projekte"); await expect(projekte.getByText("Temporary", { exact: true })).toBeVisible(); await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1); - await expect( - projectSpace(projekte, "Temporary").getByRole("button", { name: "Chief, main conversation" }), - ).toBeVisible(); + await expect(projectRow(projekte, "Temporary")).toBeVisible(); await expect(projekte.getByRole("button", { name: "Start", exact: true })).toHaveCount(0); expect(await rpc>(page, "bots/list", {})).toEqual([ expect.objectContaining({ kind: "main" }), @@ -178,9 +166,8 @@ test("a new space opens its main conversation and can be deleted from its menu", await captureScreenshot(page, testInfo, "new-space-main-conversation"); // Deletion is discoverable through the visible Space actions menu. - await projectSpace(projekte, "Temporary") - .getByRole("button", { name: "Actions for Temporary" }) - .click(); + await projectRow(projekte, "Temporary").hover(); + await projekte.getByRole("button", { name: "Actions for Temporary" }).click(); await captureScreenshot(page, testInfo, "space-actions-menu"); await expect(page.getByRole("menuitem", { name: "Delete space" })).toBeVisible(); await page.getByRole("menuitem", { name: "Delete space" }).click(); @@ -242,11 +229,10 @@ test("deleting the last bot in a space stays in the app after first use", async await expect(page).not.toHaveURL(/\/onboarding/); // The project's main conversation stays, so the project opens there. await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 }); - await expect( - projectSpace(projekte, "Side").getByRole("button", { name: "Chief, main conversation" }), - ).toBeVisible(); + await expect(projectRow(projekte, "Side")).toBeVisible(); - await projectSpace(projekte, "Side").getByRole("button", { name: "Actions for Side" }).click(); + await projectRow(projekte, "Side").hover(); + await projekte.getByRole("button", { name: "Actions for Side" }).click(); await expect(page.getByRole("menuitem", { name: "Delete space" })).toBeVisible(); await page.getByRole("menuitem", { name: "Delete space" }).click(); await page diff --git a/apps/web/src/components/SortableGroup.tsx b/apps/web/src/components/SortableGroup.tsx index 97ece43..2d44424 100644 --- a/apps/web/src/components/SortableGroup.tsx +++ b/apps/web/src/components/SortableGroup.tsx @@ -1,62 +1,18 @@ /** - * Sortable wrapper for a project (space) sidebar group card. - * Drag listeners attach only to the grip handle via `useSortableGroupHandle`. + * Sortable wrapper for a project in the sidebar. The whole row drags: a mouse after a short + * move, a finger after a short hold, so a plain click or scroll still works. */ import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; -import { useLingui } from "@lingui/react/macro"; -import { createContext, memo, useContext, useMemo } from "react"; +import { memo, useMemo } from "react"; interface SortableGroupProps { group: { spaceId: string; key: string; title?: string | null }; children: React.ReactNode; } -type SortableGroupHandle = { - setActivatorNodeRef: (element: HTMLElement | null) => void; - listeners: ReturnType["listeners"]; - attributes: ReturnType["attributes"]; -}; - -const SortableGroupHandleContext = createContext(null); - -export function useSortableGroupHandle(): SortableGroupHandle | null { - return useContext(SortableGroupHandleContext); -} - -export const ProjectDragGrip = memo(function ProjectDragGrip({ - className, - children, -}: { - className?: string; - children: React.ReactNode; -}) { - const { t } = useLingui(); - const handle = useSortableGroupHandle(); - return ( - - ); -}); - export const SortableGroup = memo(function SortableGroup({ group, children }: SortableGroupProps) { - const { - attributes, - listeners, - setNodeRef, - setActivatorNodeRef, - transform, - transition, - isDragging, - } = useSortable({ + const { listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: group.spaceId, }); @@ -70,16 +26,9 @@ export const SortableGroup = memo(function SortableGroup({ group, children }: So [transform, transition, isDragging], ); - const handle = useMemo( - () => ({ setActivatorNodeRef, listeners, attributes }), - [setActivatorNodeRef, listeners, attributes], - ); - return ( - -
- {children} -
-
+
+ {children} +
); }); diff --git a/apps/web/src/components/project-icon.tsx b/apps/web/src/components/project-icon.tsx new file mode 100644 index 0000000..dee2f95 --- /dev/null +++ b/apps/web/src/components/project-icon.tsx @@ -0,0 +1,167 @@ +import type { ProjectIconId } from "@rakazo/core"; +import { projectLook } from "@rakazo/core"; +import type { LucideIcon } from "lucide-react"; +import { + Banknote, + Bike, + BookOpen, + Box, + Briefcase, + BriefcaseMedical, + Calendar, + Camera, + Car, + ChartColumn, + Clock, + Cloud, + Code, + Coffee, + Dumbbell, + FileText, + Film, + Flag, + Flame, + FlaskConical, + Folder, + Gamepad2, + Gift, + Globe, + GraduationCap, + Hammer, + Heart, + House, + Image, + Infinity as InfinityIcon, + KeyRound, + Layers, + Leaf, + Library, + Lightbulb, + Lock, + Mail, + Map as MapIcon, + MapPin, + MessageCircle, + Mic, + Monitor, + Moon, + Music, + Newspaper, + Paintbrush, + PawPrint, + Pencil, + Plane, + Server, + Settings, + Shield, + ShoppingBag, + ShoppingCart, + Smartphone, + Sparkles, + SquareTerminal, + Star, + Sun, + Target, + TrendingUp, + Trophy, + User, + Users, + Utensils, + Wrench, + Zap, +} from "lucide-react"; + +export const PROJECT_ICON_COMPONENTS: Record = { + folder: Folder, + briefcase: Briefcase, + house: House, + heart: Heart, + star: Star, + book: BookOpen, + code: Code, + terminal: SquareTerminal, + globe: Globe, + camera: Camera, + film: Film, + music: Music, + mic: Mic, + image: Image, + paintbrush: Paintbrush, + pencil: Pencil, + cart: ShoppingCart, + bag: ShoppingBag, + money: Banknote, + chart: ChartColumn, + trending: TrendingUp, + calendar: Calendar, + clock: Clock, + mail: Mail, + chat: MessageCircle, + people: Users, + person: User, + school: GraduationCap, + flask: FlaskConical, + bulb: Lightbulb, + bolt: Zap, + flame: Flame, + leaf: Leaf, + paw: PawPrint, + airplane: Plane, + car: Car, + bicycle: Bike, + map: MapIcon, + pin: MapPin, + fitness: Dumbbell, + medical: BriefcaseMedical, + food: Utensils, + coffee: Coffee, + game: Gamepad2, + gift: Gift, + trophy: Trophy, + flag: Flag, + key: KeyRound, + lock: Lock, + shield: Shield, + gear: Settings, + hammer: Hammer, + tools: Wrench, + cloud: Cloud, + sun: Sun, + moon: Moon, + box: Box, + layers: Layers, + server: Server, + phone: Smartphone, + desktop: Monitor, + news: Newspaper, + document: FileText, + library: Library, + sparkles: Sparkles, + target: Target, + infinity: InfinityIcon, +}; + +/** A project's icon on its color, as a rounded tile. */ +export function ProjectIcon({ + icon, + color, + size = 32, +}: { + icon?: string | null; + color?: string | null; + size?: number; +}) { + const look = projectLook({ icon, color }); + const Glyph = PROJECT_ICON_COMPONENTS[look.icon.id]; + return ( + + ); +} diff --git a/apps/web/src/lib/run-activity.ts b/apps/web/src/lib/run-activity.ts new file mode 100644 index 0000000..eeb58cb --- /dev/null +++ b/apps/web/src/lib/run-activity.ts @@ -0,0 +1,59 @@ +import type { RunActivityRow } from "@rakazo/contracts"; +import { useSyncExternalStore } from "react"; +import { rpc } from "./rpc"; + +export type RunActivity = { + active: RunActivityRow[]; + recent: RunActivityRow[]; + loaded: boolean; +}; + +const POLL_MS = 15_000; +let snapshot: RunActivity = { active: [], recent: [], loaded: false }; +const listeners = new Set<() => void>(); +let timer: number | undefined; +let polling = false; + +async function poll() { + try { + const [active, recent] = await Promise.all([ + rpc.runs.list({ filter: "active" }), + rpc.runs.list({ filter: "recent" }), + ]); + snapshot = { active: active.runs, recent: recent.runs, loaded: true }; + } catch { + // Keep the last good snapshot on transient failures. + if (!snapshot.loaded) snapshot = { ...snapshot, loaded: true }; + } + for (const listener of listeners) listener(); + // Schedule the next poll after this one settles, and only while someone is listening. + timer = listeners.size > 0 ? window.setTimeout(() => void poll(), POLL_MS) : undefined; + polling = timer !== undefined; +} + +function subscribe(listener: () => void) { + listeners.add(listener); + if (!polling) { + polling = true; + void poll(); + } + return () => { + listeners.delete(listener); + if (listeners.size === 0 && timer !== undefined) { + window.clearTimeout(timer); + timer = undefined; + polling = false; + } + }; +} + +/** The user's active and recently finished runs in every space, polled once for all readers. */ +export function useRunActivity(): RunActivity { + return useSyncExternalStore(subscribe, () => snapshot); +} + +/** Ask for a fresh snapshot now, e.g. after the user answered a waiting thread. */ +export function refreshRunActivity() { + if (timer !== undefined) window.clearTimeout(timer); + void poll(); +} diff --git a/apps/web/src/locales/de/messages.po b/apps/web/src/locales/de/messages.po index 46985b7..5680578 100644 --- a/apps/web/src/locales/de/messages.po +++ b/apps/web/src/locales/de/messages.po @@ -352,6 +352,10 @@ msgstr "{name} ergänzt: {line}" msgid "{name} stopped" msgstr "{name} wurde gestoppt" +#: src/pages/shell/project-row.tsx +msgid "{name}, {needsYou} need you" +msgstr "{name}, {needsYou} brauchen dich" + #: src/pages/shell/main-conversation-row.tsx msgid "{name}, main conversation" msgstr "{name}, Hauptunterhaltung" @@ -461,6 +465,10 @@ msgstr "Aktionsbestätigungen" msgid "Actions for {0}" msgstr "Aktionen für {0}" +#: src/pages/shell/project-row.tsx +msgid "Actions for {name}" +msgstr "Aktionen für {name}" + #: src/pages/Shell.tsx msgid "Actions for space" msgstr "Aktionen für Space" @@ -1067,6 +1075,7 @@ msgstr "Server nicht erreichbar." #: src/pages/PluginsOverlay.tsx #: src/pages/PrivacySettingsOverlay.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Cancel" msgstr "Abbrechen" @@ -1376,6 +1385,7 @@ msgid "Collapse {0}" msgstr "{0} einklappen" #: src/components/OoAtelier.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Color" msgstr "Farbe" @@ -1829,6 +1839,7 @@ msgstr "Routine konnte nicht ausgeführt werden" #: src/pages/KnowledgeSection.tsx #: src/pages/ModelTierSettings.tsx #: src/pages/shell/bot-panel.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Could not save" msgstr "Speichern fehlgeschlagen" @@ -2056,6 +2067,7 @@ msgid "Customer support" msgstr "Kundensupport" #: src/components/onboarding/OnboardingOoDesigner.tsx +#: src/pages/Shell.tsx msgid "Customize" msgstr "Anpassen" @@ -2280,6 +2292,7 @@ msgstr "Kein Stress — wir sind jetzt da." #: src/components/ThreadCard.tsx #: src/pages/HeuteBrief.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Done" msgstr "Fertig" @@ -2325,10 +2338,6 @@ msgstr "Skill-Entwurf" msgid "Drafting the spec…" msgstr "Entwerfe die Spezifikation…" -#: src/components/SortableGroup.tsx -msgid "Drag to reorder" -msgstr "Ziehen zum Umsortieren" - #: src/components/OoAtelier.tsx msgid "Drag, drop, or paste an image" msgstr "Bild ziehen, ablegen oder einfügen" @@ -2897,6 +2906,10 @@ msgstr "Ich bin fertig" msgid "I'm listening — what's the priority?" msgstr "Ich höre zu — was hat Priorität?" +#: src/pages/shell/project-look-dialog.tsx +msgid "Icon" +msgstr "Icon" + #: src/components/UserProfileSettings.tsx msgid "Identity" msgstr "Identität" @@ -3483,6 +3496,7 @@ msgstr "In Ordner verschieben" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Name" msgstr "Name" @@ -3520,6 +3534,7 @@ msgstr "Braucht dich" #: src/pages/ActivityList.tsx #: src/pages/HeuteBrief.tsx #: src/pages/shell/fullscreen-computer-overlay.tsx +#: src/pages/shell/project-overview.tsx #: src/pages/shell/transcript.tsx msgid "Needs you" msgstr "Braucht dich" @@ -4483,6 +4498,7 @@ msgstr "läuft" #: src/components/ThreadCard.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Running" msgstr "Läuft" @@ -4518,6 +4534,7 @@ msgstr "Läuft, wenn dieser Bot eine verifizierte Nachricht von diesem Anbieter #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Save" msgstr "Speichern" diff --git a/apps/web/src/locales/en/messages.po b/apps/web/src/locales/en/messages.po index 0236ddf..3b434df 100644 --- a/apps/web/src/locales/en/messages.po +++ b/apps/web/src/locales/en/messages.po @@ -352,6 +352,10 @@ msgstr "{name} riffed: {line}" msgid "{name} stopped" msgstr "{name} stopped" +#: src/pages/shell/project-row.tsx +msgid "{name}, {needsYou} need you" +msgstr "{name}, {needsYou} need you" + #: src/pages/shell/main-conversation-row.tsx msgid "{name}, main conversation" msgstr "{name}, main conversation" @@ -461,6 +465,10 @@ msgstr "Action confirmations" msgid "Actions for {0}" msgstr "Actions for {0}" +#: src/pages/shell/project-row.tsx +msgid "Actions for {name}" +msgstr "Actions for {name}" + #: src/pages/Shell.tsx msgid "Actions for space" msgstr "Actions for space" @@ -1067,6 +1075,7 @@ msgstr "Can't reach the server." #: src/pages/PluginsOverlay.tsx #: src/pages/PrivacySettingsOverlay.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Cancel" msgstr "Cancel" @@ -1376,6 +1385,7 @@ msgid "Collapse {0}" msgstr "Collapse {0}" #: src/components/OoAtelier.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Color" msgstr "Color" @@ -1829,6 +1839,7 @@ msgstr "Could not run routine" #: src/pages/KnowledgeSection.tsx #: src/pages/ModelTierSettings.tsx #: src/pages/shell/bot-panel.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Could not save" msgstr "Could not save" @@ -2056,6 +2067,7 @@ msgid "Customer support" msgstr "Customer support" #: src/components/onboarding/OnboardingOoDesigner.tsx +#: src/pages/Shell.tsx msgid "Customize" msgstr "Customize" @@ -2280,6 +2292,7 @@ msgstr "Don't stress — we're here now." #: src/components/ThreadCard.tsx #: src/pages/HeuteBrief.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Done" msgstr "Done" @@ -2325,10 +2338,6 @@ msgstr "Draft skill" msgid "Drafting the spec…" msgstr "Drafting the spec…" -#: src/components/SortableGroup.tsx -msgid "Drag to reorder" -msgstr "Drag to reorder" - #: src/components/OoAtelier.tsx msgid "Drag, drop, or paste an image" msgstr "Drag, drop, or paste an image" @@ -2897,6 +2906,10 @@ msgstr "I’m done" msgid "I'm listening — what's the priority?" msgstr "I'm listening — what's the priority?" +#: src/pages/shell/project-look-dialog.tsx +msgid "Icon" +msgstr "Icon" + #: src/components/UserProfileSettings.tsx msgid "Identity" msgstr "Identity" @@ -3483,6 +3496,7 @@ msgstr "Move to folder" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Name" msgstr "Name" @@ -3520,6 +3534,7 @@ msgstr "Needs you" #: src/pages/ActivityList.tsx #: src/pages/HeuteBrief.tsx #: src/pages/shell/fullscreen-computer-overlay.tsx +#: src/pages/shell/project-overview.tsx #: src/pages/shell/transcript.tsx msgid "Needs you" msgstr "Needs you" @@ -4483,6 +4498,7 @@ msgstr "running" #: src/components/ThreadCard.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Running" msgstr "Running" @@ -4518,6 +4534,7 @@ msgstr "Runs when this bot receives a verified message from this provider." #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Save" msgstr "Save" diff --git a/apps/web/src/locales/es/messages.po b/apps/web/src/locales/es/messages.po index 0288d11..c09f376 100644 --- a/apps/web/src/locales/es/messages.po +++ b/apps/web/src/locales/es/messages.po @@ -352,6 +352,10 @@ msgstr "" msgid "{name} stopped" msgstr "{name} se detuvo" +#: src/pages/shell/project-row.tsx +msgid "{name}, {needsYou} need you" +msgstr "" + #: src/pages/shell/main-conversation-row.tsx msgid "{name}, main conversation" msgstr "" @@ -461,6 +465,10 @@ msgstr "Confirmaciones de acción" msgid "Actions for {0}" msgstr "Acciones para {0}" +#: src/pages/shell/project-row.tsx +msgid "Actions for {name}" +msgstr "" + #: src/pages/Shell.tsx msgid "Actions for space" msgstr "" @@ -1067,6 +1075,7 @@ msgstr "No se puede alcanzar el servidor." #: src/pages/PluginsOverlay.tsx #: src/pages/PrivacySettingsOverlay.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Cancel" msgstr "Cancelar" @@ -1376,6 +1385,7 @@ msgid "Collapse {0}" msgstr "Contraer {0}" #: src/components/OoAtelier.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Color" msgstr "Color" @@ -1829,6 +1839,7 @@ msgstr "No se pudo ejecutar la rutina" #: src/pages/KnowledgeSection.tsx #: src/pages/ModelTierSettings.tsx #: src/pages/shell/bot-panel.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Could not save" msgstr "No se pudo guardar" @@ -2056,6 +2067,7 @@ msgid "Customer support" msgstr "Atención al cliente" #: src/components/onboarding/OnboardingOoDesigner.tsx +#: src/pages/Shell.tsx msgid "Customize" msgstr "" @@ -2280,6 +2292,7 @@ msgstr "" #: src/components/ThreadCard.tsx #: src/pages/HeuteBrief.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Done" msgstr "Listo" @@ -2325,10 +2338,6 @@ msgstr "Habilidad en borrador" msgid "Drafting the spec…" msgstr "" -#: src/components/SortableGroup.tsx -msgid "Drag to reorder" -msgstr "" - #: src/components/OoAtelier.tsx msgid "Drag, drop, or paste an image" msgstr "Arrastra, suelta o pega una imagen" @@ -2897,6 +2906,10 @@ msgstr "Listo" msgid "I'm listening — what's the priority?" msgstr "" +#: src/pages/shell/project-look-dialog.tsx +msgid "Icon" +msgstr "" + #: src/components/UserProfileSettings.tsx msgid "Identity" msgstr "" @@ -3483,6 +3496,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Name" msgstr "Nombre" @@ -3520,6 +3534,7 @@ msgstr "" #: src/pages/ActivityList.tsx #: src/pages/HeuteBrief.tsx #: src/pages/shell/fullscreen-computer-overlay.tsx +#: src/pages/shell/project-overview.tsx #: src/pages/shell/transcript.tsx msgid "Needs you" msgstr "" @@ -4483,6 +4498,7 @@ msgstr "en ejecución" #: src/components/ThreadCard.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Running" msgstr "En ejecución" @@ -4518,6 +4534,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Save" msgstr "Guardar" diff --git a/apps/web/src/locales/hi/messages.po b/apps/web/src/locales/hi/messages.po index 4ab9c48..00ec7b7 100644 --- a/apps/web/src/locales/hi/messages.po +++ b/apps/web/src/locales/hi/messages.po @@ -352,6 +352,10 @@ msgstr "" msgid "{name} stopped" msgstr "{name} रोक दिया गया" +#: src/pages/shell/project-row.tsx +msgid "{name}, {needsYou} need you" +msgstr "" + #: src/pages/shell/main-conversation-row.tsx msgid "{name}, main conversation" msgstr "" @@ -461,6 +465,10 @@ msgstr "कार्रवाई की पुष्टि" msgid "Actions for {0}" msgstr "{0} के लिए कार्रवाइयां" +#: src/pages/shell/project-row.tsx +msgid "Actions for {name}" +msgstr "" + #: src/pages/Shell.tsx msgid "Actions for space" msgstr "" @@ -1067,6 +1075,7 @@ msgstr "सर्वर तक नहीं पहुंचा जा सका #: src/pages/PluginsOverlay.tsx #: src/pages/PrivacySettingsOverlay.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Cancel" msgstr "रद्द करें" @@ -1376,6 +1385,7 @@ msgid "Collapse {0}" msgstr "" #: src/components/OoAtelier.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Color" msgstr "रंग" @@ -1829,6 +1839,7 @@ msgstr "" #: src/pages/KnowledgeSection.tsx #: src/pages/ModelTierSettings.tsx #: src/pages/shell/bot-panel.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Could not save" msgstr "सहेजा नहीं जा सका" @@ -2056,6 +2067,7 @@ msgid "Customer support" msgstr "" #: src/components/onboarding/OnboardingOoDesigner.tsx +#: src/pages/Shell.tsx msgid "Customize" msgstr "" @@ -2280,6 +2292,7 @@ msgstr "" #: src/components/ThreadCard.tsx #: src/pages/HeuteBrief.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Done" msgstr "पूर्ण" @@ -2325,10 +2338,6 @@ msgstr "ड्राफ़्ट स्किल" msgid "Drafting the spec…" msgstr "" -#: src/components/SortableGroup.tsx -msgid "Drag to reorder" -msgstr "" - #: src/components/OoAtelier.tsx msgid "Drag, drop, or paste an image" msgstr "छवि खींचें, छोड़ें या पेस्ट करें" @@ -2897,6 +2906,10 @@ msgstr "मेरा काम हो गया" msgid "I'm listening — what's the priority?" msgstr "" +#: src/pages/shell/project-look-dialog.tsx +msgid "Icon" +msgstr "" + #: src/components/UserProfileSettings.tsx msgid "Identity" msgstr "" @@ -3483,6 +3496,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Name" msgstr "नाम" @@ -3520,6 +3534,7 @@ msgstr "" #: src/pages/ActivityList.tsx #: src/pages/HeuteBrief.tsx #: src/pages/shell/fullscreen-computer-overlay.tsx +#: src/pages/shell/project-overview.tsx #: src/pages/shell/transcript.tsx msgid "Needs you" msgstr "" @@ -4483,6 +4498,7 @@ msgstr "चल रहा है" #: src/components/ThreadCard.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Running" msgstr "चल रहा है" @@ -4518,6 +4534,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Save" msgstr "सहेजें" diff --git a/apps/web/src/locales/ko/messages.po b/apps/web/src/locales/ko/messages.po index ca2b666..7d3d41e 100644 --- a/apps/web/src/locales/ko/messages.po +++ b/apps/web/src/locales/ko/messages.po @@ -352,6 +352,10 @@ msgstr "" msgid "{name} stopped" msgstr "{name} 중지됨" +#: src/pages/shell/project-row.tsx +msgid "{name}, {needsYou} need you" +msgstr "" + #: src/pages/shell/main-conversation-row.tsx msgid "{name}, main conversation" msgstr "" @@ -461,6 +465,10 @@ msgstr "작업 실행 전 확인" msgid "Actions for {0}" msgstr "{0} 작업 메뉴" +#: src/pages/shell/project-row.tsx +msgid "Actions for {name}" +msgstr "" + #: src/pages/Shell.tsx msgid "Actions for space" msgstr "" @@ -1067,6 +1075,7 @@ msgstr "서버에 연결할 수 없습니다." #: src/pages/PluginsOverlay.tsx #: src/pages/PrivacySettingsOverlay.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Cancel" msgstr "취소" @@ -1376,6 +1385,7 @@ msgid "Collapse {0}" msgstr "{0} 접기" #: src/components/OoAtelier.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Color" msgstr "색상" @@ -1829,6 +1839,7 @@ msgstr "루틴을 실행할 수 없습니다" #: src/pages/KnowledgeSection.tsx #: src/pages/ModelTierSettings.tsx #: src/pages/shell/bot-panel.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Could not save" msgstr "저장하지 못했습니다." @@ -2056,6 +2067,7 @@ msgid "Customer support" msgstr "고객 지원" #: src/components/onboarding/OnboardingOoDesigner.tsx +#: src/pages/Shell.tsx msgid "Customize" msgstr "" @@ -2280,6 +2292,7 @@ msgstr "" #: src/components/ThreadCard.tsx #: src/pages/HeuteBrief.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Done" msgstr "완료" @@ -2325,10 +2338,6 @@ msgstr "작업 기술 초안" msgid "Drafting the spec…" msgstr "" -#: src/components/SortableGroup.tsx -msgid "Drag to reorder" -msgstr "" - #: src/components/OoAtelier.tsx msgid "Drag, drop, or paste an image" msgstr "이미지를 끌어다 놓거나 붙여넣으세요" @@ -2897,6 +2906,10 @@ msgstr "조작 끝내기" msgid "I'm listening — what's the priority?" msgstr "" +#: src/pages/shell/project-look-dialog.tsx +msgid "Icon" +msgstr "" + #: src/components/UserProfileSettings.tsx msgid "Identity" msgstr "" @@ -3483,6 +3496,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Name" msgstr "이름" @@ -3520,6 +3534,7 @@ msgstr "" #: src/pages/ActivityList.tsx #: src/pages/HeuteBrief.tsx #: src/pages/shell/fullscreen-computer-overlay.tsx +#: src/pages/shell/project-overview.tsx #: src/pages/shell/transcript.tsx msgid "Needs you" msgstr "" @@ -4483,6 +4498,7 @@ msgstr "실행 중" #: src/components/ThreadCard.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Running" msgstr "실행 중" @@ -4518,6 +4534,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Save" msgstr "저장" diff --git a/apps/web/src/locales/pt-BR/messages.po b/apps/web/src/locales/pt-BR/messages.po index 8350ce9..dcccea2 100644 --- a/apps/web/src/locales/pt-BR/messages.po +++ b/apps/web/src/locales/pt-BR/messages.po @@ -352,6 +352,10 @@ msgstr "" msgid "{name} stopped" msgstr "{name} foi interrompido" +#: src/pages/shell/project-row.tsx +msgid "{name}, {needsYou} need you" +msgstr "" + #: src/pages/shell/main-conversation-row.tsx msgid "{name}, main conversation" msgstr "" @@ -461,6 +465,10 @@ msgstr "Confirmações de ação" msgid "Actions for {0}" msgstr "Ações para {0}" +#: src/pages/shell/project-row.tsx +msgid "Actions for {name}" +msgstr "" + #: src/pages/Shell.tsx msgid "Actions for space" msgstr "" @@ -1067,6 +1075,7 @@ msgstr "Não é possível acessar o servidor." #: src/pages/PluginsOverlay.tsx #: src/pages/PrivacySettingsOverlay.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Cancel" msgstr "Cancelar" @@ -1376,6 +1385,7 @@ msgid "Collapse {0}" msgstr "" #: src/components/OoAtelier.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Color" msgstr "Cor" @@ -1829,6 +1839,7 @@ msgstr "" #: src/pages/KnowledgeSection.tsx #: src/pages/ModelTierSettings.tsx #: src/pages/shell/bot-panel.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Could not save" msgstr "Não foi possível salvar" @@ -2056,6 +2067,7 @@ msgid "Customer support" msgstr "" #: src/components/onboarding/OnboardingOoDesigner.tsx +#: src/pages/Shell.tsx msgid "Customize" msgstr "" @@ -2280,6 +2292,7 @@ msgstr "" #: src/components/ThreadCard.tsx #: src/pages/HeuteBrief.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Done" msgstr "Concluído" @@ -2325,10 +2338,6 @@ msgstr "Habilidade de rascunho" msgid "Drafting the spec…" msgstr "" -#: src/components/SortableGroup.tsx -msgid "Drag to reorder" -msgstr "" - #: src/components/OoAtelier.tsx msgid "Drag, drop, or paste an image" msgstr "Arraste, solte ou cole uma imagem" @@ -2897,6 +2906,10 @@ msgstr "Eu terminei." msgid "I'm listening — what's the priority?" msgstr "" +#: src/pages/shell/project-look-dialog.tsx +msgid "Icon" +msgstr "" + #: src/components/UserProfileSettings.tsx msgid "Identity" msgstr "" @@ -3483,6 +3496,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Name" msgstr "Nome" @@ -3520,6 +3534,7 @@ msgstr "" #: src/pages/ActivityList.tsx #: src/pages/HeuteBrief.tsx #: src/pages/shell/fullscreen-computer-overlay.tsx +#: src/pages/shell/project-overview.tsx #: src/pages/shell/transcript.tsx msgid "Needs you" msgstr "" @@ -4483,6 +4498,7 @@ msgstr "em execução" #: src/components/ThreadCard.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Running" msgstr "Em execução" @@ -4518,6 +4534,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Save" msgstr "Salvar" diff --git a/apps/web/src/locales/ru/messages.po b/apps/web/src/locales/ru/messages.po index 695c2e0..12bfba2 100644 --- a/apps/web/src/locales/ru/messages.po +++ b/apps/web/src/locales/ru/messages.po @@ -352,6 +352,10 @@ msgstr "" msgid "{name} stopped" msgstr "{name}: остановлено" +#: src/pages/shell/project-row.tsx +msgid "{name}, {needsYou} need you" +msgstr "" + #: src/pages/shell/main-conversation-row.tsx msgid "{name}, main conversation" msgstr "" @@ -461,6 +465,10 @@ msgstr "Подтверждения действий" msgid "Actions for {0}" msgstr "Действия для {0}" +#: src/pages/shell/project-row.tsx +msgid "Actions for {name}" +msgstr "" + #: src/pages/Shell.tsx msgid "Actions for space" msgstr "Действия для пространства" @@ -1067,6 +1075,7 @@ msgstr "Не удаётся связаться с сервером." #: src/pages/PluginsOverlay.tsx #: src/pages/PrivacySettingsOverlay.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Cancel" msgstr "Отмена" @@ -1376,6 +1385,7 @@ msgid "Collapse {0}" msgstr "Свернуть {0}" #: src/components/OoAtelier.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Color" msgstr "Цвет" @@ -1829,6 +1839,7 @@ msgstr "Не удалось запустить задачу" #: src/pages/KnowledgeSection.tsx #: src/pages/ModelTierSettings.tsx #: src/pages/shell/bot-panel.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Could not save" msgstr "Не удалось сохранить" @@ -2056,6 +2067,7 @@ msgid "Customer support" msgstr "Поддержка клиентов" #: src/components/onboarding/OnboardingOoDesigner.tsx +#: src/pages/Shell.tsx msgid "Customize" msgstr "" @@ -2280,6 +2292,7 @@ msgstr "" #: src/components/ThreadCard.tsx #: src/pages/HeuteBrief.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Done" msgstr "Готово" @@ -2325,10 +2338,6 @@ msgstr "Черновик навыка" msgid "Drafting the spec…" msgstr "" -#: src/components/SortableGroup.tsx -msgid "Drag to reorder" -msgstr "" - #: src/components/OoAtelier.tsx msgid "Drag, drop, or paste an image" msgstr "Перетащите или вставьте изображение" @@ -2897,6 +2906,10 @@ msgstr "Готово" msgid "I'm listening — what's the priority?" msgstr "" +#: src/pages/shell/project-look-dialog.tsx +msgid "Icon" +msgstr "" + #: src/components/UserProfileSettings.tsx msgid "Identity" msgstr "" @@ -3483,6 +3496,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Name" msgstr "Имя" @@ -3520,6 +3534,7 @@ msgstr "Требуется ваше участие" #: src/pages/ActivityList.tsx #: src/pages/HeuteBrief.tsx #: src/pages/shell/fullscreen-computer-overlay.tsx +#: src/pages/shell/project-overview.tsx #: src/pages/shell/transcript.tsx msgid "Needs you" msgstr "Требуется ваше участие" @@ -4483,6 +4498,7 @@ msgstr "выполняется" #: src/components/ThreadCard.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Running" msgstr "Выполняется" @@ -4518,6 +4534,7 @@ msgstr "Запускается, когда этот бот получает пр #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Save" msgstr "Сохранить" diff --git a/apps/web/src/locales/tr/messages.po b/apps/web/src/locales/tr/messages.po index efa9ae6..8cd0d65 100644 --- a/apps/web/src/locales/tr/messages.po +++ b/apps/web/src/locales/tr/messages.po @@ -352,6 +352,10 @@ msgstr "" msgid "{name} stopped" msgstr "{name} durduruldu" +#: src/pages/shell/project-row.tsx +msgid "{name}, {needsYou} need you" +msgstr "" + #: src/pages/shell/main-conversation-row.tsx msgid "{name}, main conversation" msgstr "" @@ -461,6 +465,10 @@ msgstr "İşlem onayları" msgid "Actions for {0}" msgstr "{0} için işlemler" +#: src/pages/shell/project-row.tsx +msgid "Actions for {name}" +msgstr "" + #: src/pages/Shell.tsx msgid "Actions for space" msgstr "" @@ -1067,6 +1075,7 @@ msgstr "Sunucuya ulaşılamıyor." #: src/pages/PluginsOverlay.tsx #: src/pages/PrivacySettingsOverlay.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Cancel" msgstr "İptal" @@ -1376,6 +1385,7 @@ msgid "Collapse {0}" msgstr "" #: src/components/OoAtelier.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Color" msgstr "Renk" @@ -1829,6 +1839,7 @@ msgstr "" #: src/pages/KnowledgeSection.tsx #: src/pages/ModelTierSettings.tsx #: src/pages/shell/bot-panel.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Could not save" msgstr "Kaydedilemedi" @@ -2056,6 +2067,7 @@ msgid "Customer support" msgstr "" #: src/components/onboarding/OnboardingOoDesigner.tsx +#: src/pages/Shell.tsx msgid "Customize" msgstr "" @@ -2280,6 +2292,7 @@ msgstr "" #: src/components/ThreadCard.tsx #: src/pages/HeuteBrief.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Done" msgstr "Bitti" @@ -2325,10 +2338,6 @@ msgstr "Taslak beceri" msgid "Drafting the spec…" msgstr "" -#: src/components/SortableGroup.tsx -msgid "Drag to reorder" -msgstr "" - #: src/components/OoAtelier.tsx msgid "Drag, drop, or paste an image" msgstr "Görüntüyü sürükleyip bırakın veya yapıştırın" @@ -2897,6 +2906,10 @@ msgstr "Bitirdim" msgid "I'm listening — what's the priority?" msgstr "" +#: src/pages/shell/project-look-dialog.tsx +msgid "Icon" +msgstr "" + #: src/components/UserProfileSettings.tsx msgid "Identity" msgstr "" @@ -3483,6 +3496,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Name" msgstr "Ad" @@ -3520,6 +3534,7 @@ msgstr "" #: src/pages/ActivityList.tsx #: src/pages/HeuteBrief.tsx #: src/pages/shell/fullscreen-computer-overlay.tsx +#: src/pages/shell/project-overview.tsx #: src/pages/shell/transcript.tsx msgid "Needs you" msgstr "" @@ -4483,6 +4498,7 @@ msgstr "çalışıyor" #: src/components/ThreadCard.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Running" msgstr "Çalışıyor" @@ -4518,6 +4534,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Save" msgstr "Kaydet" diff --git a/apps/web/src/locales/zh-CN/messages.po b/apps/web/src/locales/zh-CN/messages.po index c47f86f..7d442f0 100644 --- a/apps/web/src/locales/zh-CN/messages.po +++ b/apps/web/src/locales/zh-CN/messages.po @@ -352,6 +352,10 @@ msgstr "" msgid "{name} stopped" msgstr "{name} 已停止" +#: src/pages/shell/project-row.tsx +msgid "{name}, {needsYou} need you" +msgstr "" + #: src/pages/shell/main-conversation-row.tsx msgid "{name}, main conversation" msgstr "" @@ -461,6 +465,10 @@ msgstr "操作确认" msgid "Actions for {0}" msgstr "{0} 的操作" +#: src/pages/shell/project-row.tsx +msgid "Actions for {name}" +msgstr "" + #: src/pages/Shell.tsx msgid "Actions for space" msgstr "" @@ -1067,6 +1075,7 @@ msgstr "无法连接服务器。" #: src/pages/PluginsOverlay.tsx #: src/pages/PrivacySettingsOverlay.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Cancel" msgstr "取消" @@ -1376,6 +1385,7 @@ msgid "Collapse {0}" msgstr "折叠 {0}" #: src/components/OoAtelier.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Color" msgstr "颜色" @@ -1829,6 +1839,7 @@ msgstr "无法运行例行任务" #: src/pages/KnowledgeSection.tsx #: src/pages/ModelTierSettings.tsx #: src/pages/shell/bot-panel.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Could not save" msgstr "无法保存" @@ -2056,6 +2067,7 @@ msgid "Customer support" msgstr "客户支持" #: src/components/onboarding/OnboardingOoDesigner.tsx +#: src/pages/Shell.tsx msgid "Customize" msgstr "" @@ -2280,6 +2292,7 @@ msgstr "" #: src/components/ThreadCard.tsx #: src/pages/HeuteBrief.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Done" msgstr "完成" @@ -2325,10 +2338,6 @@ msgstr "技能草稿" msgid "Drafting the spec…" msgstr "" -#: src/components/SortableGroup.tsx -msgid "Drag to reorder" -msgstr "" - #: src/components/OoAtelier.tsx msgid "Drag, drop, or paste an image" msgstr "拖放或粘贴图片" @@ -2897,6 +2906,10 @@ msgstr "我做完了" msgid "I'm listening — what's the priority?" msgstr "" +#: src/pages/shell/project-look-dialog.tsx +msgid "Icon" +msgstr "" + #: src/components/UserProfileSettings.tsx msgid "Identity" msgstr "" @@ -3483,6 +3496,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Name" msgstr "名称" @@ -3520,6 +3534,7 @@ msgstr "" #: src/pages/ActivityList.tsx #: src/pages/HeuteBrief.tsx #: src/pages/shell/fullscreen-computer-overlay.tsx +#: src/pages/shell/project-overview.tsx #: src/pages/shell/transcript.tsx msgid "Needs you" msgstr "" @@ -4483,6 +4498,7 @@ msgstr "运行中" #: src/components/ThreadCard.tsx #: src/pages/ProjectSessionTimeline.tsx +#: src/pages/shell/project-overview.tsx msgid "Running" msgstr "运行中" @@ -4518,6 +4534,7 @@ msgstr "" #: src/pages/RoutineEditor.tsx #: src/pages/shell/bot-panel.tsx #: src/pages/shell/dialogs.tsx +#: src/pages/shell/project-look-dialog.tsx msgid "Save" msgstr "保存" diff --git a/apps/web/src/pages/ActivityList.tsx b/apps/web/src/pages/ActivityList.tsx index 0cb4e5b..e4b0b49 100644 --- a/apps/web/src/pages/ActivityList.tsx +++ b/apps/web/src/pages/ActivityList.tsx @@ -3,9 +3,8 @@ import { t } from "@lingui/core/macro"; import { Trans, useLingui } from "@lingui/react/macro"; import type { RunActivityRow } from "@rakazo/contracts"; import { runChatTitle } from "@rakazo/core"; -import { useEffect, useState } from "react"; import { isNeedsYouStatus, runActivityLabel } from "../lib/agent-activity"; -import { rpc } from "../lib/rpc"; +import { useRunActivity } from "../lib/run-activity"; function statusTone(status: RunActivityRow["status"]): string { if (status === "failed") return "text-destructive"; @@ -20,41 +19,8 @@ type ActivityListProps = { }; export function ActivityList({ onOpenRun }: ActivityListProps) { - const [activeRuns, setActiveRuns] = useState([]); - const [recentRuns, setRecentRuns] = useState([]); - const [loading, setLoading] = useState(true); - - useEffect(() => { - let cancelled = false; - let timer: number | undefined; - - const tick = async () => { - try { - const [active, recent] = await Promise.all([ - rpc.runs.list({ filter: "active" }), - rpc.runs.list({ filter: "recent" }), - ]); - if (cancelled) return; - setActiveRuns(active.runs); - setRecentRuns(recent.runs); - } catch { - // Keep the last good snapshot on transient RPC failures. - if (cancelled) return; - } finally { - if (!cancelled) { - setLoading(false); - // Schedule the next poll after the previous settles — no overlap. - timer = window.setTimeout(() => void tick(), 15_000); - } - } - }; - - void tick(); - return () => { - cancelled = true; - if (timer !== undefined) window.clearTimeout(timer); - }; - }, []); + const { active: activeRuns, recent: recentRuns, loaded } = useRunActivity(); + const loading = !loaded; if (loading) { return ( @@ -92,7 +58,7 @@ export function ActivityList({ onOpenRun }: ActivityListProps) { ); } -function ActivityRow({ run, onOpen }: { run: RunActivityRow; onOpen: () => void }) { +export function ActivityRow({ run, onOpen }: { run: RunActivityRow; onOpen: () => void }) { const { t } = useLingui(); const title = runChatTitle(run); const label = statusLabel(run.status); diff --git a/apps/web/src/pages/Shell.tsx b/apps/web/src/pages/Shell.tsx index 38c3bd6..a4e36fe 100644 --- a/apps/web/src/pages/Shell.tsx +++ b/apps/web/src/pages/Shell.tsx @@ -40,6 +40,7 @@ import { matchVoiceByName, type PaneState, parseVoiceChatCommand, + projectThreads, recordScreenPaneChoice, reorderBotTo, resolveComposerSendPlan, @@ -266,6 +267,10 @@ function voiceStatusRefreshTimeout(): Promise { export type NavPillar = "heute" | "team" | "projekte"; import { SortableGroup } from "../components/SortableGroup"; +import { useRunActivity } from "../lib/run-activity"; +import { ProjectLookDialog } from "./shell/project-look-dialog"; +import { ProjectOverview } from "./shell/project-overview"; +import { ProjectRow } from "./shell/project-row"; export function ShellPage() { const { t } = useLingui(); @@ -512,6 +517,7 @@ export function ShellPage() { const [deleteTarget, setDeleteTarget] = useState(null); const [deleteGroupTarget, setDeleteGroupTarget] = useState(null); const [deleteSpaceTarget, setDeleteSpaceTarget] = useState(null); + const [lookTarget, setLookTarget] = useState(null); const [spaceMenu, setSpaceMenu] = useState<{ id: string; position: ContextMenuPosition; @@ -1824,6 +1830,7 @@ export function ShellPage() { }, [voiceEntry, showProjectTimeline, callOpen, voiceCallBot, voiceStatus?.ready]); const heuteActionCount = useHeuteActionCount(groups, botsRef); + const runActivity = useRunActivity(); useShellSounds({ bots, transcriptRunning, @@ -3069,6 +3076,64 @@ export function ShellPage() { strategy={verticalListSortingStrategy} > {projectSidebarGroups.map((group) => { + const projectSpace = group.key.startsWith("project-space:") + ? spaces.find((space) => space.id === group.spaceId) + : undefined; + if (projectSpace) { + const threads = projectThreads(runActivity, { + spaceId: projectSpace.id, + }); + const projectSelected = + activeNavPillar === "projekte" && + bootstrapMe?.spaceId === projectSpace.id; + // Other chats in the project show only while it is open. + const projectChats = projectSelected + ? group.bots.filter((item) => item.chat.kind !== "main") + : []; + return ( + + item.chat.unread)} + needsYou={threads.needsYou.length} + running={threads.running.length > 0} + onOpen={() => { + setMobileSidebarOpen(false); + void startProject(projectSpace.id); + }} + onMenu={(menu, anchor) => { + spaceMenuAnchor.current = anchor; + setSpaceMenu(menu); + }} + /> + {projectChats.length > 0 ? ( +
+ {projectChats.map((item) => ( + { + setActiveNavPillar("projekte"); + openSpaceChat(item.chat.spaceId, `/app/g/${item.chat.id}`); + }} + activeSpaceId={bootstrapMe?.spaceId} + menuAnchor={botMenuAnchor} + onMenu={setBotMenu} + /> + ))} +
+ ) : null} +
+ ); + } const collapsed = Boolean(group.title) && collapsedSidebarSections.has(group.key); return ( @@ -3414,6 +3479,25 @@ export function ShellPage() { enrichMember={enrichStageMember} /> ) : null} + {!callOpen && activeGroup?.kind === "main" ? ( + + openSpaceChat( + run.spaceId, + run.groupId ? `/app/g/${run.groupId}` : `/app/${run.botId}`, + ) + } + /> + ) : null} - { - const target = spaces.find((space) => space.id === spaceMenu.id); - if (target) setDeleteSpaceTarget(target); - setSpaceMenu(null); - }} - > - - {t`Delete space`} - + {spaces.some( + (space) => space.id === spaceMenu.id && space.canEdit && !space.isDefault, + ) ? ( + { + const target = spaces.find((space) => space.id === spaceMenu.id); + if (target) setLookTarget(target); + setSpaceMenu(null); + }} + > + + {t`Customize`} + + ) : null} + {spaces.find((space) => space.id === spaceMenu.id)?.canDelete ? ( + { + const target = spaces.find((space) => space.id === spaceMenu.id); + if (target) setDeleteSpaceTarget(target); + setSpaceMenu(null); + }} + > + + {t`Delete space`} + + ) : null} ) : null} + {lookTarget ? ( + setLookTarget(null)} + onSave={async (look) => { + await rpc.spaces.update({ spaceId: lookTarget.id, ...look }); + setLookTarget(null); + await refreshBots(); + }} + /> + ) : null} + {deleteTarget ? ( void; + onSave: (look: ProjectLook) => Promise; +}) { + const { t } = useLingui(); + const nameId = useId(); + const start = projectLook(initial); + const [name, setName] = useState(initial.name); + const [icon, setIcon] = useState(start.icon.id); + const [color, setColor] = useState(start.color.id); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + + const save = () => { + const trimmed = name.trim(); + if (!trimmed || saving) return; + setSaving(true); + setError(null); + void onSave({ name: trimmed, icon, color }).catch((reason: unknown) => { + setError(reason instanceof Error ? reason.message : t`Could not save`); + setSaving(false); + }); + }; + + return ( + { + if (!open && !saving) onCancel(); + }} + > + + + + + {name.trim() || initial.name} + + + +
+ {PROJECT_COLORS.map((option) => ( +
+
+ {PROJECT_ICONS.map((option) => { + const Glyph = PROJECT_ICON_COMPONENTS[option.id]; + return ( + + ); + })} +
+ {error ?

{error}

: null} + + + + +
+
+ ); +} diff --git a/apps/web/src/pages/shell/project-overview.tsx b/apps/web/src/pages/shell/project-overview.tsx new file mode 100644 index 0000000..356cd4f --- /dev/null +++ b/apps/web/src/pages/shell/project-overview.tsx @@ -0,0 +1,75 @@ +import { Trans } from "@lingui/react/macro"; +import type { RunActivityRow } from "@rakazo/contracts"; +import { projectThreads } from "@rakazo/core"; +import type { ReactNode } from "react"; +import { useState } from "react"; +import { useRunActivity } from "../../lib/run-activity"; +import { ActivityRow } from "../ActivityList"; + +type Section = "needsYou" | "running" | "done"; + +/** + * The coordinator's view of its project: threads that wait on the user, threads its agents are + * working on, and recently finished ones. Empty groups stay hidden. + */ +export function ProjectOverview({ + spaceId, + coordinator, + onOpenRun, +}: { + spaceId: string; + coordinator?: { threadId: string; botId?: string }; + onOpenRun: (run: RunActivityRow) => void; +}) { + const activity = useRunActivity(); + const threads = projectThreads(activity, { spaceId, coordinator }); + // null until the user picks; "none" once they closed every group. + const [open, setOpen] = useState
(null); + const sections: { id: Section; runs: RunActivityRow[]; label: ReactNode; tone: string }[] = [ + { + id: "needsYou", + runs: threads.needsYou, + label: Needs you, + tone: "text-warning", + }, + { + id: "running", + runs: threads.running, + label: Running, + tone: "text-foreground", + }, + { id: "done", runs: threads.done, label: Done, tone: "text-muted-foreground" }, + ]; + const visible = sections.filter((section) => section.runs.length > 0); + if (visible.length === 0) return null; + // Waiting threads are what the user came for, so they start open. + const shown = open ?? (threads.needsYou.length > 0 ? "needsYou" : null); + const current = visible.find((section) => section.id === shown); + return ( +
+
+ {visible.map((section) => ( + + ))} +
+ {current ? ( +
+ {current.runs.map((run) => ( + onOpenRun(run)} /> + ))} +
+ ) : null} +
+ ); +} diff --git a/apps/web/src/pages/shell/project-row.tsx b/apps/web/src/pages/shell/project-row.tsx new file mode 100644 index 0000000..cec0c14 --- /dev/null +++ b/apps/web/src/pages/shell/project-row.tsx @@ -0,0 +1,95 @@ +import { useLingui } from "@lingui/react/macro"; +import { Button } from "@rakazo/ui-web"; +import { MoreHorizontal } from "lucide-react"; +import { ProjectIcon } from "../../components/project-icon"; +import { rosterNameClass, rosterRowSurfaceClass } from "./roster-row-style"; +import type { SpaceMenuRequest } from "./sidebar-group-header"; + +/** One project in the sidebar: its icon and name; it opens the project's coordinator. */ +export function ProjectRow({ + spaceId, + name, + icon, + color, + hasMenu, + selected, + unread, + needsYou, + running, + onOpen, + onMenu, +}: { + spaceId: string; + name: string; + icon?: string | null; + color?: string | null; + hasMenu: boolean; + selected: boolean; + unread: boolean; + needsYou: number; + running: boolean; + onOpen: () => void; + onMenu: (menu: SpaceMenuRequest, anchor: HTMLElement) => void; +}) { + const { t } = useLingui(); + return ( +
+ + {hasMenu ? ( + // Pointer devices reveal the menu on hover; touch opens it with a long press. + + ) : null} +
+ ); +} diff --git a/apps/web/src/pages/shell/roster-row-style.ts b/apps/web/src/pages/shell/roster-row-style.ts index 5e72d8d..6431647 100644 --- a/apps/web/src/pages/shell/roster-row-style.ts +++ b/apps/web/src/pages/shell/roster-row-style.ts @@ -1,6 +1,11 @@ /** One look for sidebar conversation rows: the main conversation entry and the bot roster. */ export function rosterRowClass(selected: boolean): string { - return `flex w-full items-center gap-3 rounded-2xl border px-3 py-3 mb-[2px] text-start transition-all duration-150 cursor-pointer min-h-[52px] ${ + return `flex w-full items-center gap-3 px-3 py-3 text-start cursor-pointer min-h-[52px] ${rosterRowSurfaceClass(selected)}`; +} + +/** The row's card: border, fill and shadow, for rows that lay out their own content. */ +export function rosterRowSurfaceClass(selected: boolean): string { + return `rounded-2xl border mb-[2px] transition-all duration-150 ${ selected ? "border-sidebar-border bg-sidebar-accent shadow-xs dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.14),0_4px_16px_rgba(0,0,0,0.4)]" : "border-transparent hover:bg-sidebar-accent/50" diff --git a/apps/web/src/pages/shell/sidebar-group-header.tsx b/apps/web/src/pages/shell/sidebar-group-header.tsx index 1ba7b55..4d6d42f 100644 --- a/apps/web/src/pages/shell/sidebar-group-header.tsx +++ b/apps/web/src/pages/shell/sidebar-group-header.tsx @@ -1,9 +1,8 @@ import { useLingui } from "@lingui/react/macro"; import type { BotSection, Space } from "@rakazo/contracts"; import { Button } from "@rakazo/ui-web"; -import { ChevronDown, Folder, GripVertical, Lock, MoreHorizontal } from "lucide-react"; +import { ChevronDown, Folder, Lock, MoreHorizontal } from "lucide-react"; import type { RefObject } from "react"; -import { ProjectDragGrip } from "../../components/SortableGroup"; import type { ContextMenuPosition } from "../BotContextMenu"; export type SidebarGroupHeaderGroup = { @@ -26,7 +25,7 @@ export type SectionMenuRequest = { }; /** - * Collapsible title of a sidebar group. Project groups add a drag grip, show a lock for other + * Collapsible title of a sidebar group. Project groups show a lock for other * spaces and select an empty space instead of opening it. */ export function SidebarGroupHeader({ @@ -59,20 +58,10 @@ export function SidebarGroupHeader({ const { t } = useLingui(); const project = variant === "project"; return ( -
- {project ? ( - // Drag grip — positioned to the left of the title text - - - - ) : null} +