Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b0a90c5
docs: v2 project create/import design + plan
Kitenite Apr 18, 2026
b23f593
feat(trpc): v2Projects.findByGitHubRemote + jwt-scoped create
Kitenite Apr 18, 2026
85969df
feat(host-service): project.create / setup / list / findByPath / remove
Kitenite Apr 18, 2026
82524eb
feat(desktop): add-repository modals at dashboard layout level
Kitenite Apr 18, 2026
4888888
feat(desktop): workspaces-tab Available section + folder-first import…
Kitenite Apr 18, 2026
0bd1845
feat(desktop): workspace-create inline setup + remote-device stub
Kitenite Apr 18, 2026
364e874
Fix infinite import
Kitenite Apr 18, 2026
fdfe619
fix: pre-existing notification test, a11y labels, design doc shape
Kitenite Apr 18, 2026
f344e24
feat(desktop): unify new-workspace pickers + link popovers, strip cha…
Kitenite Apr 19, 2026
e3a4919
feat(desktop): host-scoped project picker with Available / Needs setu…
Kitenite Apr 19, 2026
c095e96
lint
Kitenite Apr 19, 2026
68218f4
fix(desktop): IssueLinkCommand uncontrolled close + PlusMenu aria-label
Kitenite Apr 19, 2026
39faeb1
refactor(desktop): drop controlled-open props from IssueLinkCommand; …
Kitenite Apr 19, 2026
7529cb7
fix(trpc): scope v2Project.findByGitHubRemote + modal picker to activ…
Kitenite Apr 19, 2026
03184b8
Lint
Kitenite Apr 19, 2026
8b5aa02
fix(host-service): clone-then-cloud in project.createFromClone, rollb…
Kitenite Apr 19, 2026
9a35708
fix(host-service): move project.create visibility into GitHub-provisi…
Kitenite Apr 19, 2026
60dd385
refactor(desktop): use Radix composition for link-command tooltips, d…
Kitenite Apr 19, 2026
7d61d5f
chore: trim past-state narration + what-describing comments
Kitenite Apr 19, 2026
28f8a0e
refactor(desktop): strip v2 discovery/recovery surface to MVP
Kitenite Apr 19, 2026
9993b56
refactor(desktop): open remote-host workspaces without gating
Kitenite Apr 19, 2026
c528068
refactor(desktop): extract FormPickerTrigger component
Kitenite Apr 19, 2026
7dd4e50
refactor(desktop): remove dead PinAndSetupModal + async-hygiene sweep
Kitenite Apr 19, 2026
4554490
refactor(host-service): reject re-pointing instead of confirming it
Kitenite Apr 19, 2026
5b95fd8
fix(desktop): unify workspaces-tab empty state
Kitenite Apr 19, 2026
126379a
revert queries
Kitenite Apr 19, 2026
31dab8b
Clean up dead code
Kitenite Apr 19, 2026
15570dd
refactor(desktop): port v1 new-project UI into NewProjectModal
Kitenite Apr 19, 2026
8a69dc1
feat(db/trpc): decouple v2 projects from GitHub App installs
Kitenite Apr 20, 2026
090460e
No candidate thing
Kitenite Apr 20, 2026
5b0a832
feat(desktop): flag projects not set up on selected host in new-works…
Kitenite Apr 20, 2026
bc78d2c
Merge remote-tracking branch 'origin/main' into estimated-drink
Kitenite Apr 20, 2026
1abd270
chore: biome format fix + sync design doc to workspaces-tab filter
Kitenite Apr 20, 2026
c7d62c2
docs: move v2 project create/import plan to plans/done
Kitenite Apr 20, 2026
b6085fb
docs: drop rewrite/history notes from plan and design doc
Kitenite Apr 20, 2026
70f0797
chore: trim restating/navigational comments in project handlers
Kitenite Apr 20, 2026
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
3 changes: 2 additions & 1 deletion apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1791,7 +1791,8 @@ export async function createWorktreeFromPr({
{ cwd: worktreePath, timeout: 120_000 },
);
} catch (ghError) {
const ghMsg = ghError instanceof Error ? ghError.message : String(ghError);
const ghMsg =
ghError instanceof Error ? ghError.message : String(ghError);
// `gh pr checkout` can fail with "is not a branch" when the branch name
// contains '/' (e.g. "user/feature-branch"). Git has trouble resolving
// "origin/user/feature-branch" as a tracking ref inside a worktree.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,8 @@ describe("NotificationManager", () => {

expect(createNotification).toHaveBeenCalledWith(
expect.objectContaining({
title: "Input Needed — Test Workspace",
body: '"Test Title" needs your attention',
title: "Awaiting Response — Test Workspace",
body: '"Test Title" is waiting for your reply',
}),
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { useFocusPromptOnPane } from "renderer/components/Chat/ChatInterface/hoo
import { useHotkeyDisplay } from "renderer/hotkeys";
import type { SlashCommand } from "../../hooks/useSlashCommands";
import type { ModelOption, PermissionMode } from "../../types";
import { IssueLinkCommand } from "../IssueLinkCommand";
import { TiptapPromptEditor } from "../TiptapPromptEditor";
import { ChatComposerControls } from "./components/ChatComposerControls";
import { ChatInputDropZone } from "./components/ChatInputDropZone";
Expand Down Expand Up @@ -100,23 +99,12 @@ export function ChatInputFooter({
}
}, [pendingQuestion, textInput]);

const [issueLinkOpen, setIssueLinkOpen] = useState(false);
const [linkedIssues, setLinkedIssues] = useState<LinkedIssue[]>([]);
const inputRootRef = useRef<HTMLDivElement>(null);
const errorMessage = getErrorMessage(error);
const focusShortcutText = useHotkeyDisplay("FOCUS_CHAT_INPUT").text;
const showFocusHint = focusShortcutText !== "Unassigned";

const addLinkedIssue = useCallback(
(slug: string, title: string, taskId: string | undefined, url?: string) => {
setLinkedIssues((prev) => {
if (prev.some((issue) => issue.slug === slug)) return prev;
return [...prev, { slug, title, taskId, url }];
});
},
[],
);

const removeLinkedIssue = useCallback((slug: string) => {
setLinkedIssues((prev) => prev.filter((issue) => issue.slug !== slug));
}, []);
Expand Down Expand Up @@ -176,15 +164,7 @@ export function ChatInputFooter({
maxFileSize={10 * 1024 * 1024}
globalDrop
>
<ChatShortcuts
isFocused={isFocused}
setIssueLinkOpen={setIssueLinkOpen}
/>
<IssueLinkCommand
open={issueLinkOpen}
onOpenChange={setIssueLinkOpen}
onSelect={addLinkedIssue}
/>
<ChatShortcuts isFocused={isFocused} />
<FileDropOverlay visible={dragType === "files"} />
<PromptInputAttachments>
{renderAttachment ??
Expand Down Expand Up @@ -217,7 +197,6 @@ export function ChatInputFooter({
submitStatus={submitStatus}
submitDisabled={submitDisabled}
onStop={onStop}
onLinkIssue={() => setIssueLinkOpen(true)}
/>
</PromptInput>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ interface ChatComposerControlsProps {
submitStatus?: ChatStatus;
submitDisabled?: boolean;
onStop: (event: React.MouseEvent) => void;
onLinkIssue: () => void;
}

export function ChatComposerControls({
Expand All @@ -47,7 +46,6 @@ export function ChatComposerControls({
submitStatus,
submitDisabled,
onStop,
onLinkIssue,
}: ChatComposerControlsProps) {
return (
<PromptInputFooter>
Expand All @@ -70,7 +68,7 @@ export function ChatComposerControls({
/>
</PromptInputTools>
<div className="flex items-center gap-2">
<PlusMenu onLinkIssue={onLinkIssue} />
<PlusMenu />
<PromptInputSubmit
className="size-[23px] rounded-full border border-transparent bg-foreground/10 shadow-none p-[5px] hover:bg-foreground/20"
status={submitStatus}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@ import {
usePromptInputAttachments,
usePromptInputController,
} from "@superset/ui/ai-elements/prompt-input";
import type React from "react";
import { useHotkey } from "renderer/hotkeys";

interface ChatShortcutsProps {
isFocused: boolean;
setIssueLinkOpen: React.Dispatch<React.SetStateAction<boolean>>;
}

export function ChatShortcuts({
isFocused,
setIssueLinkOpen,
}: ChatShortcutsProps) {
export function ChatShortcuts({ isFocused }: ChatShortcutsProps) {
const attachments = usePromptInputAttachments();
const { textInput } = usePromptInputController();

Expand All @@ -25,14 +20,6 @@ export function ChatShortcuts({
{ enabled: isFocused, preventDefault: true },
);

useHotkey(
"CHAT_LINK_ISSUE",
() => {
setIssueLinkOpen((prev) => !prev);
},
{ enabled: isFocused, preventDefault: true },
);

useHotkey(
"FOCUS_CHAT_INPUT",
() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@superset/ui/command";
import { Popover, PopoverAnchor, PopoverContent } from "@superset/ui/popover";
import { Popover, PopoverContent, PopoverTrigger } from "@superset/ui/popover";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { useLiveQuery } from "@tanstack/react-db";
import Fuse from "fuse.js";
import type React from "react";
import type { RefObject } from "react";
import type { ReactNode } from "react";
import { useMemo, useState } from "react";
import {
StatusIcon,
Expand All @@ -21,22 +20,23 @@ import { useCollections } from "renderer/routes/_authenticated/providers/Collect

const MAX_RESULTS = 20;

type IssueLinkCommandProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
interface IssueLinkCommandProps {
children: ReactNode;
tooltipLabel: string;
onSelect: (
slug: string,
title: string,
taskId: string | undefined,
url?: string,
) => void;
} & (
| { variant?: "dialog" }
| { variant: "popover"; anchorRef: RefObject<HTMLElement | null> }
);
}

export function IssueLinkCommand(props: IssueLinkCommandProps) {
const { open, onOpenChange, onSelect } = props;
export function IssueLinkCommand({
children,
tooltipLabel,
onSelect,
}: IssueLinkCommandProps) {
const [open, setOpen] = useState(false);
Comment thread
Kitenite marked this conversation as resolved.
const [searchQuery, setSearchQuery] = useState("");
const collections = useCollections();

Expand Down Expand Up @@ -108,115 +108,93 @@ export function IssueLinkCommand(props: IssueLinkCommandProps) {
.map((r) => r.item);
}, [allTasks, searchQuery, taskFuse]);

const handleClose = () => {
setSearchQuery("");
onOpenChange(false);
};

const handleSelect = (
slug: string,
title: string,
taskId: string | undefined,
url?: string,
) => {
onSelect(slug, title, taskId, url);
handleClose();
setSearchQuery("");
setOpen(false);
};

const issueListContent = (
<>
<CommandInput
placeholder="Search issues..."
value={searchQuery}
onValueChange={setSearchQuery}
/>
<CommandList
className={props.variant === "popover" ? "max-h-[280px]" : undefined}
>
{filteredTasks.length === 0 && (
<CommandEmpty>No issues found.</CommandEmpty>
)}
{filteredTasks.length > 0 && (
<CommandGroup heading={searchQuery ? "Results" : "Recent issues"}>
{filteredTasks.map((task) => {
const status = task.statusId
? statusMap.get(task.statusId)
: undefined;
return (
<CommandItem
key={task.id}
value={task.slug}
onSelect={() =>
handleSelect(
task.slug,
task.title,
task.id,
task.externalUrl ?? undefined,
)
}
className="group"
>
{status ? (
<StatusIcon
type={status.type}
color={status.color}
progress={status.progressPercent ?? undefined}
/>
) : (
<span className="size-3.5 shrink-0 rounded-full border border-muted-foreground/40" />
)}
<span className="max-w-24 shrink-0 truncate font-mono text-xs text-muted-foreground">
{task.slug}
</span>
<span className="min-w-0 flex-1 truncate text-xs">
{task.title}
</span>
<span className="shrink-0 hidden text-xs text-muted-foreground group-data-[selected=true]:inline">
Link ↵
</span>
</CommandItem>
);
})}
</CommandGroup>
)}
</CommandList>
</>
);

if (props.variant === "popover") {
return (
<Popover open={open}>
<PopoverAnchor
virtualRef={props.anchorRef as React.RefObject<Element>}
/>
<PopoverContent
className="w-80 p-0"
align="start"
side="bottom"
onWheel={(event) => event.stopPropagation()}
onPointerDownOutside={handleClose}
onEscapeKeyDown={handleClose}
onFocusOutside={(e) => e.preventDefault()}
>
<Command shouldFilter={false}>{issueListContent}</Command>
</PopoverContent>
</Popover>
);
}

return (
<CommandDialog
<Popover
open={open}
onOpenChange={(nextOpen) => {
if (!nextOpen) setSearchQuery("");
onOpenChange(nextOpen);
onOpenChange={(next) => {
if (!next) setSearchQuery("");
setOpen(next);
}}
modal
title="Link issue"
description="Search for an issue to link"
showCloseButton={false}
>
{issueListContent}
</CommandDialog>
<Tooltip>
<PopoverTrigger asChild>
<TooltipTrigger asChild>{children}</TooltipTrigger>
</PopoverTrigger>
<TooltipContent side="bottom">{tooltipLabel}</TooltipContent>
</Tooltip>
<PopoverContent
className="w-80 p-0"
align="start"
side="bottom"
onWheel={(event) => event.stopPropagation()}
>
<Command shouldFilter={false}>
<CommandInput
placeholder="Search issues..."
value={searchQuery}
onValueChange={setSearchQuery}
/>
<CommandList className="max-h-[280px]">
{filteredTasks.length === 0 && (
<CommandEmpty>No issues found.</CommandEmpty>
)}
{filteredTasks.length > 0 && (
<CommandGroup heading={searchQuery ? "Results" : "Recent issues"}>
{filteredTasks.map((task) => {
const status = task.statusId
? statusMap.get(task.statusId)
: undefined;
return (
<CommandItem
key={task.id}
value={task.slug}
onSelect={() =>
handleSelect(
task.slug,
task.title,
task.id,
task.externalUrl ?? undefined,
)
}
className="group"
>
{status ? (
<StatusIcon
type={status.type}
color={status.color}
progress={status.progressPercent ?? undefined}
/>
) : (
<span className="size-3.5 shrink-0 rounded-full border border-muted-foreground/40" />
)}
<span className="max-w-24 shrink-0 truncate font-mono text-xs text-muted-foreground">
{task.slug}
</span>
<span className="min-w-0 flex-1 truncate text-xs">
{task.title}
</span>
<span className="shrink-0 hidden text-xs text-muted-foreground group-data-[selected=true]:inline">
Link ↵
</span>
</CommandItem>
);
})}
</CommandGroup>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}
Loading
Loading