@happy-nut/monacori 0.1.25 → 0.1.26

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/viewer.css CHANGED
@@ -503,8 +503,9 @@ td.d2h-del:not(.d2h-code-side-linenumber) { color: #d8e0e8; }
503
503
  .d2h-code-line-ctn del { background: var(--del-strong); }
504
504
  .d2h-code-side-linenumber.d2h-del, .d2h-code-linenumber.d2h-del { background: var(--del); }
505
505
  .d2h-code-side-linenumber.d2h-ins, .d2h-code-linenumber.d2h-ins { background: var(--add); }
506
- .d2h-diff-table tr.hunk, .d2h-diff-table tr.hunk-peer { scroll-margin-top: 76px; }
507
- .d2h-diff-table tr.hunk.active td, .d2h-diff-table tr.hunk-peer.active td {
506
+ .d2h-diff-table tr.hunk, .d2h-diff-table tr.hunk-peer, .d2h-diff-table tr.history-hunk, .d2h-diff-table tr.history-hunk-peer { scroll-margin-top: 76px; }
507
+ .d2h-diff-table tr.hunk.active td, .d2h-diff-table tr.hunk-peer.active td,
508
+ .d2h-diff-table tr.history-hunk.active td, .d2h-diff-table tr.history-hunk-peer.active td {
508
509
  box-shadow: none;
509
510
  }
510
511
  .d2h-diff-table tr.diff-active-row td { background: rgba(74, 136, 199, 0.16) !important; }
@@ -903,7 +904,8 @@ h1 { margin: 0; font-size: 18px; }
903
904
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28);
904
905
  overflow: hidden;
905
906
  }
906
- /* Question vs change-request is a small colored pill on the kind label no heavy left bar. */
907
+ /* Question vs change-request is a small monochrome pill: an icon (help-circle / pencil) carries the
908
+ distinction — no color, no heavy left bar. */
907
909
  .mc-card-head { display: flex; align-items: center; gap: 8px; padding: 8px 10px 6px; color: var(--muted); }
908
910
  .mc-target { margin-left: auto; font: 11px Monaco, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: var(--muted); opacity: 0.9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 62%; }
909
911
  /* While composing a comment, the textarea owns the only caret — hide the file's inline caret and the
@@ -920,12 +922,12 @@ body.mc-composing .source-row.cursor-line .num { color: inherit; }
920
922
  body:has(.dock-panel:focus-within) .code-cursor,
921
923
  body:has(#settings-modal:not(.hidden)) .code-cursor { display: none; }
922
924
  .mc-kind {
925
+ display: inline-flex; align-items: center; gap: 5px;
923
926
  font-weight: 700; font-size: 10px; letter-spacing: 0.05em; text-transform: uppercase;
924
927
  padding: 2px 8px; border-radius: 0;
925
928
  color: var(--muted); background: color-mix(in srgb, var(--muted) 16%, transparent);
926
929
  }
927
- .mc-card.mc-q .mc-kind { color: var(--token-number); background: color-mix(in srgb, var(--token-number) 18%, transparent); }
928
- .mc-card.mc-c .mc-kind { color: var(--token-tag); background: color-mix(in srgb, var(--token-tag) 18%, transparent); }
930
+ .mc-kind-ic { flex: none; }
929
931
  .mc-del { margin-left: auto; background: transparent; border: 0; color: var(--muted); cursor: pointer; font-size: 15px; line-height: 1; padding: 1px 5px; border-radius: 0; }
930
932
  .mc-del:hover { color: var(--del-strong); background: color-mix(in srgb, var(--del-strong) 16%, transparent); }
931
933
  .mc-card-body { padding: 2px 12px 11px; color: var(--text); white-space: pre-wrap; overflow-wrap: anywhere; }
@@ -947,7 +949,7 @@ body:has(#settings-modal:not(.hidden)) .code-cursor { display: none; }
947
949
  .mc-btn:hover { filter: brightness(1.1); }
948
950
  .mc-btn.mc-ghost { background: transparent; border: 1px solid var(--border); color: var(--text); }
949
951
 
950
- /* ===== Git history view (Cmd+9): full-screen overlay, commit list (graph lanes) | commit detail+diff. ===== */
952
+ /* ===== Git history view (Cmd+9): full-screen overlay, commit list (graph lanes) | changed files + single-file diff. ===== */
951
953
  .history-view { position: fixed; inset: 0; z-index: 75; display: flex; flex-direction: column; background: var(--bg); }
