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
4 changes: 4 additions & 0 deletions electron/host-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,10 @@ async function invokeLocalMcpAction(action: HostLocalMcpAction, args: unknown) {
return localMcpRuntime.addWorkspaceSlackThread(
args as Parameters<typeof localMcpRuntime.addWorkspaceSlackThread>[0],
);
case "add-workspace-amplify-link":
return localMcpRuntime.addWorkspaceAmplifyLink(
args as Parameters<typeof localMcpRuntime.addWorkspaceAmplifyLink>[0],
);
default:
action satisfies never;
throw new Error(`Unsupported local MCP action: ${action}`);
Expand Down
53 changes: 52 additions & 1 deletion electron/host-service/local-mcp-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { buildCurrentTaskAwarenessRetrievedContext } from "../../src/lib/task-co
import type { AppNotificationCreateInput } from "../../src/lib/notifications/notification.types";
import { workspaceHasActiveTurns } from "../../src/lib/notifications/notification.types";
import {
createWorkspaceAmplifyLink,
createWorkspaceConfluencePage,
createWorkspaceFigmaResource,
createWorkspaceInfoCustomField,
Expand All @@ -22,6 +23,7 @@ import {
applyWorkspaceTodoStatus,
createWorkspaceTodoItem,
type WorkspaceTodoStatus,
extractAmplifyLinkReference,
extractConfluencePageReference,
extractFigmaResourceReference,
extractJiraIssueReference,
Expand Down Expand Up @@ -169,7 +171,8 @@ type WorkspaceInformationResourceKind =
| "confluence"
| "figma"
| "storybook"
| "slack";
| "slack"
| "amplify";

type WorkspaceCustomFieldValueInput = string | number | boolean | null;

Expand Down Expand Up @@ -512,6 +515,7 @@ function normalizeWorkspaceResourceKind(
case "figma":
case "storybook":
case "slack":
case "amplify":
return value.trim();
default:
throw new Error(`Unsupported workspace resource kind: ${value}`);
Expand Down Expand Up @@ -895,6 +899,17 @@ export async function addWorkspaceResource(args: {
slackThreads: [...current.slackThreads, nextLink],
};
}
case "amplify": {
const nextLink = createWorkspaceAmplifyLink();
nextLink.url = url;
nextLink.label =
title || extractAmplifyLinkReference(url)?.branch || "";
nextLink.note = note;
return {
...current,
amplifyLinks: [...(current.amplifyLinks ?? []), nextLink],
};
}
}
},
});
Expand Down Expand Up @@ -985,6 +1000,18 @@ export async function removeWorkspaceResource(args: {
slackThreads,
};
}
case "amplify": {
const amplifyLinks = (current.amplifyLinks ?? []).filter(
(item) => item.id !== args.itemId,
);
if (amplifyLinks.length === (current.amplifyLinks ?? []).length) {
throw new Error(`Workspace resource not found: ${args.itemId}`);
}
return {
...current,
amplifyLinks,
};
}
}
},
});
Expand Down Expand Up @@ -1308,6 +1335,30 @@ export async function addWorkspaceSlackThread(args: {
workspaceInformation: workspaceInformation.workspaceInformation,
};
}

export async function addWorkspaceAmplifyLink(args: {
workspaceId: string;
url: string;
label?: string;
note?: string;
}) {
const parsed = extractAmplifyLinkReference(args.url);
const workspaceInformation = await addWorkspaceResource({
workspaceId: args.workspaceId,
kind: "amplify",
url: normalizeWorkspaceInfoString(args.url),
title: normalizeWorkspaceInfoString(args.label) || parsed?.branch || "",
note: normalizeWorkspaceInfoString(args.note),
});
return {
workspaceId: workspaceInformation.workspaceId,
added:
(workspaceInformation.workspaceInformation.amplifyLinks ?? []).at(-1) ??
null,
workspaceInformation: workspaceInformation.workspaceInformation,
};
}

