| author | |
| committer | |
| log | 4a08686e749cca58dbab30307ab8445a3d5bef33 |
| tree | 8f6435664af925f160934266a4361302f42a3ddb |
| parent | b3de2bd926bf9e5f381d02cdcc32b85c39f7c4ed |
- add `zed` to shared editor contracts and Open In picker
- add Zed icon and wire launch resolution tests on macOS
- wrap branch optimistic updates in `startTransition` to avoid sync warnings5 files changed, 60 insertions(+), 8 deletions(-)
apps/server/src/open.test.ts+18| ... | ... | @@ -33,6 +33,15 @@ describe("resolveEditorLaunch", () => { |
| 33 | 33 | command: "code", |
| 34 | 34 | args: ["/tmp/workspace"], |
| 35 | 35 | }); |
| 36 | ||
| 37 | const zedLaunch = yield* resolveEditorLaunch( | |
| 38 | { cwd: "/tmp/workspace", editor: "zed" }, | |
| 39 | "darwin", | |
| 40 | ); | |
| 41 | assert.deepEqual(zedLaunch, { | |
| 42 | command: "zed", | |
| 43 | args: ["/tmp/workspace"], | |
| 44 | }); | |
| 36 | 45 | }), |
| 37 | 46 | ); |
| 38 | 47 | |
| ... | ... | @@ -64,6 +73,15 @@ describe("resolveEditorLaunch", () => { |
| 64 | 73 | command: "code", |
| 65 | 74 | args: ["--goto", "/tmp/workspace/src/open.ts:71:5"], |
| 66 | 75 | }); |
| 76 | ||
| 77 | const zedLineAndColumn = yield* resolveEditorLaunch( | |
| 78 | { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "zed" }, | |
| 79 | "darwin", | |
| 80 | ); | |
| 81 | assert.deepEqual(zedLineAndColumn, { | |
| 82 | command: "zed", | |
| 83 | args: ["/tmp/workspace/src/open.ts:71:5"], | |
| 84 | }); | |
| 67 | 85 | }), |
| 68 | 86 | ); |
| 69 | 87 |
apps/web/src/components/BranchToolbar.tsx+12-7| ... | ... | @@ -1,6 +1,6 @@ |
| 1 | 1 | import type { GitBranch, ThreadId } from "@t3tools/contracts"; |
| 2 | 2 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; |
| 3 | import { useOptimistic, useState } from "react"; | |
| 3 | import { startTransition, useOptimistic, useState } from "react"; | |
| 4 | 4 | |
| 5 | 5 | import { newCommandId } from "../lib/utils"; |
| 6 | 6 | import { |
| ... | ... | @@ -80,6 +80,11 @@ export default function BranchToolbar({ |
| 80 | 80 | canonicalActiveBranch, |
| 81 | 81 | (_currentBranch: string | null, optimisticBranch: string | null) => optimisticBranch, |
| 82 | 82 | ); |
| 83 | const setOptimisticBranchInTransition = (branch: string | null) => { | |
| 84 | startTransition(() => { | |
| 85 | setOptimisticBranch(branch); | |
| 86 | }); | |
| 87 | }; | |
| 83 | 88 | const branchNames = branches.map((branch) => branch.name); |
| 84 | 89 | const branchByName = new Map(branches.map((branch) => [branch.name, branch])); |
| 85 | 90 | const trimmedBranchQuery = branchQuery.trim(); |
| ... | ... | @@ -172,7 +177,7 @@ export default function BranchToolbar({ |
| 172 | 177 | const selectedBranchName = branch.isRemote |
| 173 | 178 | ? deriveLocalBranchNameFromRemoteRef(branch.name) |
| 174 | 179 | : branch.name; |
| 175 | setOptimisticBranch(selectedBranchName); | |
| 180 | setOptimisticBranchInTransition(selectedBranchName); | |
| 176 | 181 | |
| 177 | 182 | checkoutMutation.mutate(branch.name, { |
| 178 | 183 | onSuccess: async () => { |
| ... | ... | @@ -184,13 +189,13 @@ export default function BranchToolbar({ |
| 184 | 189 | nextBranchName = status.branch; |
| 185 | 190 | } |
| 186 | 191 | } |
| 187 | setOptimisticBranch(nextBranchName); | |
| 192 | setOptimisticBranchInTransition(nextBranchName); | |
| 188 | 193 | setThreadBranch(nextBranchName, activeWorktreePath); |
| 189 | 194 | setIsBranchMenuOpen(false); |
| 190 | 195 | onComposerFocusRequest?.(); |
| 191 | 196 | }, |
| 192 | 197 | onError: (error) => { |
| 193 | setOptimisticBranch(canonicalActiveBranch); | |
| 198 | setOptimisticBranchInTransition(canonicalActiveBranch); | |
| 194 | 199 | setThreadError(error instanceof Error ? error.message : "Failed to checkout branch."); |
| 195 | 200 | setIsBranchMenuOpen(true); |
| 196 | 201 | }, |
| ... | ... | @@ -201,10 +206,10 @@ export default function BranchToolbar({ |
| 201 | 206 | const name = rawName.trim(); |
| 202 | 207 | const api = readNativeApi(); |
| 203 | 208 | if (!api || !activeThreadId || !branchCwd || !name || createBranchMutation.isPending) return; |
| 204 | setOptimisticBranch(name); | |
| 209 | setOptimisticBranchInTransition(name); | |
| 205 | 210 | createBranchMutation.mutate(name, { |
| 206 | 211 | onSuccess: () => { |
| 207 | setOptimisticBranch(name); | |
| 212 | setOptimisticBranchInTransition(name); | |
| 208 | 213 | setThreadError(null); |
| 209 | 214 | setThreadBranch(name, activeWorktreePath); |
| 210 | 215 | setBranchQuery(""); |
| ... | ... | @@ -212,7 +217,7 @@ export default function BranchToolbar({ |
| 212 | 217 | onComposerFocusRequest?.(); |
| 213 | 218 | }, |
| 214 | 219 | onError: (error) => { |
| 215 | setOptimisticBranch(canonicalActiveBranch); | |
| 220 | setOptimisticBranchInTransition(canonicalActiveBranch); | |
| 216 | 221 | setThreadError(error instanceof Error ? error.message : "Failed to create branch."); |
| 217 | 222 | }, |
| 218 | 223 | }); |
apps/web/src/components/ChatView.tsx+6-1| ... | ... | @@ -100,7 +100,7 @@ import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./u |
| 100 | 100 | import { Separator } from "./ui/separator"; |
| 101 | 101 | import { Group, GroupSeparator } from "./ui/group"; |
| 102 | 102 | import { Menu, MenuItem, MenuPopup, MenuShortcut, MenuTrigger } from "./ui/menu"; |
| 103 | import { CursorIcon, Icon, VisualStudioCode } from "./Icons"; | |
| 103 | import { CursorIcon, Icon, VisualStudioCode, Zed } from "./Icons"; | |
| 104 | 104 | import { cn, isMacPlatform, isWindowsPlatform } from "~/lib/utils"; |
| 105 | 105 | import { Badge } from "./ui/badge"; |
| 106 | 106 | import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; |
| ... | ... | @@ -3693,6 +3693,11 @@ const OpenInPicker = memo(function OpenInPicker({ |
| 3693 | 3693 | Icon: VisualStudioCode, |
| 3694 | 3694 | value: "vscode", |
| 3695 | 3695 | }, |
| 3696 | { | |
| 3697 | label: "Zed", | |
| 3698 | Icon: Zed, | |
| 3699 | value: "zed", | |
| 3700 | }, | |
| 3696 | 3701 | { |
| 3697 | 3702 | label: isMacPlatform(navigator.platform) |
| 3698 | 3703 | ? "Finder" |
apps/web/src/components/Icons.tsx+23| ... | ... | @@ -119,3 +119,26 @@ export const VisualStudioCode: Icon = (props) => { |
| 119 | 119 | </svg> |
| 120 | 120 | ); |
| 121 | 121 | }; |
| 122 | ||
| 123 | export const Zed: Icon = (props) => { | |
| 124 | const id = useId(); | |
| 125 | const clipPathId = `${id}-zed-logo-a`; | |
| 126 | ||
| 127 | return ( | |
| 128 | <svg {...props} fill="none" viewBox="0 0 96 96"> | |
| 129 | <g clipPath={`url(#${clipPathId})`}> | |
| 130 | <path | |
| 131 | fill="currentColor" | |
| 132 | fillRule="evenodd" | |
| 133 | d="M9 6a3 3 0 0 0-3 3v66H0V9a9 9 0 0 1 9-9h80.379c4.009 0 6.016 4.847 3.182 7.682L43.055 57.187H57V51h6v7.688a4.5 4.5 0 0 1-4.5 4.5H37.055L26.743 73.5H73.5V36h6v37.5a6 6 0 0 1-6 6H20.743L10.243 90H87a3 3 0 0 0 3-3V21h6v66a9 9 0 0 1-9 9H6.621c-4.009 0-6.016-4.847-3.182-7.682L52.757 39H39v6h-6v-7.5a4.5 4.5 0 0 1 4.5-4.5h21.257l10.5-10.5H22.5V60h-6V22.5a6 6 0 0 1 6-6h52.757L85.757 6H9Z" | |
| 134 | clipRule="evenodd" | |
| 135 | /> | |
| 136 | </g> | |
| 137 | <defs> | |
| 138 | <clipPath id={clipPathId}> | |
| 139 | <path fill="#fff" d="M0 0h96v96H0z" /> | |
| 140 | </clipPath> | |
| 141 | </defs> | |
| 142 | </svg> | |
| 143 | ); | |
| 144 | }; |
packages/contracts/src/editor.ts+1| ... | ... | @@ -4,6 +4,7 @@ import { TrimmedNonEmptyString } from "./baseSchemas"; |
| 4 | 4 | export const EDITORS = [ |
| 5 | 5 | { id: "cursor", label: "Cursor", command: "cursor" }, |
| 6 | 6 | { id: "vscode", label: "VS Code", command: "code" }, |
| 7 | { id: "zed", label: "Zed", command: "zed" }, | |
| 7 | 8 | { id: "file-manager", label: "File Manager", command: null }, |
| 8 | 9 | ] as const; |
| 9 | 10 |