feat(web): add search button to sidebar header + restore turbo globalEnv

Add a visible search trigger button next to the create-issue button in
the sidebar header, improving search discoverability (previously only
accessible via ⌘K). Search dialog open state is shared via a Zustand
store so both the button and keyboard shortcut work.

Also restores turbo.json globalEnv config (FRONTEND_PORT, etc.) that was
accidentally dropped during the monorepo extraction, fixing worktree
port conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing
2026-04-09 18:35:22 +08:00
parent 141d7fd0aa
commit 7950ac72af
5 changed files with 58 additions and 14 deletions

View File

@@ -17,6 +17,7 @@ import {
SquarePen,
CircleUser,
FolderKanban,
Search,
} from "lucide-react";
import { WorkspaceAvatar } from "@multica/views/workspace/workspace-avatar";
import { useIssueDraftStore } from "@multica/core/issues/stores/draft-store";
@@ -49,6 +50,7 @@ import { inboxKeys, deduplicateInboxItems } from "@multica/core/inbox/queries";
import { api } from "@/platform/api";
import { useModalStore } from "@multica/core/modals";
import { useMyRuntimesNeedUpdate } from "@multica/core/runtimes/hooks";
import { useSearchStore } from "@/features/search";
const primaryNav = [
{ href: "/inbox", label: "Inbox", icon: Inbox },
@@ -172,16 +174,27 @@ export function AppSidebar() {
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
<Tooltip>
<TooltipTrigger
className="relative flex h-7 w-7 items-center justify-center rounded-lg bg-background text-foreground shadow-sm hover:bg-accent"
onClick={() => useModalStore.getState().open("create-issue")}
>
<SquarePen className="size-3.5" />
<DraftDot />
</TooltipTrigger>
<TooltipContent side="bottom">New issue</TooltipContent>
</Tooltip>
<div className="flex items-center gap-1">
<Tooltip>
<TooltipTrigger
className="flex h-7 w-7 items-center justify-center rounded-lg bg-secondary text-muted-foreground hover:bg-secondary/80 hover:text-secondary-foreground"
onClick={() => useSearchStore.getState().setOpen(true)}
>
<Search className="size-3.5" />
</TooltipTrigger>
<TooltipContent side="bottom">Search workspace (K)</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
className="relative flex h-7 w-7 items-center justify-center rounded-lg bg-background text-foreground shadow-sm hover:bg-accent"
onClick={() => useModalStore.getState().open("create-issue")}
>
<SquarePen className="size-3.5" />
<DraftDot />
</TooltipTrigger>
<TooltipContent side="bottom">New issue</TooltipContent>
</Tooltip>
</div>
</div>
</SidebarHeader>

View File

@@ -15,10 +15,12 @@ import {
DialogTitle,
DialogDescription,
} from "@multica/ui/components/ui/dialog";
import { useSearchStore } from "../stores/search-store";
export function SearchCommand() {
const router = useRouter();
const [open, setOpen] = useState(false);
const open = useSearchStore((s) => s.open);
const setOpen = useSearchStore((s) => s.setOpen);
const [query, setQuery] = useState("");
const [results, setResults] = useState<SearchIssueResult[]>([]);
const [isLoading, setIsLoading] = useState(false);
@@ -30,7 +32,7 @@ export function SearchCommand() {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((prev) => !prev);
useSearchStore.getState().toggle();
}
};
document.addEventListener("keydown", handleKeyDown);
@@ -186,8 +188,9 @@ export function SearchCommand() {
)}
{!isLoading && !query.trim() && (
<div className="py-10 text-center text-sm text-muted-foreground">
Type to search issues...
<div className="flex flex-col items-center gap-2 py-10 text-sm text-muted-foreground">
<span>Type to search issues...</span>
<span className="text-xs">Press <kbd className="rounded bg-muted px-1.5 py-0.5 font-medium">K</kbd> to open this anytime</span>
</div>
)}
</CommandPrimitive.List>

View File

@@ -1 +1,2 @@
export { SearchCommand } from "./components/search-command";
export { useSearchStore } from "./stores/search-store";

View File

@@ -0,0 +1,15 @@
"use client";
import { create } from "zustand";
interface SearchStore {
open: boolean;
setOpen: (open: boolean) => void;
toggle: () => void;
}
export const useSearchStore = create<SearchStore>((set) => ({
open: false,
setOpen: (open) => set({ open }),
toggle: () => set((s) => ({ open: !s.open })),
}));

View File

@@ -1,5 +1,17 @@
{
"$schema": "https://turbo.build/schema.json",
"globalEnv": [
"DATABASE_URL",
"PORT",
"FRONTEND_PORT",
"FRONTEND_ORIGIN",
"NEXT_PUBLIC_API_URL",
"NEXT_PUBLIC_WS_URL",
"MULTICA_SERVER_URL",
"COMPOSE_PROJECT_NAME",
"POSTGRES_DB",
"POSTGRES_PORT"
],
"tasks": {
"build": {
"dependsOn": ["^build"],