function buildTaskTitleFromPrompt(prompt: string) {
return (
prompt
Expand Down
3 changes: 2 additions & 1 deletion electron/host-service/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,8 @@ export type HostLocalMcpAction =
| "add-workspace-figma-resource"
| "add-workspace-storybook-resource"
| "update-workspace-storybook-resource-access"
| "add-workspace-slack-thread";
| "add-workspace-slack-thread"
| "add-workspace-amplify-link";

export interface HostServiceRequestMap {
"service.shutdown": undefined;
Expand Down
37 changes: 37 additions & 0 deletions electron/main/stave-mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from "./stave-mcp-config";
import { ensurePersistenceReady } from "./state";
import {
addWorkspaceAmplifyLink,
addWorkspaceCustomField,
addWorkspaceConfluencePage,
addWorkspaceFigmaResource,
Expand Down Expand Up @@ -598,6 +599,7 @@ function createToolServer() {
"storybook",
"slack",
"figma",
"amplify",
])
.describe("Resource kind."),
url: z.string().url().describe("Resource URL."),
Expand Down Expand Up @@ -659,6 +661,7 @@ function createToolServer() {
"storybook",
"slack",
"figma",
"amplify",
])
.describe("Resource kind."),
itemId: z.string().min(1).describe("Stored resource id."),
Expand Down Expand Up @@ -995,6 +998,40 @@ function createToolServer() {
),
);

server.registerTool(
"stave_add_workspace_amplify_link",
{
description:
"Register an AWS Amplify deploy URL in the Stave Workspace Information panel.",
inputSchema: {
workspaceId: z.string().min(1).describe("Workspace id."),
url: z
.string()
.min(1)
.describe(
"Amplify deploy URL, e.g. https://<branch>.<appid>.amplifyapp.com.",
),
label: z
.string()
.optional()
.describe("Optional branch/environment label."),
note: z
.string()
.optional()
.describe("Optional note stored with the link."),
},
},
async ({ workspaceId, url, label, note }) =>
toStructuredResult(
await addWorkspaceAmplifyLink({
workspaceId,
url,
label,
note,
}),
),
);

server.registerTool(
"stave_respond_approval",
{
Expand Down
12 changes: 12 additions & 0 deletions electron/main/stave-mcp-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,18 @@ export async function addWorkspaceSlackThread(args: {
}>("add-workspace-slack-thread", args);
}

export async function addWorkspaceAmplifyLink(args: {
workspaceId: string;
url: string;
label?: string;
note?: string;
}) {
return invokeLocalMcp<{
workspaceId: string;
workspaceInformation: import("../../src/lib/workspace-information").WorkspaceInformationState;
}>("add-workspace-amplify-link", args);
}

export async function registerProject(args: {
projectPath: string;
projectName?: string;
Expand Down
1 change: 1 addition & 0 deletions electron/providers/claude-sdk-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ const CLAUDE_AUTO_ALLOWED_MCP_TOOL_NAMES = new Set([
"stave_update_workspace_storybook_resource_access",
"stave_add_workspace_figma_resource",
"stave_add_workspace_slack_thread",
"stave_add_workspace_amplify_link",
"stave_add_workspace_custom_field",
"stave_set_workspace_custom_field",
"stave_remove_workspace_custom_field",
Expand Down
8 changes: 4 additions & 4 deletions src/components/ai-elements/prompt-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1902,7 +1902,7 @@ export function PromptInput(args: PromptInputProps) {
<div
className={cn(
minimal
? "rounded-md border border-border/60 bg-background/55 px-3 py-2.5 supports-backdrop-filter:backdrop-blur-md"
? "rounded-md border border-border/60 bg-background px-3 py-2.5"
: undefined,
)}
>
Expand Down Expand Up @@ -1937,7 +1937,7 @@ export function PromptInput(args: PromptInputProps) {
className: cn(
PROMPT_TOOLBAR_BUTTON,
PROMPT_FLOATING_SURFACE,
"h-7 gap-1.5 px-2.5 text-xs shadow-sm supports-backdrop-filter:backdrop-blur-md",
"h-7 gap-1.5 px-2.5 text-xs shadow-sm",
steerQueueSecondaryAction === "steer" &&
"text-primary hover:text-primary",
primaryActionDisabled &&
Expand Down Expand Up @@ -1980,7 +1980,7 @@ export function PromptInput(args: PromptInputProps) {
className={cn(
PROMPT_TOOLBAR_BUTTON,
PROMPT_FLOATING_SURFACE,
"pointer-events-auto h-8 gap-2 shadow-sm supports-backdrop-filter:backdrop-blur-md",
"pointer-events-auto h-8 gap-2 shadow-sm",
)}
>
<span>Focus</span>
Expand Down Expand Up @@ -3235,7 +3235,7 @@ export function PromptInput(args: PromptInputProps) {
<SlidersHorizontal className="size-3.5" />
</Button>
</DrawerTrigger>
<DrawerContent className="border-border/80 bg-background/95 shadow-2xl supports-backdrop-filter:backdrop-blur-xl data-[vaul-drawer-direction=bottom]:max-h-[78vh]">
<DrawerContent className="border-border/80 bg-background shadow-2xl data-[vaul-drawer-direction=bottom]:max-h-[78vh]">
<DrawerHeader className="gap-2 border-b border-border/70 px-5 pb-5 pt-5 text-left md:px-6">
<DrawerTitle className="text-lg font-semibold">
Current Runtime
Expand Down
5 changes: 3 additions & 2 deletions src/components/layout/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ export function AppShell() {
function OverlayLoadingFallback(args: { title: string }) {
return (
<div
className={`${UI_LAYER_CLASS.dialog} fixed inset-0 flex items-center justify-center bg-overlay p-4 backdrop-blur-[2px]`}
className={`${UI_LAYER_CLASS.dialog} fixed inset-0 flex items-center justify-center bg-overlay p-4`}
>
<Card className="w-full max-w-md border-border/80 bg-background/95 p-6 shadow-2xl">
<div className="text-sm text-muted-foreground">
Expand Down Expand Up @@ -1263,7 +1263,7 @@ export function AppShell() {
<div
className={`pointer-events-none absolute left-1/2 top-16 ${UI_LAYER_CLASS.floatingChrome} -translate-x-1/2`}
>
<div className="rounded-full border border-border/80 bg-card/95 px-3 py-1 text-sm font-medium text-foreground shadow-lg backdrop-blur-sm">
<div className="rounded-full border border-border/80 bg-card px-3 py-1 text-sm font-medium text-foreground shadow-lg">
Zoom {zoomHudPercent}%
</div>
</div>
Expand Down Expand Up @@ -1397,6 +1397,7 @@ export function AppShell() {
>
<div
ref={contentRowRef}
data-stave-content-row=""
className="flex min-h-0 min-w-0 flex-1 overflow-hidden"
>
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/CliSessionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ function CliSessionPanelImpl() {
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
{/* Header container always at child position 0 so {terminalViewport}
stays at position 1 and React never unmounts the terminal surface. */}
<div className="shrink-0 border-b border-border/70 bg-card/95 px-4 py-3 backdrop-blur-sm">
<div className="shrink-0 border-b border-border/70 bg-card px-4 py-3">
{isVisible ? (
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function ConfirmDialog(args: ConfirmDialogProps) {
}

return (
<div className={cn(UI_LAYER_CLASS.dialog, "fixed inset-0 flex items-center justify-center bg-overlay p-4 backdrop-blur-[2px]")} onMouseDown={loading ? undefined : onCancel}>
<div className={cn(UI_LAYER_CLASS.dialog, "fixed inset-0 flex items-center justify-center bg-overlay p-4")} onMouseDown={loading ? undefined : onCancel}>
<Card className="w-full max-w-md rounded-lg border-border/80 bg-card p-4 shadow-xl" onMouseDown={(event) => event.stopPropagation()}>
<form onSubmit={handleSubmit} onKeyDown={handleKeyDown}>
<h3 className="text-base font-semibold text-foreground">{title}</h3>
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/CreateWorkspaceBranchPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ export function CreateWorkspaceBranchPicker({
<PopoverContent
align="start"
sideOffset={8}
className="max-w-[32rem] gap-0 overflow-hidden border border-border/80 bg-card/96 p-0 shadow-2xl supports-backdrop-filter:backdrop-blur-xl"
className="max-w-[32rem] gap-0 overflow-hidden border border-border/80 bg-card p-0 shadow-2xl"
style={{ width: "var(--radix-popover-trigger-width)" }}
onOpenAutoFocus={(event) => event.preventDefault()}
>
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/CreateWorkspaceDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ export function CreateWorkspaceDialog({
<div
className={cn(
UI_LAYER_CLASS.dialog,
"t-overlay fixed inset-0 flex items-center justify-center bg-overlay p-4 backdrop-blur-[2px]",
"t-overlay fixed inset-0 flex items-center justify-center bg-overlay p-4",
)}
role="dialog"
aria-modal="true"
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/KeyboardShortcutsDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ export function KeyboardShortcutsDrawer({

return (
<Drawer open={open} onOpenChange={onOpenChange} direction="top">
<DrawerContent className="overflow-hidden border-border/80 bg-card/95 shadow-2xl supports-backdrop-filter:backdrop-blur-xl data-[vaul-drawer-direction=top]:mb-0 data-[vaul-drawer-direction=top]:h-dvh data-[vaul-drawer-direction=top]:max-h-dvh data-[vaul-drawer-direction=top]:rounded-b-none data-[vaul-drawer-direction=top]:border-b-0">
<DrawerContent className="overflow-hidden border-border/80 bg-card shadow-2xl data-[vaul-drawer-direction=top]:mb-0 data-[vaul-drawer-direction=top]:h-dvh data-[vaul-drawer-direction=top]:max-h-dvh data-[vaul-drawer-direction=top]:rounded-b-none data-[vaul-drawer-direction=top]:border-b-0">
<div className="mx-auto flex min-h-0 w-full max-w-6xl flex-1 flex-col">
<DrawerHeader className="shrink-0 gap-0 border-b border-border/70 px-5 pb-5 pt-5 !text-left md:px-6">
<div className="flex items-center justify-between gap-3">
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/OpenPathDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export function OpenPathDialog(args: OpenPathDialogProps) {

return (
<div
className={cn(UI_LAYER_CLASS.dialog, "fixed inset-0 flex items-center justify-center bg-overlay p-4 backdrop-blur-[2px]")}
className={cn(UI_LAYER_CLASS.dialog, "fixed inset-0 flex items-center justify-center bg-overlay p-4")}
onMouseDown={close}
>
<Card
Expand Down
Loading
Loading