@nkzw/codiff-core 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ import type { OpenReviewSourceKind } from '../../types.ts';
2
+ export declare function OpenReviewSourceDialog({ kind, onClose, onOpen, }: {
3
+ kind: OpenReviewSourceKind;
4
+ onClose: () => void;
5
+ onOpen: (value: string) => Promise<void>;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import type { OpenReviewSourceKind } from '../../types.ts';
2
+ export declare function OpenReviewSourceMenu({ onOpen, onOpenFolder, }: {
3
+ onOpen: (kind: OpenReviewSourceKind) => void;
4
+ onOpenFolder: () => void;
5
+ }): import("react").JSX.Element;
@@ -1,12 +1,17 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { CodiffKeymap } from '../../config/types.ts';
3
3
  import type { RepositoryLoadError, ReviewComment } from '../../lib/app-types.ts';
4
- import type { ChangedFile, PullRequestMergeOptions, PullRequestMergeState, PullRequestReviewEvent, PullRequestReviewStatus } from '../../types.ts';
4
+ import type { ChangedFile, CodiffUpdateStatus, PullRequestMergeOptions, PullRequestMergeState, PullRequestReviewEvent, PullRequestReviewStatus } from '../../types.ts';
5
5
  export declare function ReviewSourceLoading(): import("react").JSX.Element;
6
6
  export declare function RepositoryChangeBanner({ onRefresh, visible, }: {
7
7
  onRefresh: () => void;
8
8
  visible: boolean;
9
9
  }): import("react").JSX.Element;
10
+ export type { CodiffUpdateStatus as UpdateStatus } from '../../types.ts';
11
+ export declare function UpdatePill({ onApply, status, }: {
12
+ onApply: () => void;
13
+ status: CodiffUpdateStatus;
14
+ }): import("react").JSX.Element | null;
10
15
  export declare function WalkthroughOutdatedBanner({ onDismiss, reason, }: {
11
16
  onDismiss: () => void;
12
17
  reason: string | null;
@@ -7,7 +7,7 @@ export type ReviewModeItem<Mode extends string> = {
7
7
  title?: string;
8
8
  value: Mode;
9
9
  };
10
- export declare function ReviewTopBar<Mode extends string>({ actions, context, leading, mode, modes, onModeChange, onToggleSidebar, repository, repositoryTooltip, sidebarCollapsed, toggleTitle, }: {
10
+ export declare function ReviewTopBar<Mode extends string>({ actions, context, leading, mode, modes, onModeChange, onToggleSidebar, repository, repositoryTooltip, sidebarCollapsed, sourceMenu, toggleTitle, }: {
11
11
  actions?: ReactNode;
12
12
  context?: ReactNode;
13
13
  leading?: ReactNode;
@@ -18,5 +18,6 @@ export declare function ReviewTopBar<Mode extends string>({ actions, context, le
18
18
  repository: ReactNode;
19
19
  repositoryTooltip?: string;
20
20
  sidebarCollapsed: boolean;
21
+ sourceMenu?: ReactNode;
21
22
  toggleTitle: string;
22
23
  }): import("react").JSX.Element;
@@ -2,12 +2,13 @@ import { type RefObject } from 'react';
2
2
  import type { ReviewComment, ReviewIdentity, SidebarMode } from '../../lib/app-types.ts';
3
3
  import { type Command } from '../../lib/command-registry.ts';
4
4
  import type { ReviewCommandTarget } from '../../lib/review-command-target.ts';
5
- import type { ChangedFile, CodiffPreferences, RepositoryState } from '../../types.ts';
5
+ import type { ChangedFile, CodiffPreferences, OpenReviewSourceKind, RepositoryState } from '../../types.ts';
6
6
  type UseAppCommandsOptions = {
7
7
  changeSidebarMode: (mode: SidebarMode) => void;
8
8
  focusFileFilter: () => void;
9
9
  getReviewCommandTarget: () => ReviewCommandTarget | null;
10
10
  onOpenDiffSearch: () => void;
11
+ onOpenReviewSource: (kind: OpenReviewSourceKind) => void;
11
12
  onOpenSelectedFile: () => void;
12
13
  onRefreshRepository: () => void;
13
14
  onToggleSidebar: () => void;
@@ -18,5 +19,5 @@ type UseAppCommandsOptions = {
18
19
  stateRef: RefObject<RepositoryState | null>;
19
20
  viewedRef: RefObject<Record<string, string>>;
20
21
  };
21
- export declare function useAppCommands({ changeSidebarMode, focusFileFilter, getReviewCommandTarget, onOpenDiffSearch, onOpenSelectedFile, onRefreshRepository, onToggleSidebar, onToggleViewed, onToggleWordWrap, preferencesRef, reviewCommentsRef, stateRef, viewedRef, }: UseAppCommandsOptions): readonly Command[];
22
+ export declare function useAppCommands({ changeSidebarMode, focusFileFilter, getReviewCommandTarget, onOpenDiffSearch, onOpenReviewSource, onOpenSelectedFile, onRefreshRepository, onToggleSidebar, onToggleViewed, onToggleWordWrap, preferencesRef, reviewCommentsRef, stateRef, viewedRef, }: UseAppCommandsOptions): readonly Command[];
22
23
  export {};
@@ -0,0 +1,13 @@
1
+ export type NativeKeyboardKeyMapping = {
2
+ value: string;
3
+ valueIsDeadKey?: boolean;
4
+ withShift: string;
5
+ withShiftIsDeadKey?: boolean;
6
+ };
7
+ export type NativeKeyboardLayout = Readonly<Record<string, NativeKeyboardKeyMapping>>;
8
+ export declare const codeForCharacter: (character: string, shiftKey: boolean) => string | null;
9
+ export declare const shiftedCharacterForCode: (code: string) => string | null;
10
+ export declare const hasKeyboardLayout: () => boolean;
11
+ export declare const trackKeyboardLayout: () => void;
12
+ export declare const applyKeyboardLayout: (layout: NativeKeyboardLayout) => void;
13
+ export declare const resetKeyboardLayout: () => void;
@@ -1,3 +1,5 @@
1
1
  import type { CodiffKeymap } from './types.ts';
2
- export declare const matchesShortcut: (event: Pick<KeyboardEvent, 'altKey' | 'ctrlKey' | 'key' | 'metaKey' | 'shiftKey'>, keymap: CodiffKeymap, action: keyof CodiffKeymap) => boolean;
2
+ type ShortcutEvent = Pick<KeyboardEvent, 'altKey' | 'code' | 'ctrlKey' | 'key' | 'metaKey' | 'shiftKey'>;
3
+ export declare const matchesShortcut: (event: ShortcutEvent, keymap: CodiffKeymap, action: keyof CodiffKeymap) => boolean;
3
4
  export declare const getShortcutLabel: (keymap: CodiffKeymap, action: keyof CodiffKeymap) => string;
5
+ export {};
@@ -3,6 +3,7 @@ export type CodiffTheme = 'system' | 'light' | 'dark';
3
3
  export type CodiffAgentBackend = 'codex' | 'claude' | 'opencode' | 'pi';
4
4
  export type CodiffSettings = {
5
5
  agentBackend: CodiffAgentBackend;
6
+ checkForUpdates: boolean;
6
7
  claudeModel: string;
7
8
  codeFontFamily: string;
8
9
  codeFontSize: number;
@@ -9,6 +9,15 @@ export declare const fileTreeSort: (left: {
9
9
  path: string;
10
10
  segments?: ReadonlyArray<string>;
11
11
  }) => number;
12
- export declare const compactPath: (path: string) => string;
12
+ export declare const abbreviateHomePath: (path: string) => string;
13
+ /**
14
+ * Splits a repository path into a shrinkable head and the repository's own
15
+ * directory, so the top bar can keep the repository name visible and put the
16
+ * ellipsis in the middle when the full path overflows.
17
+ */
18
+ export declare const splitRepositoryPath: (path: string) => {
19
+ head: string;
20
+ tail: string;
21
+ };
13
22
  export declare const sortFiles: (files: ReadonlyArray<ChangedFile>) => ChangedFile[];
14
23
  export declare const fuzzyMatches: (path: string, query: string) => boolean;
package/dist/react.mjs CHANGED
@@ -19,6 +19,7 @@ import { CaretDownIcon } from "@phosphor-icons/react/CaretDown";
19
19
  import "@phosphor-icons/react/CaretUp";
20
20
  import { CheckIcon } from "@phosphor-icons/react/Check";
21
21
  import { CheckCircleIcon } from "@phosphor-icons/react/CheckCircle";
22
+ import "@phosphor-icons/react/CircleNotch";
22
23
  import { PowerIcon } from "@phosphor-icons/react/Power";
23
24
  import { SealQuestionIcon } from "@phosphor-icons/react/SealQuestion";
24
25
  import { WarningOctagonIcon } from "@phosphor-icons/react/WarningOctagon";
@@ -1425,16 +1426,7 @@ const statusForTree = {
1425
1426
  untracked: "untracked"
1426
1427
  };
1427
1428
  const fileTreeSort = (left, right) => compareTreePaths(left.path, right.path);
1428
- const compactPath = (path) => {
1429
- const homePath = path.replace(/^\/Users\/[^/]+(?=\/|$)/, "~").replace(/^\/home\/[^/]+(?=\/|$)/, "~");
1430
- const parts = homePath.split("/").filter(Boolean);
1431
- if (parts.length <= 2) return homePath;
1432
- const prefix = homePath.startsWith("/") ? "/" : "";
1433
- const [first, ...rest] = parts;
1434
- const last = rest.pop();
1435
- const middle = rest.map((part) => part[0]).join("/");
1436
- return `${prefix}${first}/${middle ? `${middle}/` : ""}${last}`;
1437
- };
1429
+ const abbreviateHomePath = (path) => path.replace(/^\/Users\/[^/]+(?=\/|$)/, "~").replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^[A-Za-z]:[/\\]Users[/\\][^/\\]+(?=[/\\]|$)/, "~");
1438
1430
  function compareTreePaths(leftPath, rightPath) {
1439
1431
  const leftParts = leftPath.split("/");
1440
1432
  const rightParts = rightPath.split("/");
@@ -1633,26 +1625,108 @@ const syncTreeShadowStyle = (treeHost, styleAttribute, css) => {
1633
1625
  if (!existingStyle) shadowRoot.append(style);
1634
1626
  return true;
1635
1627
  };
1628
+ [...[..."abcdefghijklmnopqrstuvwxyz"].map((letter) => `Key${letter.toUpperCase()}`), ..."1234567890".split("").map((digit) => `Digit${digit}`)];
1629
+ const codeForCharacter = (character, shiftKey) => null;
1630
+ const shiftedCharacterForCode = (code) => null;
1631
+ const hasKeyboardLayout = () => false;
1636
1632
  //#endregion
1637
1633
  //#region config/keymap.ts
1638
1634
  const isMac = () => navigator.platform.toLowerCase().includes("mac");
1635
+ const unshiftedPunctuationCodes = {
1636
+ "-": "Minus",
1637
+ ",": "Comma",
1638
+ ";": "Semicolon",
1639
+ ".": "Period",
1640
+ "'": "Quote",
1641
+ "[": "BracketLeft",
1642
+ "]": "BracketRight",
1643
+ "/": "Slash",
1644
+ "\\": "Backslash",
1645
+ "`": "Backquote",
1646
+ "=": "Equal"
1647
+ };
1648
+ const shiftedPunctuationCodes = {
1649
+ _: "Minus",
1650
+ ":": "Semicolon",
1651
+ "!": "Digit1",
1652
+ "?": "Slash",
1653
+ "\"": "Quote",
1654
+ "(": "Digit9",
1655
+ ")": "Digit0",
1656
+ "{": "BracketLeft",
1657
+ "}": "BracketRight",
1658
+ "@": "Digit2",
1659
+ "*": "Digit8",
1660
+ "&": "Digit7",
1661
+ "#": "Digit3",
1662
+ "%": "Digit5",
1663
+ "^": "Digit6",
1664
+ "+": "Equal",
1665
+ "<": "Comma",
1666
+ ">": "Period",
1667
+ "|": "Backslash",
1668
+ "~": "Backquote",
1669
+ $: "Digit4"
1670
+ };
1671
+ const physicalCode = (key, shiftKey) => {
1672
+ const layoutCode = codeForCharacter(key, shiftKey);
1673
+ if (layoutCode !== null) return layoutCode;
1674
+ if (hasKeyboardLayout()) return null;
1675
+ if (/^[a-z]$/.test(key)) return `Key${key.toUpperCase()}`;
1676
+ if (/^[0-9]$/.test(key)) return shiftKey ? null : `Digit${key}`;
1677
+ return (shiftKey ? shiftedPunctuationCodes : unshiftedPunctuationCodes)[key] ?? null;
1678
+ };
1679
+ const resolveComboKey = (spelled, shiftKey) => {
1680
+ if (!shiftKey || spelled.length !== 1 || !hasKeyboardLayout()) return {
1681
+ code: physicalCode(spelled, shiftKey),
1682
+ key: spelled,
1683
+ requiresCode: false
1684
+ };
1685
+ const shiftedCode = codeForCharacter(spelled, true);
1686
+ if (shiftedCode !== null) return {
1687
+ code: shiftedCode,
1688
+ key: spelled,
1689
+ requiresCode: false
1690
+ };
1691
+ const unshiftedCode = codeForCharacter(spelled, false);
1692
+ const shiftedCharacter = unshiftedCode === null ? null : shiftedCharacterForCode(unshiftedCode);
1693
+ if (unshiftedCode !== null && shiftedCharacter !== null) return {
1694
+ code: unshiftedCode,
1695
+ key: shiftedCharacter,
1696
+ requiresCode: true
1697
+ };
1698
+ return {
1699
+ code: null,
1700
+ key: spelled,
1701
+ requiresCode: false
1702
+ };
1703
+ };
1639
1704
  const parseKeyCombo = (combo) => {
1640
1705
  const parts = combo.split("+").map((part) => part.trim().toLowerCase());
1641
1706
  const mac = isMac();
1707
+ const shiftKey = parts.includes("shift");
1708
+ const { code, key, requiresCode } = resolveComboKey(parts.find((part) => part !== "mod" && part !== "ctrl" && part !== "alt" && part !== "shift" && part !== "meta") ?? "", shiftKey);
1642
1709
  return {
1643
1710
  altKey: parts.includes("alt"),
1711
+ code,
1644
1712
  ctrlKey: mac ? parts.includes("ctrl") : parts.includes("mod") || parts.includes("ctrl"),
1645
- key: parts.find((part) => part !== "mod" && part !== "ctrl" && part !== "alt" && part !== "shift" && part !== "meta") ?? "",
1713
+ key,
1646
1714
  metaKey: mac ? parts.includes("mod") || parts.includes("meta") : parts.includes("meta"),
1647
- shiftKey: parts.includes("shift")
1715
+ requiresCode,
1716
+ shiftKey
1648
1717
  };
1649
1718
  };
1650
1719
  const getBindingCombos = (binding) => Array.isArray(binding) ? binding : [binding];
1651
- const matchesKeyCombo = (event, combo) => {
1720
+ const isDeadOrCharacterKey = (key) => key === "Dead" || key.length === 1;
1721
+ const matchesTypedKey = (event, parsed) => event.key.toLowerCase() === parsed.key && (!parsed.requiresCode || parsed.code !== null && event.code === parsed.code);
1722
+ const matchesPhysicalKey = (event, parsed) => parsed.altKey && parsed.code !== null && event.code === parsed.code;
1723
+ const matchesCombo = (event, combo, matchesKey) => {
1652
1724
  const parsed = parseKeyCombo(combo);
1653
- return event.key.toLowerCase() === parsed.key && event.altKey === parsed.altKey && event.ctrlKey === parsed.ctrlKey && event.metaKey === parsed.metaKey && event.shiftKey === parsed.shiftKey;
1725
+ return matchesKey(event, parsed) && event.altKey === parsed.altKey && event.ctrlKey === parsed.ctrlKey && event.metaKey === parsed.metaKey && event.shiftKey === parsed.shiftKey;
1654
1726
  };
1655
- const matchesShortcut = (event, keymap, action) => getBindingCombos(keymap[action]).some((combo) => matchesKeyCombo(event, combo));
1727
+ const matchesBinding = (event, binding, matchesKey) => getBindingCombos(binding).some((combo) => matchesCombo(event, combo, matchesKey));
1728
+ const canFallBackToPhysicalKey = (event, keymap) => event.altKey && isMac() && isDeadOrCharacterKey(event.key) && !Object.values(keymap).some((binding) => matchesBinding(event, binding, matchesTypedKey));
1729
+ const matchesShortcut = (event, keymap, action) => matchesBinding(event, keymap[action], matchesTypedKey) || canFallBackToPhysicalKey(event, keymap) && matchesBinding(event, keymap[action], matchesPhysicalKey);
1656
1730
  //#endregion
1657
1731
  //#region app/components/merge-request/GeneralComments.tsx
1658
1732
  const getAuthorDisplayName = (author) => author.name || author.login;
@@ -7003,7 +7077,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
7003
7077
  }
7004
7078
  //#endregion
7005
7079
  //#region app/components/ReviewTopBar.tsx
7006
- function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, onToggleSidebar, repository, repositoryTooltip, sidebarCollapsed, toggleTitle }) {
7080
+ function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, onToggleSidebar, repository, repositoryTooltip, sidebarCollapsed, sourceMenu, toggleTitle }) {
7007
7081
  return /* @__PURE__ */ jsxs("header", {
7008
7082
  className: "review-top-bar workspace-top-bar",
7009
7083
  children: [
@@ -7013,7 +7087,7 @@ function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, on
7013
7087
  leading,
7014
7088
  /* @__PURE__ */ jsx("button", {
7015
7089
  "aria-label": sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar",
7016
- className: "sidebar-toggle-button",
7090
+ className: "review-top-bar-icon-button sidebar-toggle-button",
7017
7091
  onClick: onToggleSidebar,
7018
7092
  title: toggleTitle,
7019
7093
  type: "button",
@@ -7023,6 +7097,7 @@ function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, on
7023
7097
  weight: "bold"
7024
7098
  })
7025
7099
  }),
7100
+ sourceMenu,
7026
7101
  /* @__PURE__ */ jsx("div", {
7027
7102
  className: "review-top-bar-repository-slot",
7028
7103
  title: repositoryTooltip,
@@ -8652,6 +8727,7 @@ function useReviewFileState({ initialSelectedPath = null, onViewedChange } = {})
8652
8727
  const defaultConfigTemplate = {
8653
8728
  settings: {
8654
8729
  "agentBackend": "codex",
8730
+ "checkForUpdates": true,
8655
8731
  "claudeModel": "claude-sonnet-4-6",
8656
8732
  "codeFontFamily": "",
8657
8733
  "codeFontSize": 13,
@@ -9181,7 +9257,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
9181
9257
  });
9182
9258
  };
9183
9259
  const sourceLabel = snapshot.repository.source.type === "working-tree" ? null : getSourceLabel(snapshot.repository.source);
9184
- const rootLabel = repositoryUrl ? snapshot.repository.root : compactPath(snapshot.repository.root);
9260
+ const rootLabel = repositoryUrl ? snapshot.repository.root : abbreviateHomePath(snapshot.repository.root);
9185
9261
  const sourceExternalUrl = snapshot.repository.source.type === "pull-request" ? externalUrl ?? snapshot.repository.source.url : null;
9186
9262
  const repositoryLinkUrl = repositoryUrl ?? sourceExternalUrl;
9187
9263
  const walkthroughStatus = walkthroughRequestPending && interactive?.walkthroughStatus !== "ready" ? "generating" : interactive?.walkthroughStatus;
package/dist/style.css CHANGED
@@ -5298,6 +5298,29 @@ html[data-codiff-platform="darwin"] .workspace-top-bar {
5298
5298
  overflow: hidden;
5299
5299
  }
5300
5300
 
5301
+ .review-top-bar-repository-path {
5302
+ flex: 0 auto;
5303
+ align-items: center;
5304
+ display: flex;
5305
+ }
5306
+
5307
+ .review-top-bar-repository-head {
5308
+ text-overflow: ellipsis;
5309
+ white-space: nowrap;
5310
+ flex: 0 auto;
5311
+ min-width: 0;
5312
+ overflow: hidden;
5313
+ }
5314
+
5315
+ .review-top-bar-repository-tail {
5316
+ text-overflow: ellipsis;
5317
+ white-space: nowrap;
5318
+ flex: none;
5319
+ min-width: 0;
5320
+ max-width: 100%;
5321
+ overflow: hidden;
5322
+ }
5323
+
5301
5324
  .review-top-bar-branch {
5302
5325
  color: var(--muted);
5303
5326
  corner-shape: squircle;
@@ -5433,7 +5456,7 @@ a.review-top-bar-repository:focus-visible, a.review-top-bar-source:focus-visible
5433
5456
  height: 6px;
5434
5457
  }
5435
5458
 
5436
- .sidebar-toggle-button {
5459
+ .review-top-bar-icon-button {
5437
5460
  -webkit-app-region: no-drag;
5438
5461
  color: var(--muted);
5439
5462
  corner-shape: squircle;
@@ -5451,12 +5474,13 @@ a.review-top-bar-repository:focus-visible, a.review-top-bar-source:focus-visible
5451
5474
  display: inline-flex;
5452
5475
  }
5453
5476
 
5454
- .sidebar-toggle-button:hover {
5477
+ .review-top-bar-icon-button:hover {
5455
5478
  color: var(--sidebar-text);
5456
5479
  background: #7f7f7f24;
5457
5480
  }
5458
5481
 
5459
- .sidebar-toggle-button:active {
5482
+ .review-top-bar-icon-button:active {
5483
+ color: var(--sidebar-text);
5460
5484
  background: #7f7f7f33;
5461
5485
  }
5462
5486
 
@@ -8162,6 +8186,7 @@ diffs-container .review-comment-thread {
8162
8186
 
8163
8187
  .empty-panel code {
8164
8188
  color: var(--text);
8189
+ overflow-wrap: anywhere;
8165
8190
  }
8166
8191
 
8167
8192
  .empty-panel-menu-path {
@@ -8346,9 +8371,8 @@ diffs-container .review-comment-thread {
8346
8371
  padding: 4px 6px;
8347
8372
  }
8348
8373
 
8349
- .command-bar-overlay {
8374
+ .command-bar-overlay, .open-review-source-overlay {
8350
8375
  backdrop-filter: blur(4px);
8351
- z-index: 100;
8352
8376
  background: #ffffff47;
8353
8377
  justify-content: center;
8354
8378
  align-items: flex-start;
@@ -8358,28 +8382,35 @@ diffs-container .review-comment-thread {
8358
8382
  inset: 0;
8359
8383
  }
8360
8384
 
8361
- :root[data-theme="dark"] .command-bar-overlay {
8385
+ .command-bar-overlay {
8386
+ z-index: 100;
8387
+ }
8388
+
8389
+ :root[data-theme="dark"] .command-bar-overlay, :root[data-theme="dark"] .open-review-source-overlay {
8362
8390
  background: #00000029;
8363
8391
  }
8364
8392
 
8365
8393
  @media (prefers-color-scheme: dark) {
8366
- :root:not([data-theme="light"]) .command-bar-overlay {
8394
+ :root:not([data-theme="light"]) .command-bar-overlay, :root:not([data-theme="light"]) .open-review-source-overlay {
8367
8395
  background: #00000029;
8368
8396
  }
8369
8397
  }
8370
8398
 
8371
- .command-bar {
8399
+ .command-bar, .open-review-source-dialog {
8372
8400
  backdrop-filter: blur(28px) saturate(1.4);
8373
8401
  background: color-mix(in srgb, var(--code-bg) 88%, transparent);
8374
8402
  border: 1px solid var(--file-border);
8375
8403
  corner-shape: squircle;
8376
8404
  border-radius: 28px;
8405
+ box-shadow: inset 0 0 0 1px #ffffff1f, 0 18px 80px -54px #000000c7, 0 16px 46px -30px #000000b8, 0 12px 24px -18px #000000e6;
8406
+ }
8407
+
8408
+ .command-bar {
8377
8409
  flex-direction: column;
8378
8410
  width: min(520px, 90vw);
8379
8411
  max-height: 400px;
8380
8412
  display: flex;
8381
8413
  overflow: hidden;
8382
- box-shadow: inset 0 0 0 1px #ffffff1f, 0 18px 80px -54px #000000c7, 0 16px 46px -30px #000000b8, 0 12px 24px -18px #000000e6;
8383
8414
  }
8384
8415
 
8385
8416
  .command-bar-input {
@@ -8467,6 +8498,73 @@ diffs-container .review-comment-thread {
8467
8498
  padding: 3px 6px;
8468
8499
  }
8469
8500
 
8501
+ .open-review-source-overlay {
8502
+ z-index: 101;
8503
+ padding-inline: 16px;
8504
+ }
8505
+
8506
+ .open-review-source-dialog {
8507
+ flex-direction: column;
8508
+ gap: 18px;
8509
+ width: min(520px, 100%);
8510
+ padding: 20px;
8511
+ display: flex;
8512
+ box-shadow: 0 10px 30px #00000029;
8513
+ }
8514
+
8515
+ .open-review-source-heading {
8516
+ gap: 5px;
8517
+ display: grid;
8518
+ }
8519
+
8520
+ .open-review-source-heading h2 {
8521
+ font: 600 16px/1.35 var(--font-sans);
8522
+ }
8523
+
8524
+ .open-review-source-heading p, .open-review-source-error {
8525
+ color: var(--muted);
8526
+ font: 13px/1.4 var(--font-sans);
8527
+ }
8528
+
8529
+ .open-review-source-field {
8530
+ gap: 7px;
8531
+ display: grid;
8532
+ }
8533
+
8534
+ .open-review-source-label {
8535
+ font: 600 12px/1.35 var(--font-sans);
8536
+ }
8537
+
8538
+ .open-review-source-input {
8539
+ color: var(--text);
8540
+ corner-shape: squircle;
8541
+ font: 14px/1.35 var(--font-sans);
8542
+ background: #7f7f7f1a;
8543
+ border: 0;
8544
+ border-radius: 17px;
8545
+ outline: none;
8546
+ width: 100%;
8547
+ height: 34px;
8548
+ padding: 0 12px;
8549
+ }
8550
+
8551
+ .open-review-source-input:focus {
8552
+ box-shadow: 0 0 0 2px #3d87f552;
8553
+ }
8554
+
8555
+ .open-review-source-input::placeholder {
8556
+ color: var(--muted);
8557
+ }
8558
+
8559
+ .open-review-source-error {
8560
+ color: var(--danger);
8561
+ }
8562
+
8563
+ .open-review-source-actions {
8564
+ justify-content: space-between;
8565
+ display: flex;
8566
+ }
8567
+
8470
8568
  @media (width <= 720px) {
8471
8569
  .review-top-bar-repository-slot, .review-top-bar-context {
8472
8570
  display: none;
package/dist/types.d.ts CHANGED
@@ -147,6 +147,8 @@ export type ReviewSource = {
147
147
  type: 'pull-request';
148
148
  url: string;
149
149
  };
150
+ /** Sources that can be entered from the palette or native application menu. */
151
+ export type OpenReviewSourceKind = 'branch' | 'commit' | 'pull-request';
150
152
  export type HistoryEntry = {
151
153
  author: string;
152
154
  committedAt: number;
@@ -355,6 +357,7 @@ export type WalkthroughContext = {
355
357
  };
356
358
  export type CodiffLaunchOptions = {
357
359
  agentBackend?: 'codex' | 'claude' | 'opencode' | 'pi';
360
+ applyUpdate?: boolean;
358
361
  claudeSessionId?: string;
359
362
  codexSessionId?: string;
360
363
  opencodeSessionId?: string;
@@ -647,6 +650,14 @@ export type CodiffPreferences = {
647
650
  wordWrap: boolean;
648
651
  };
649
652
  export type ReviewPreferences = Pick<CodiffPreferences, 'codeFontFamily' | 'codeFontSize' | 'diffStyle' | 'showWhitespace' | 'theme' | 'wordWrap'>;
653
+ export type CodiffUpdatePhase = 'available' | 'error' | 'idle' | 'installerReady' | 'updating';
654
+ export type CodiffUpdateStatus = {
655
+ currentVersion: string;
656
+ message?: string;
657
+ phase: CodiffUpdatePhase;
658
+ strategy?: 'download' | 'manual' | 'squirrel';
659
+ version?: string;
660
+ };
650
661
  export type PullRequestReviewComment = {
651
662
  anchor?: 'file' | 'line';
652
663
  body: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkzw/codiff-core",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "description": "A fast diff viewer.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -18,6 +18,7 @@
18
18
  "type": "module",
19
19
  "sideEffects": [
20
20
  "./App.css",
21
+ "./Desktop.css",
21
22
  "./dist/*.css"
22
23
  ],
23
24
  "main": "./dist/index.mjs",
@@ -55,10 +56,10 @@
55
56
  "@phosphor-icons/react": "^2.1.10",
56
57
  "@pierre/diffs": "1.3.0-beta.9",
57
58
  "@pierre/trees": "1.0.0-beta.5",
58
- "@radix-ui/react-slot": "^1.3.0",
59
+ "@radix-ui/react-slot": "^1.3.3",
59
60
  "class-variance-authority": "^0.7.1",
60
61
  "ghostty-web": "^0.4.0",
61
- "lucide-react": "^1.24.0",
62
+ "lucide-react": "^1.26.0",
62
63
  "valibot": "^1.4.2"
63
64
  },
64
65
  "peerDependencies": {