952
954
  .history-view.hidden { display: none; }
953
955
  .history-bar { flex: none; display: flex; align-items: center; gap: 12px; padding: 8px 12px; background: var(--sidebar); border-bottom: 1px solid var(--border); }
@@ -956,8 +958,8 @@ body:has(#settings-modal:not(.hidden)) .code-cursor { display: none; }
956
958
  .history-search:focus { border-color: var(--active); }
957
959
  .history-bar .dock-btn { margin-left: auto; font-size: 16px; }
958
960
  .history-body { flex: 1 1 auto; min-height: 0; display: flex; }
959
- .history-list { flex: 1.3 1 0; min-width: 0; overflow: auto; padding: 4px 0; border-right: 1px solid var(--border); }
960
- .history-detail { flex: 1 1 0; min-width: 0; overflow: auto; }
961
+ .history-list { flex: 0 0 min(46vw, 760px); min-width: 320px; overflow: auto; padding: 4px 0; border-right: 1px solid var(--border); }
962
+ .history-detail { flex: 1 1 auto; min-width: 0; overflow: hidden; display: flex; flex-direction: column; }
961
963
  /* Rows are a fixed 24px so the per-row graph SVG (also 24px) tiles seamlessly and the lanes connect. */
962
964
  .hrow { display: grid; grid-template-columns: var(--hgraph-w, 60px) minmax(0, 1fr) auto auto; align-items: center; gap: 10px; width: 100%; height: 24px; padding: 0 12px 0 0; border: 0; background: transparent; color: var(--text); text-align: left; cursor: pointer; font-size: 12.5px; }
963
965
  .hrow:hover { background: color-mix(in srgb, var(--active) 8%, transparent); }
@@ -975,10 +977,15 @@ body:has(#settings-modal:not(.hidden)) .code-cursor { display: none; }
975
977
  .href-branch { color: #7faf6b; border-color: #7faf6b; }
976
978
  .href-remote { color: var(--muted); }
977
979
  .href-tag { color: #d4a857; border-color: #d4a857; }
978
- .history-detail-head { padding: 12px 14px; border-bottom: 1px solid var(--border); }
980
+ .history-detail-head { flex: none; padding: 12px 14px; border-bottom: 1px solid var(--border); }
979
981
  .hd-msg { font-size: 13px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; margin-bottom: 8px; }
980
982
  .hd-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; font-size: 12px; color: var(--muted); }
981
983
  .hd-hash { font-family: Monaco, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
984
+ .history-workspace { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(190px, 260px) minmax(0, 1fr); }
985
+ .history-files { min-width: 0; overflow: auto; padding: 6px; border-right: 1px solid var(--border); background: var(--sidebar); }
986
+ .history-file { width: 100%; }
987
+ .history-diff { min-width: 0; min-height: 0; overflow: auto; display: flex; flex-direction: column; }
988
+ .history-diff:focus { outline: none; }
982
989
 
983
990
  /* Go-to-line (Cmd/Ctrl+L): a small numeric prompt near the top, like a command palette. */
984
991
  .goto-line {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happy-nut/monacori",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "Validation control plane for AI-generated code changes.",
5
5
  "type": "module",
6
6
  "main": "dist/app-main.js",
@@ -1,16 +1,22 @@
1
- import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
1
+ import { existsSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { createRequire } from "node:module";
4
4
  import { dirname, join } from "node:path";
5
5
 
6
- const APP_NAME = "monacori";
6
+ const APP_DISPLAY_NAME = "Monacori";
7
+ const APP_EXECUTABLE_NAME = "monacori";
8
+ const APP_BUNDLE_ID = "dev.happynut.monacori";
9
+ const OLD_APP_NAME = "Electron";
10
+ const APP_BUNDLE = APP_DISPLAY_NAME + ".app";
11
+ const LEGACY_APP_BUNDLES = [OLD_APP_NAME + ".app", APP_EXECUTABLE_NAME + ".app"];
7
12
 
8
13
  // Electron ships Electron.app with bundle name + executable "Electron", which is what macOS shows in
9
14
  // the Dock / Cmd+Tab. The npm `mo` model spawns node_modules/electron's executable directly (not a
10
15
  // packaged .app), and a directly-spawned GUI process takes its switcher/Dock name from the *executable*
11
- // name CFBundleName and app.setName() only affect the menu items, not the switcher. So we rename the
12
- // executable to "monacori" (and repoint electron's path.txt) in addition to patching bundle metadata.
16
+ // name and LaunchServices cache identity. So we rename Electron.app + its executable, patch the bundle
17
+ // id/name metadata, and repoint electron's path.txt at the branded binary.
13
18
  function electronRoot() {
19
+ if (process.env.MONACORI_ELECTRON_ROOT) return process.env.MONACORI_ELECTRON_ROOT;
14
20
  if (process.platform !== "darwin") return null;
15
21
  const require = createRequire(import.meta.url);
16
22
  try {
@@ -20,54 +26,98 @@ function electronRoot() {
20
26
  }
21
27
  }
22
28
 
29
+ function setPlistString(plist, key, value) {
30
+ const pattern = new RegExp("(<key>" + key + "</key>\\s*<string>)[^<]*(</string>)");
31
+ if (pattern.test(plist)) {
32
+ return plist.replace(pattern, (_match, prefix, suffix) => prefix + value + suffix);
33
+ }
34
+ return plist.replace("</dict>", "\t<key>" + key + "</key>\n\t<string>" + value + "</string>\n</dict>");
35
+ }
36
+
37
+ function registerBundle(appDir) {
38
+ if (process.env.MONACORI_SKIP_LSREGISTER === "1") return;
39
+ spawnSync(
40
+ "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister",
41
+ ["-f", appDir],
42
+ { stdio: "ignore" },
43
+ );
44
+ }
45
+
46
+ function findAppBundleEntry(distDir) {
47
+ try {
48
+ const entries = readdirSync(distDir);
49
+ return [APP_BUNDLE, ...LEGACY_APP_BUNDLES].find((bundle) => entries.includes(bundle)) ?? null;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
23
55
  function main() {
24
56
  const root = electronRoot();
25
57
  if (!root) return; // not macOS, or electron missing — nothing to do
26
- const appDir = join(root, "dist", "Electron.app");
27
- const plistPath = join(appDir, "Contents", "Info.plist");
28
- const macosDir = join(appDir, "Contents", "MacOS");
29
- const oldExe = join(macosDir, "Electron");
30
- const newExe = join(macosDir, APP_NAME);
58
+ const distDir = join(root, "dist");
59
+ const newAppDir = join(root, "dist", APP_BUNDLE);
60
+ const appBundleEntry = findAppBundleEntry(distDir);
31
61
  const pathTxt = join(root, "path.txt");
32
- if (!existsSync(plistPath)) {
33
- console.warn('monacori: Electron.app not found at ' + appDir + ' — skipping rebrand (Dock/menu may show "Electron")');
62
+ if (!appBundleEntry) {
63
+ console.warn('monacori: Electron.app not found under ' + distDir + ' — skipping rebrand (Dock/menu may show "Electron")');
34
64
  return;
35
65
  }
36
66
 
37
67
  try {
38
68
  let changed = false;
39
- // 1. Bundle metadata: name, display name, AND executable -> monacori.
69
+ let appDir = join(distDir, appBundleEntry);
70
+
71
+ // 1. Rename the app bundle directory itself. Leaving Electron.app on disk can keep LaunchServices keyed
72
+ // to Electron even after CFBundleName is patched. The temp hop makes case-only renames reliable on
73
+ // macOS's common case-insensitive volumes (monacori.app -> Monacori.app).
74
+ if (appBundleEntry !== APP_BUNDLE) {
75
+ const tmpAppDir = join(distDir, ".monacori-app-rename-" + process.pid + "-" + Date.now());
76
+ renameSync(appDir, tmpAppDir);
77
+ renameSync(tmpAppDir, newAppDir);
78
+ appDir = newAppDir;
79
+ changed = true;
80
+ }
81
+
82
+ const plistPath = join(appDir, "Contents", "Info.plist");
83
+ const macosDir = join(appDir, "Contents", "MacOS");
84
+ const oldExe = join(macosDir, OLD_APP_NAME);
85
+ const newExe = join(macosDir, APP_EXECUTABLE_NAME);
86
+ if (!existsSync(plistPath)) {
87
+ console.warn('monacori: Info.plist not found at ' + plistPath + ' — skipping rebrand (Dock/menu may show "Electron")');
88
+ return;
89
+ }
90
+
91
+ // 2. Bundle metadata: display name -> Monacori, executable -> monacori, bundle id -> monacori.
40
92
  const before = readFileSync(plistPath, "utf8");
41
- const after = before
42
- .replace(/(<key>CFBundleName<\/key>\s*<string>)[^<]*(<\/string>)/, "$1" + APP_NAME + "$2")
43
- .replace(/(<key>CFBundleDisplayName<\/key>\s*<string>)[^<]*(<\/string>)/, "$1" + APP_NAME + "$2")
44
- .replace(/(<key>CFBundleExecutable<\/key>\s*<string>)[^<]*(<\/string>)/, "$1" + APP_NAME + "$2");
93
+ const after = [
94
+ ["CFBundleName", APP_DISPLAY_NAME],
95
+ ["CFBundleDisplayName", APP_DISPLAY_NAME],
96
+ ["CFBundleExecutable", APP_EXECUTABLE_NAME],
97
+ ["CFBundleIdentifier", APP_BUNDLE_ID],
98
+ ].reduce((plist, [key, value]) => setPlistString(plist, key, value), before);
45
99
  if (after !== before) { writeFileSync(plistPath, after); changed = true; }
46
100
 
47
- // 2. Rename the executable so the directly-spawned process is "monacori" (idempotent).
101
+ // 3. Rename the executable so the directly-spawned process is "monacori" (idempotent).
48
102
  if (existsSync(oldExe) && !existsSync(newExe)) { renameSync(oldExe, newExe); changed = true; }
49
103
 
50
- // 3. Repoint electron's path.txt at the renamed binary so require("electron") resolves it.
104
+ // 4. Repoint electron's path.txt at the renamed binary so require("electron") resolves it.
51
105
  if (existsSync(pathTxt)) {
52
106
  const pt = readFileSync(pathTxt, "utf8");
53
- const fixed = pt.replace("MacOS/Electron", "MacOS/" + APP_NAME);
107
+ const fixed = APP_BUNDLE + "/Contents/MacOS/" + APP_EXECUTABLE_NAME;
54
108
  if (fixed !== pt) { writeFileSync(pathTxt, fixed); changed = true; }
55
109
  }
56
110
 
57
111
  // Only when something actually changed: refresh LaunchServices so the Dock / Cmd+Tab show "monacori"
58
112
  // instead of a cached "Electron". Skipping it when already-branded keeps the startup re-run cheap.
59
113
  if (changed) {
60
- spawnSync(
61
- "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister",
62
- ["-f", appDir],
63
- { stdio: "ignore" },
64
- );
65
- console.log('monacori: branded Electron app + executable as "' + APP_NAME + '"');
114
+ registerBundle(appDir);
115
+ console.log('monacori: branded Electron app as "' + APP_DISPLAY_NAME + '"');
66
116
  }
67
117
  } catch (e) {
68
118
  // Surface the reason (perms / read-only) instead of failing SILENTLY — otherwise the Dock/Cmd+Tab/menu
69
119
  // keep showing "Electron" with no hint why. Non-fatal: app-main.ts re-runs this at startup.
70
- console.warn('monacori: could not rebrand the Electron app to "' + APP_NAME + '". Dock/Cmd+Tab/menu may stay "Electron". Reason: ' + (e && e.message ? e.message : e));
120
+ console.warn('monacori: could not rebrand the Electron app to "' + APP_DISPLAY_NAME + '". Dock/Cmd+Tab/menu may stay "Electron". Reason: ' + (e && e.message ? e.message : e));
71
121
  }
72
122
  }
73
123