@happy-nut/monacori 0.1.21 → 0.1.23

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.
package/dist/app-main.js CHANGED
@@ -2,9 +2,10 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { spawn } from "node:child_process";
3
3
  import { basename, dirname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { app, BrowserWindow, dialog, ipcMain, Menu, nativeImage } from "electron";
5
+ import { app, BrowserWindow, dialog, ipcMain, Menu, nativeImage, Notification, shell } from "electron";
6
6
  import { buildDiffReview, performHttpRequest } from "./cli.js";
7
- import { sanitizeTerminalEnv } from "./util.js";
7
+ import { sanitizeTerminalEnv, ensureUtf8Locale } from "./util.js";
8
+ import { readGitLog, readCommitDiff } from "./git-log.js";
8
9
  import { readUnifiedDiff } from "./diff.js";
9
10
  import { isGitRepository } from "./git.js";
10
11
  import { renderWelcomeHtml } from "./render.js";
@@ -96,6 +97,61 @@ ipcMain.handle("monacori:get-file", (event, request) => {
96
97
  });
97
98
  // Phase 2b lazy-LOAD: serve the full source files JSON (with content) for the calling window on demand.
98
99
  ipcMain.handle("monacori:get-source-data", (event) => stateFromEvent(event)?.sourceData ?? "[]");
100
+ // Git history view (Cmd+9): the log list and a single commit's full diff, for the calling window's repo.
101
+ ipcMain.handle("monacori:git-log", (event, request) => {
102
+ const state = stateFromEvent(event);
103
+ if (!state)
104
+ return [];
105
+ try {
106
+ return readGitLog(state.options.root, { limit: request?.limit, skip: request?.skip });
107
+ }
108
+ catch {
109
+ return [];
110
+ }
111
+ });
112
+ ipcMain.handle("monacori:git-commit-diff", (event, request) => {
113
+ const state = stateFromEvent(event);
114
+ if (!state || !request?.sha)
115
+ return null;
116
+ try {
117
+ return readCommitDiff(state.options.root, request.sha);
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ });
123
+ // Sidebar row actions (Opt+Enter menu): reveal a file in Finder / open a terminal at its directory.
124
+ // `path` is repo-root-relative (the tree's data-source-file / data-file), resolved against this window's root.
125
+ ipcMain.handle("monacori:reveal-in-finder", (event, request) => {
126
+ const state = stateFromEvent(event);
127
+ if (!state || !request?.path)
128
+ return { ok: false };
129
+ try {
130
+ shell.showItemInFolder(join(state.options.root, request.path));
131
+ return { ok: true };
132
+ }
133
+ catch (e) {
134
+ return { ok: false, error: String(e) };
135
+ }
136
+ });
137
+ ipcMain.handle("monacori:open-terminal-at", (event, request) => {
138
+ const state = stateFromEvent(event);
139
+ if (!state || !request?.path)
140
+ return { ok: false };
141
+ const dir = dirname(join(state.options.root, request.path)); // the file's containing directory
142
+ try {
143
+ if (process.platform === "darwin")
144
+ spawn("open", ["-a", "Terminal", dir], { detached: true, stdio: "ignore" }).unref();
145
+ else if (process.platform === "win32")
146
+ spawn("cmd", ["/c", "start", "cmd", "/k", `cd /d "${dir}"`], { detached: true, stdio: "ignore", shell: true }).unref();
147
+ else
148
+ spawn("x-terminal-emulator", [], { cwd: dir, detached: true, stdio: "ignore" }).unref();
149
+ return { ok: true };
150
+ }
151
+ catch (e) {
152
+ return { ok: false, error: String(e) };
153
+ }
154
+ });
99
155
  // Welcome screen's "Open Folder" button: pick a directory; load it into the window that asked if it's a
100
156
  // git repo, else return the "not-git" code so the welcome renderer can show its inline hint (it keys off
101
157
  // r.error === "not-git"). This flow reports errors in-page, so — unlike the File menu — no native box.
@@ -190,11 +246,13 @@ ipcMain.handle("monacori:pty-spawn", (event, size) => {
190
246
  const id = ++nextPtyId;
191
247
  const shell = process.env.SHELL || (process.platform === "win32" ? "powershell.exe" : "/bin/zsh");
192
248
  const t = spawnPty(shell, [], {
193
- name: "xterm-color",
249
+ // 256-color terminfo + COLORTERM=truecolor so TUIs (e.g. Claude Code's coral logo) emit 24-bit color and
250
+ // xterm.js renders the exact hue. "xterm-color" is 8-color, which downgraded the orange logo to ANSI red.
251
+ name: "xterm-256color",
194
252
  cols: size?.cols ?? 80,
195
253
  rows: size?.rows ?? 24,
196
254
  cwd: state.options.root,
197
- env: sanitizeTerminalEnv(process.env),
255
+ env: ensureUtf8Locale({ ...sanitizeTerminalEnv(process.env), TERM: "xterm-256color", COLORTERM: "truecolor" }),
198
256
  });
199
257
  state.terms.set(id, t);
200
258
  // Guard every relay with isDestroyed(): a pty can outlive its window (close races pty teardown), and
@@ -227,6 +285,35 @@ ipcMain.on("monacori:pty-kill", (event, msg) => {
227
285
  state.terms.delete(msg.id);
228
286
  }
229
287
  });
288
+ // A TUI in the integrated terminal rang the bell (e.g. Claude Code finished a turn / needs input). Raise a
289
+ // native notification when the window ISN'T focused — while you're watching, the bell itself is enough — plus
290
+ // a dock bounce / taskbar flash. Clicking the notification brings the window forward.
291
+ ipcMain.on("monacori:bell", (event, msg) => {
292
+ const win = BrowserWindow.fromWebContents(event.sender);
293
+ if (!win || win.isDestroyed() || win.isFocused())
294
+ return;
295
+ try {
296
+ if (Notification.isSupported()) {
297
+ const note = new Notification({ title: msg?.title || "monacori", body: msg?.body || "Terminal task finished" });
298
+ note.on("click", () => { if (!win.isDestroyed()) {
299
+ win.show();
300
+ win.focus();
301
+ } });
302
+ note.show();
303
+ }
304
+ }
305
+ catch { /* notifications are best-effort */ }
306
+ try {
307
+ win.flashFrame(true);
308
+ }
309
+ catch { /* taskbar flash — Windows/Linux */ }
310
+ if (process.platform === "darwin" && app.dock) {
311
+ try {
312
+ app.dock.bounce("informational");
313
+ }
314
+ catch { /* best-effort */ }
315
+ }
316
+ });
230
317
  // Persisted global settings (locale, …) live in a JSON file under userData and reach the renderer
231
318
  // via preload + the two handlers below. The renderer's file:// localStorage is NOT reliably persisted
232
319
  // across app restarts, so settings that must survive a reopen round-trip through the main process.
@@ -306,6 +393,10 @@ app.on("window-all-closed", () => {
306
393
  });
307
394
  // Keep the Ignore-whitespace menu checkbox honest as focus moves between windows (it's per-window state).
308
395
  app.on("browser-window-focus", (_event, win) => {
396
+ try {
397
+ win.flashFrame(false);
398
+ }
399
+ catch { /* stop any terminal-bell taskbar flash once the user is back */ }
309
400
  const state = states.get(win.id);
310
401
  const item = Menu.getApplicationMenu()?.getMenuItemById("ignore-whitespace");
311
402
  if (item && state)
package/dist/assets.js CHANGED
@@ -26,7 +26,14 @@ export function diffCss() {
26
26
  return readViewerAsset("viewer.css");
27
27
  }
28
28
  export function diffScript() {
29
- return readViewerAsset("viewer.client.js");
29
+ // Prefer the minified bundle the build emits (smaller inlined <script>); fall back to the readable concat
30
+ // when minify was skipped (e.g. terser unavailable).
31
+ try {
32
+ return readViewerAsset("viewer.client.min.js");
33
+ }
34
+ catch {
35
+ return readViewerAsset("viewer.client.js");
36
+ }
30
37
  }
31
38
  // xterm.js (terminal renderer) for the integrated terminal panel. UMD bundles that expose
32
39
  // window.Terminal + window.FitAddon when inlined. Resolved from node_modules like diff2HtmlCss();
package/dist/build.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { basename } from "node:path";
3
- import { isGitRepository } from "./git.js";
3
+ import { isGitRepository, git } from "./git.js";
4
4
  import { collectHttpEnvironments, collectReviewFileStates, collectSourceFiles, parseUnifiedDiff, readUnifiedDiff } from "./diff.js";
5
5
  import { renderDiff2Html } from "./highlight.js";
6
6
  import { diffSubtitle, renderDiffHtml, renderDiffTree, renderNotGitRepoHtml, renderReviewStatus, renderSourceTree, shouldLazyRender, splitDiffForLazy } from "./render.js";
@@ -29,6 +29,8 @@ export function buildDiffReview(input) {
29
29
  const httpEnvironments = collectHttpEnvironments(root);
30
30
  const hunks = files.reduce((sum, file) => sum + file.hunks.length, 0);
31
31
  const generatedAt = new Date().toISOString();
32
+ // Current branch for the sidebar chip (empty on a detached HEAD); refreshed in the watch payload too.
33
+ const branch = git(root, ["branch", "--show-current"]);
32
34
  const diffHtml = renderDiff2Html(diffText);
33
35
  const totalLines = files.reduce((sum, file) => sum + file.hunks.reduce((t, h) => t + h.lines.length, 0), 0);
34
36
  // lazy-LOAD (Phase 2) serves each file body + source on demand instead of embedding them; it implies
@@ -61,6 +63,7 @@ export function buildDiffReview(input) {
61
63
  subtitle: diffSubtitle(input),
62
64
  projectName: basename(root),
63
65
  projectPath: root,
66
+ branch,
64
67
  watch: Boolean(input.watch),
65
68
  ignoreWhitespace: Boolean(input.ignoreWhitespace),
66
69
  app: Boolean(input.app),
@@ -72,6 +75,7 @@ export function buildDiffReview(input) {
72
75
  const update = {
73
76
  signature,
74
77
  generatedAt,
78
+ branch,
75
79
  diffContainer: diffSplit.container || '<div class="empty" data-i18n="diff.noDiff">No diff to review.</div>',
76
80
  changesPanel: renderDiffTree(files),
77
81
  filesTree: renderSourceTree(sourceFiles),
@@ -0,0 +1,23 @@
1
+ export type GitCommit = {
2
+ hash: string;
3
+ parents: string[];
4
+ author: string;
5
+ email: string;
6
+ date: string;
7
+ refs: string;
8
+ subject: string;
9
+ };
10
+ export declare function readGitLog(root: string, options?: {
11
+ limit?: number;
12
+ skip?: number;
13
+ }): GitCommit[];
14
+ export declare function readCommitDiff(root: string, sha: string): {
15
+ hash: string;
16
+ author: string;
17
+ email: string;
18
+ date: string;
19
+ refs: string;
20
+ message: string;
21
+ diffHtml: string;
22
+ isMerge: boolean;
23
+ } | null;
@@ -0,0 +1,60 @@
1
+ import { git } from "./git.js";
2
+ import { renderDiff2Html } from "./highlight.js";
3
+ // Field/record separators that can't occur in git output, so subjects with spaces/commas parse cleanly.
4
+ const FS = "\x1f";
5
+ const RS = "\x1e";
6
+ // Read up to `limit` commits (optionally skipping `skip`). Newest first — the order the graph walker expects.
7
+ export function readGitLog(root, options = {}) {
8
+ const limit = options.limit && options.limit > 0 ? options.limit : 200;
9
+ const args = [
10
+ "-c", "log.showSignature=false",
11
+ "log", "--no-color",
12
+ "--date=iso-strict",
13
+ `--pretty=format:%H${FS}%P${FS}%an${FS}%ae${FS}%ad${FS}%D${FS}%s${RS}`,
14
+ `-n`, String(limit),
15
+ ];
16
+ if (options.skip && options.skip > 0)
17
+ args.push(`--skip=${options.skip}`);
18
+ const out = git(root, args);
19
+ if (!out)
20
+ return [];
21
+ return out
22
+ .split(RS)
23
+ .map((rec) => rec.replace(/^\n/, ""))
24
+ .filter((rec) => rec.trim().length > 0)
25
+ .map((rec) => {
26
+ const f = rec.split(FS);
27
+ return {
28
+ hash: f[0] || "",
29
+ parents: (f[1] || "").trim() ? f[1].trim().split(/\s+/) : [],
30
+ author: f[2] || "",
31
+ email: f[3] || "",
32
+ date: f[4] || "",
33
+ refs: f[5] || "",
34
+ subject: f[6] || "",
35
+ };
36
+ })
37
+ .filter((c) => c.hash);
38
+ }
39
+ // Full detail for one commit: metadata, full message body, and the rendered diff (diff2html HTML).
40
+ // Merge commits show no diff under plain `git show`; the renderer notes that case.
41
+ export function readCommitDiff(root, sha) {
42
+ if (!sha || !/^[0-9a-fA-F]{4,64}$/.test(sha))
43
+ return null; // guard: only a hash reaches `git`
44
+ const meta = git(root, ["show", "-s", `--pretty=format:%H${FS}%an${FS}%ae${FS}%ad${FS}%D${FS}%P${FS}%B`, "--date=iso-strict", sha]);
45
+ if (!meta)
46
+ return null;
47
+ const f = meta.split(FS);
48
+ const parents = (f[5] || "").trim() ? f[5].trim().split(/\s+/) : [];
49
+ const diffText = git(root, ["show", sha, "--no-color", "--pretty=format:"]).replace(/^\n+/, "");
50
+ return {
51
+ hash: f[0] || sha,
52
+ author: f[1] || "",
53
+ email: f[2] || "",
54
+ date: f[3] || "",
55
+ refs: f[4] || "",
56
+ message: (f[6] || "").trim(),
57
+ diffHtml: renderDiff2Html(diffText),
58
+ isMerge: parents.length > 1,
59
+ };
60
+ }
package/dist/i18n.js CHANGED
@@ -14,19 +14,39 @@ export const MESSAGES = {
14
14
  // Tabs (sidebar)
15
15
  "tab.changes": "Changes",
16
16
  "tab.files": "Files",
17
+ "tab.changes.title": "Changes (⌘0)",
18
+ "tab.files.title": "Files (⌘1)",
19
+ "rail.questions": "Questions",
20
+ "rail.changeRequests": "Change requests",
21
+ "rail.branch": "Current branch",
22
+ "rail.history": "History",
23
+ "history.title": "History",
24
+ "history.search": "Filter by message or author",
25
+ "history.close": "Close",
26
+ "history.empty": "No commits.",
27
+ "history.loading": "Loading…",
28
+ "history.selectCommit": "Select a commit to view its changes.",
29
+ "history.merge": "Merge commit — no single-parent diff to show.",
30
+ "history.noDiff": "No changes in this commit.",
31
+ "goto.placeholder": "Go to line…",
32
+ "goto.copied": "Copied",
33
+ "menu.copyPath": "Copy path",
34
+ "menu.revealFinder": "Reveal in Finder",
35
+ "menu.openTerminal": "Open terminal here",
17
36
  // Sidebar footer / About
18
37
  "sidebar.updateAvailable": "update available",
19
38
  "about.title": "About monacori",
39
+ "about.tip": "Settings (⌘,)",
20
40
  "terminal.title": "Terminal",
21
- "terminal.toggle": "Toggle terminal (Ctrl+`)",
41
+ "terminal.toggle": "Toggle terminal (⌃`)",
22
42
  "terminal.close": "Close terminal",
23
- "dock.maximize": "Maximize panel (Cmd/Ctrl+Shift+')",
24
- "dock.restore": "Restore panel (Cmd/Ctrl+Shift+')",
43
+ "dock.maximize": "Maximize panel (⌘⇧')",
44
+ "dock.restore": "Restore panel (⌘⇧')",
25
45
  // Review status (toolbar) — units; the numeric count stays dynamic and is prepended at runtime.
26
46
  "status.files": "files",
27
47
  "status.hunks": "hunks",
28
48
  "status.wsIgnored": "ws ignored",
29
- "status.wsIgnored.title": "Whitespace ignored — Cmd/Ctrl+Shift+W",
49
+ "status.wsIgnored.title": "Whitespace ignored — ⌘⇧W",
30
50
  "status.indexed": "indexed",
31
51
  "status.index.title": "Go-to-definition index",
32
52
  "status.indexing": "indexing",
@@ -44,6 +64,7 @@ export const MESSAGES = {
44
64
  "http.env.title": "HTTP Client environment",
45
65
  "http.env.aria": "HTTP environment",
46
66
  "btn.diff": "Diff",
67
+ "btn.diff.title": "Back to diff (F7)",
47
68
  "source.loading": "Loading source…",
48
69
  "source.previewUnavailable": "Source preview unavailable.",
49
70
  "source.viewRaw": "Raw",
@@ -55,6 +76,7 @@ export const MESSAGES = {
55
76
  "quickopen.recent": "Recent files",
56
77
  "quickopen.findInFiles": "Find in Files",
57
78
  "quickopen.noFiles": "No files found.",
79
+ "quickopen.typeToFilter": "type to filter",
58
80
  // Usages
59
81
  "usages.aria": "Usages",
60
82
  "usages.title": "Usages",
@@ -66,6 +88,8 @@ export const MESSAGES = {
66
88
  // Settings — General
67
89
  "settings.language": "Language",
68
90
  "settings.theme": "Theme",
91
+ "settings.bellNotify": "Notify when a terminal task finishes (bell)",
92
+ "notify.bellBody": "a task finished or needs your input",
69
93
  "theme.dark": "Dark",
70
94
  "theme.light": "Light",
71
95
  "settings.checkingUpdates": "Checking for updates…",
@@ -76,10 +100,24 @@ export const MESSAGES = {
76
100
  "settings.updated": "Updated. Restarting…",
77
101
  "settings.updateFailed": "Update failed — try again, or run: npm i -g @happy-nut/monacori",
78
102
  "settings.kbd.title": "Keyboard shortcuts",
103
+ "settings.kbd.cat.app": "App",
79
104
  "settings.kbd.cat.nav": "Navigation",
80
105
  "settings.kbd.cat.review": "Review",
81
106
  "settings.kbd.cat.terminal": "Terminal",
82
107
  // Settings — keyboard-shortcut labels (descriptions only; <kbd> key names stay literal)
108
+ "kbd.gotoLine": "Go to line",
109
+ "kbd.copyLocation": "Copy file:line",
110
+ "kbd.rowActions": "Sidebar file actions (path / Finder / terminal)",
111
+ "kbd.openFolder": "Open folder",
112
+ "kbd.openNewWindow": "Open in new window",
113
+ "kbd.openSettings": "Settings",
114
+ "kbd.closeDialog": "Close dialog / cancel",
115
+ "kbd.pageUpDown": "Page up / down",
116
+ "kbd.runHttp": "Run HTTP request (.http)",
117
+ "kbd.editComment": "Edit comment (when selected)",
118
+ "kbd.deleteComment": "Delete comment (when selected)",
119
+ "kbd.stepComments": "Step between comments (merged)",
120
+ "kbd.mergedSend": "Comment menu / send to pane (merged)",
83
121
  "kbd.nextChange": "Next change",
84
122
  "kbd.prevChange": "Previous change",
85
123
  "kbd.closeTab": "Close tab",
@@ -109,7 +147,7 @@ export const MESSAGES = {
109
147
  "kbd.closeTerminal": "Close terminal (when focused)",
110
148
  // Settings — Merge prompts
111
149
  "mergePrompts.title": "Merge prompts",
112
- "mergePrompts.desc": "Heading prepended to the merged prompt opened with Cmd/Ctrl+Shift+/ (questions) and Cmd/Ctrl+Shift+. (change requests). Leave blank to use the default.",
150
+ "mergePrompts.desc": "Heading prepended to the merged prompt opened with ⌘⇧/ (questions) and ⌘⇧. (change requests). Leave blank to use the default.",
113
151
  "mergePrompts.qHeading": "Questions heading",
114
152
  "mergePrompts.cHeading": "Change-requests heading",
115
153
  "mergePrompts.reset": "Reset to defaults",
@@ -119,7 +157,7 @@ export const MESSAGES = {
119
157
  "composer.changeRequest": "Request a change for this line",
120
158
  "composer.save": "Comment",
121
159
  "composer.cancel": "Cancel",
122
- "composer.hint": "Cmd/Ctrl+Enter to save, Esc to cancel",
160
+ "composer.hint": "Enter to save, Esc to cancel",
123
161
  "composer.delete": "Delete",
124
162
  "comment.kind.q": "❓ Question",
125
163
  "comment.kind.c": "✎ Change request",
@@ -149,19 +187,39 @@ export const MESSAGES = {
149
187
  // Tabs (sidebar)
150
188
  "tab.changes": "변경사항",
151
189
  "tab.files": "파일",
190
+ "rail.questions": "질문",
191
+ "rail.changeRequests": "변경 요청",
192
+ "rail.branch": "현재 브랜치",
193
+ "rail.history": "히스토리",
194
+ "history.title": "히스토리",
195
+ "history.search": "메시지·작성자로 필터",
196
+ "history.close": "닫기",
197
+ "history.empty": "커밋이 없습니다.",
198
+ "history.loading": "불러오는 중…",
199
+ "history.selectCommit": "커밋을 선택하면 변경 내용이 표시됩니다.",
200
+ "history.merge": "머지 커밋 — 표시할 단일 부모 diff가 없습니다.",
201
+ "history.noDiff": "이 커밋에는 변경이 없습니다.",
202
+ "goto.placeholder": "이동할 줄 번호…",
203
+ "goto.copied": "복사함",
204
+ "menu.copyPath": "경로 복사",
205
+ "menu.revealFinder": "Finder에서 열기",
206
+ "menu.openTerminal": "여기서 터미널 열기",
207
+ "tab.changes.title": "변경사항 (⌘0)",
208
+ "tab.files.title": "파일 (⌘1)",
152
209
  // Sidebar footer / About
153
210
  "sidebar.updateAvailable": "업데이트 있음",
154
211
  "about.title": "monacori 정보",
212
+ "about.tip": "설정 (⌘,)",
155
213
  "terminal.title": "터미널",
156
- "terminal.toggle": "터미널 토글 (Ctrl+`)",
214
+ "terminal.toggle": "터미널 토글 (⌃`)",
157
215
  "terminal.close": "터미널 닫기",
158
- "dock.maximize": "패널 최대화 (Cmd/Ctrl+Shift+')",
159
- "dock.restore": "패널 복원 (Cmd/Ctrl+Shift+')",
216
+ "dock.maximize": "패널 최대화 (⌘⇧')",
217
+ "dock.restore": "패널 복원 (⌘⇧')",
160
218
  // Review status (toolbar)
161
219
  "status.files": "개 파일",
162
220
  "status.hunks": "개 변경 묶음",
163
221
  "status.wsIgnored": "공백 무시",
164
- "status.wsIgnored.title": "공백 무시 — Cmd/Ctrl+Shift+W",
222
+ "status.wsIgnored.title": "공백 무시 — ⌘⇧W",
165
223
  "status.indexed": "개 인덱싱됨",
166
224
  "status.index.title": "정의로 이동 인덱스",
167
225
  "status.indexing": "인덱싱 중",
@@ -179,6 +237,7 @@ export const MESSAGES = {
179
237
  "http.env.title": "HTTP 클라이언트 환경",
180
238
  "http.env.aria": "HTTP 환경",
181
239
  "btn.diff": "Diff",
240
+ "btn.diff.title": "Diff로 돌아가기 (F7)",
182
241
  "source.loading": "소스 불러오는 중…",
183
242
  "source.previewUnavailable": "소스 미리보기를 사용할 수 없습니다.",
184
243
  "source.viewRaw": "원문",
@@ -190,6 +249,7 @@ export const MESSAGES = {
190
249
  "quickopen.recent": "최근 파일",
191
250
  "quickopen.findInFiles": "파일 내용 검색",
192
251
  "quickopen.noFiles": "파일을 찾을 수 없습니다.",
252
+ "quickopen.typeToFilter": "입력하여 필터",
193
253
  // Usages
194
254
  "usages.aria": "사용처",
195
255
  "usages.title": "사용처",
@@ -201,6 +261,8 @@ export const MESSAGES = {
201
261
  // Settings — General
202
262
  "settings.language": "언어",
203
263
  "settings.theme": "테마",
264
+ "settings.bellNotify": "터미널 작업이 끝나면 알림 (벨)",
265
+ "notify.bellBody": "작업이 끝났거나 입력이 필요합니다",
204
266
  "theme.dark": "다크",
205
267
  "theme.light": "라이트",
206
268
  "settings.checkingUpdates": "업데이트 확인 중…",
@@ -211,10 +273,24 @@ export const MESSAGES = {
211
273
  "settings.updated": "업데이트 완료. 재시작 중…",
212
274
  "settings.updateFailed": "업데이트 실패 — 다시 시도하거나 실행하세요: npm i -g @happy-nut/monacori",
213
275
  "settings.kbd.title": "키보드 단축키",
276
+ "settings.kbd.cat.app": "앱",
214
277
  "settings.kbd.cat.nav": "탐색",
215
278
  "settings.kbd.cat.review": "리뷰",
216
279
  "settings.kbd.cat.terminal": "터미널",
217
280
  // Settings — keyboard-shortcut labels
281
+ "kbd.gotoLine": "줄로 이동",
282
+ "kbd.copyLocation": "파일:줄 복사",
283
+ "kbd.rowActions": "사이드바 파일 작업 (경로 / Finder / 터미널)",
284
+ "kbd.openFolder": "폴더 열기",
285
+ "kbd.openNewWindow": "새 창에서 열기",
286
+ "kbd.openSettings": "설정",
287
+ "kbd.closeDialog": "대화상자 닫기 / 취소",
288
+ "kbd.pageUpDown": "페이지 위 / 아래",
289
+ "kbd.runHttp": "HTTP 요청 실행 (.http)",
290
+ "kbd.editComment": "코멘트 편집 (선택 시)",
291
+ "kbd.deleteComment": "코멘트 삭제 (선택 시)",
292
+ "kbd.stepComments": "코멘트 단위 이동 (합본)",
293
+ "kbd.mergedSend": "코멘트 메뉴 / 패널로 전송 (합본)",
218
294
  "kbd.nextChange": "다음 변경",
219
295
  "kbd.prevChange": "이전 변경",
220
296
  "kbd.closeTab": "탭 닫기",
@@ -244,7 +320,7 @@ export const MESSAGES = {
244
320
  "kbd.closeTerminal": "터미널 닫기 (포커스 시)",
245
321
  // Settings — Merge prompts
246
322
  "mergePrompts.title": "병합 프롬프트",
247
- "mergePrompts.desc": "Cmd/Ctrl+Shift+/ (질문) 및 Cmd/Ctrl+Shift+. (변경요청)로 여는 병합 프롬프트 맨 앞에 붙는 머리말입니다. 비워 두면 기본값을 사용합니다.",
323
+ "mergePrompts.desc": "⌘⇧/ (질문) 및 ⌘⇧. (변경요청)로 여는 병합 프롬프트 맨 앞에 붙는 머리말입니다. 비워 두면 기본값을 사용합니다.",
248
324
  "mergePrompts.qHeading": "질문 머리말",
249
325
  "mergePrompts.cHeading": "변경요청 머리말",
250
326
  "mergePrompts.reset": "기본값으로 초기화",
@@ -254,7 +330,7 @@ export const MESSAGES = {
254
330
  "composer.changeRequest": "이 줄에 대한 변경 요청하기",
255
331
  "composer.save": "코멘트",
256
332
  "composer.cancel": "취소",
257
- "composer.hint": "Cmd/Ctrl+Enter로 저장, Esc로 취소",
333
+ "composer.hint": "Enter로 저장, Esc로 취소",
258
334
  "composer.delete": "삭제",
259
335
  "comment.kind.q": "❓ 질문",
260
336
  "comment.kind.c": "✎ 변경 요청",
package/dist/preload.cjs CHANGED
@@ -47,6 +47,11 @@ electron_1.contextBridge.exposeInMainWorld("monacoriFile", {
47
47
  get: (index, kind) => electron_1.ipcRenderer.invoke("monacori:get-file", { index, kind }),
48
48
  getSourceData: () => electron_1.ipcRenderer.invoke("monacori:get-source-data"),
49
49
  });
50
+ // Git history view (Cmd+9): list commits and fetch one commit's full diff for the current window's repo.
51
+ electron_1.contextBridge.exposeInMainWorld("monacoriGit", {
52
+ log: (request) => electron_1.ipcRenderer.invoke("monacori:git-log", request),
53
+ commitDiff: (sha) => electron_1.ipcRenderer.invoke("monacori:git-commit-diff", { sha }),
54
+ });
50
55
  // Self-update: ask the main process to install the latest version globally and relaunch. Only present
51
56
  // in the Electron app (not browser/watch mode), so the renderer hides the in-app update button there.
52
57
  electron_1.contextBridge.exposeInMainWorld("monacoriUpdate", {
@@ -58,6 +63,9 @@ electron_1.contextBridge.exposeInMainWorld("monacoriApp", {
58
63
  openFolder: () => electron_1.ipcRenderer.invoke("monacori:open-folder"),
59
64
  // Welcome screen's Recent Projects: open a remembered repo path in the current window.
60
65
  openRecent: (path) => electron_1.ipcRenderer.invoke("monacori:open-recent", { path }),
66
+ // Sidebar Opt+Enter menu: reveal a file in Finder, or open a terminal at its directory.
67
+ revealInFinder: (path) => electron_1.ipcRenderer.invoke("monacori:reveal-in-finder", { path }),
68
+ openTerminalAt: (path) => electron_1.ipcRenderer.invoke("monacori:open-terminal-at", { path }),
61
69
  });
62
70
  // Integrated terminal: bridge the renderer's xterm view to a node-pty owned by the main process (the
63
71
  // sandboxed renderer can't spawn a pty). Only present in the Electron app; browser/serve mode lacks it,
@@ -67,6 +75,9 @@ electron_1.contextBridge.exposeInMainWorld("monacoriPty", {
67
75
  write: (msg) => electron_1.ipcRenderer.send("monacori:pty-write", msg),
68
76
  resize: (msg) => electron_1.ipcRenderer.send("monacori:pty-resize", msg),
69
77
  kill: (msg) => electron_1.ipcRenderer.send("monacori:pty-kill", msg),
78
+ // A TUI in the pane rang the terminal bell (e.g. Claude Code finished a turn / needs input). The renderer
79
+ // passes a pre-localized title+body; the main process decides whether to raise a native notification.
80
+ bell: (msg) => electron_1.ipcRenderer.send("monacori:bell", msg),
70
81
  onData: (cb) => {
71
82
  electron_1.ipcRenderer.on("monacori:pty-data", (_event, msg) => cb(msg));
72
83
  },
@@ -74,6 +85,11 @@ electron_1.contextBridge.exposeInMainWorld("monacoriPty", {
74
85
  electron_1.ipcRenderer.on("monacori:pty-exit", (_event, msg) => cb(msg));
75
86
  },
76
87
  });
88
+ // Clipboard bridge — the integrated terminal copies its own selection on Cmd+C (xterm doesn't auto-copy, and
89
+ // the sandboxed renderer's navigator.clipboard is unreliable on file://). Electron's clipboard always works here.
90
+ electron_1.contextBridge.exposeInMainWorld("monacoriClipboard", {
91
+ write: (text) => electron_1.clipboard.writeText(typeof text === "string" ? text : String(text)),
92
+ });
77
93
  // Global settings (locale, …) persisted by the main process under userData so they survive app
78
94
  // restarts — the renderer's file:// localStorage is not reliably persisted across reopens. `all` is
79
95
  // read synchronously at preload so the renderer can pick the locale before first paint; `set` writes
package/dist/render.d.ts CHANGED
@@ -32,6 +32,7 @@ export declare function renderDiffHtml(input: {
32
32
  subtitle: string;
33
33
  projectName: string;
34
34
  projectPath: string;
35
+ branch?: string;
35
36
  watch?: boolean;
36
37
  ignoreWhitespace?: boolean;
37
38
  app?: boolean;