@happy-nut/monacori 0.1.19 → 0.1.21
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/assets/icon.icns +0 -0
- package/dist/app-main.js +403 -155
- package/dist/build.d.ts +1 -0
- package/dist/build.js +8 -6
- package/dist/diff.d.ts +2 -1
- package/dist/diff.js +3 -3
- package/dist/i18n.js +8 -2
- package/dist/preload.cjs +7 -0
- package/dist/render.d.ts +4 -0
- package/dist/render.js +84 -0
- package/dist/viewer.client.js +489 -192
- package/dist/viewer.css +65 -13
- package/package.json +9 -2
- package/scripts/patch-electron-name.mjs +23 -14
package/dist/viewer.css
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
:root {
|
|
3
3
|
color-scheme: dark;
|
|
4
|
-
--bg: #
|
|
5
|
-
--panel: #
|
|
4
|
+
--bg: #1e1e1e;
|
|
5
|
+
--panel: #252526;
|
|
6
6
|
--text: #a9b7c6;
|
|
7
7
|
--muted: #808080;
|
|
8
8
|
--border: #393b3d;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
--add-strong: #1f5b34;
|
|
13
13
|
--del-strong: #6e2c2c;
|
|
14
14
|
--active: #4a88c7;
|
|
15
|
-
--sidebar: #
|
|
15
|
+
--sidebar: #2a2a2b;
|
|
16
16
|
--token-comment: #808080;
|
|
17
17
|
--token-keyword: #cc7832;
|
|
18
18
|
--token-string: #6a8759;
|
|
@@ -294,6 +294,10 @@ body {
|
|
|
294
294
|
border: 0;
|
|
295
295
|
border-radius: 0;
|
|
296
296
|
background: var(--panel);
|
|
297
|
+
/* #diff2html-container is a column flexbox; a flex item with overflow:hidden has min-height:0, so a single
|
|
298
|
+
shown file (showOnlyFile) shrinks to the viewport height and overflow:hidden clips the rest — the diff
|
|
299
|
+
can't scroll and the caret leaves the screen near the bottom of a big file. Pin to content height. */
|
|
300
|
+
flex-shrink: 0;
|
|
297
301
|
}
|
|
298
302
|
/* The per-file header is merged into the sticky toolbar (path + status + Viewed) to save vertical space. */
|
|
299
303
|
.d2h-file-header { display: none; }
|
|
@@ -370,6 +374,7 @@ body {
|
|
|
370
374
|
background: transparent;
|
|
371
375
|
border: 0;
|
|
372
376
|
}
|
|
377
|
+
.d2h-code-line-ctn { position: relative; } /* anchors the absolutely-positioned empty-line caret (so blank rows need no inline position) */
|
|
373
378
|
.d2h-code-side-line, .d2h-code-line {
|
|
374
379
|
/* left pad must exceed the 58px absolutely-positioned line-number, else the +/- prefix renders behind it and looks clipped */
|
|
375
380
|
padding: 0 0.6em 0 64px;
|
|
@@ -519,8 +524,11 @@ td.d2h-del:not(.d2h-code-side-linenumber) { color: #d8e0e8; }
|
|
|
519
524
|
.tree-dir[open] > summary .fi-closed { display: none; }
|
|
520
525
|
.tree-dir[open] > summary .fi-open { display: block; }
|
|
521
526
|
.file-link.tree-file { padding-left: calc(8px + (var(--depth) * 14px)); }
|
|
522
|
-
|
|
523
|
-
|
|
527
|
+
/* Arrow-key focus in the tree (changes/files) matches the active-file highlight — a tinted fill + accent
|
|
528
|
+
border — so navigating the file tree reads the same as the diff-side selection (not a faint 1px outline). */
|
|
529
|
+
.tree-focus { background: color-mix(in srgb, var(--active) 20%, transparent); border-radius: 6px; }
|
|
530
|
+
.file-link.tree-focus { border-color: var(--active); }
|
|
531
|
+
summary.tree-focus { background: color-mix(in srgb, var(--active) 20%, transparent); }
|
|
524
532
|
.file-link {
|
|
525
533
|
display: grid;
|
|
526
534
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
@@ -538,7 +546,10 @@ summary.tree-focus { background: var(--bg); }
|
|
|
538
546
|
font: inherit;
|
|
539
547
|
cursor: pointer;
|
|
540
548
|
}
|
|
541
|
-
.file-link:hover
|
|
549
|
+
.file-link:hover { background: color-mix(in srgb, var(--text) 7%, transparent); border-color: var(--border); }
|
|
550
|
+
/* The file being viewed stands out clearly: a tinted fill + accent border, not just a faint outline. */
|
|
551
|
+
.file-link.active { background: color-mix(in srgb, var(--active) 26%, transparent); border-color: var(--active); }
|
|
552
|
+
.file-link.active:hover { background: color-mix(in srgb, var(--active) 32%, transparent); }
|
|
542
553
|
.file-link.viewed { opacity: 0.58; }
|
|
543
554
|
.file-link.viewed:hover, .file-link.viewed.active { opacity: 1; }
|
|
544
555
|
.path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
|
|
@@ -689,11 +700,6 @@ h1 { margin: 0; font-size: 18px; }
|
|
|
689
700
|
key scrolls the view CONTINUOUSLY instead of leaving it still until the caret reaches the viewport edge
|
|
690
701
|
(the "stutter every ~viewport" the user reported). Applies to the source body, the diff, and the sidebar. */
|
|
691
702
|
.source-body, #diff2html-container, .sidebar-scroll { scroll-padding-block: 35vh; }
|
|
692
|
-
/* revealAt() sets scrollTop directly, and scroll-padding only affects scrollIntoView — so near EOF the
|
|
693
|
-
caret can't reach the 42% line and pins to the viewport bottom, where the footer progress bar overlaps
|
|
694
|
-
and HIDES it ("the caret leaves the screen at the end of a file"). Real trailing space lets the last
|
|
695
|
-
lines scroll up to the middle. The diff caret showed this worst (95% vs source's 81%); both get it. */
|
|
696
|
-
.source-body, #diff2html-container { padding-bottom: 45vh; }
|
|
697
703
|
.source-body {
|
|
698
704
|
border: 1px solid var(--border);
|
|
699
705
|
overflow: auto;
|
|
@@ -750,8 +756,11 @@ h1 { margin: 0; font-size: 18px; }
|
|
|
750
756
|
}
|
|
751
757
|
/* perf: let the browser skip layout/paint for off-screen rows in large files/diffs.
|
|
752
758
|
DOM is unchanged (nav, search, comment anchoring still query every row); degrades
|
|
753
|
-
gracefully where unsupported. contain-intrinsic-size keeps the scrollbar stable.
|
|
754
|
-
.source-row
|
|
759
|
+
gracefully where unsupported. contain-intrinsic-size keeps the scrollbar stable.
|
|
760
|
+
(Removing this from .source-row made the caret stutter WORSE — the full DOM forces a bigger reflow on
|
|
761
|
+
every scroll. The stutter is fixed instead by computing the source scroll from lineIndex*rowHeight,
|
|
762
|
+
which skips getBoundingClientRect's forced reflow entirely; see scheduleSourceReveal.) */
|
|
763
|
+
.source-row { content-visibility: auto; contain-intrinsic-size: auto 21px; }
|
|
755
764
|
.d2h-diff-table tr { content-visibility: auto; contain-intrinsic-size: auto 18px; }
|
|
756
765
|
/* Comment/composer rows are tall and interactive (a textarea lives here). Skip-rendering them
|
|
757
766
|
with a tiny 18px placeholder made the browser re-evaluate their render state on every
|
|
@@ -834,6 +843,12 @@ body.mc-composing .mc-diff-cursor-row .d2h-code-side-line { box-shadow: none; }
|
|
|
834
843
|
body.mc-composing .source-row.cursor-line .md-cell,
|
|
835
844
|
body.mc-composing .source-row.csv-row.cursor-line .csv-cell { background: transparent; }
|
|
836
845
|
body.mc-composing .source-row.cursor-line .num { color: inherit; }
|
|
846
|
+
/* Same single-caret rule for a floating overlay (merged comments / prompt memo / settings): it owns the only
|
|
847
|
+
caret while open, so hide the file's blinking caret behind it — never two carets across visible panels. */
|
|
848
|
+
/* A focused merged/memo dock owns the only caret: hide the file caret behind it while it has focus
|
|
849
|
+
(single-caret rule). Focus elsewhere with the dock still open -> the file caret returns. */
|
|
850
|
+
body:has(.dock-panel:focus-within) .code-cursor,
|
|
851
|
+
body:has(#settings-modal:not(.hidden)) .code-cursor { display: none; }
|
|
837
852
|
.mc-kind {
|
|
838
853
|
font-weight: 700; font-size: 10px; letter-spacing: 0.05em; text-transform: uppercase;
|
|
839
854
|
padding: 2px 8px; border-radius: 999px;
|
|
@@ -848,6 +863,10 @@ body.mc-composing .source-row.cursor-line .num { color: inherit; }
|
|
|
848
863
|
display: block; box-sizing: border-box; resize: vertical;
|
|
849
864
|
margin: 0 10px 10px; width: calc(100% - 20px); min-height: 60px;
|
|
850
865
|
background: var(--bg); color: var(--text);
|
|
866
|
+
/* The composer is injected INSIDE #diff2html-container, which sets caret-color: transparent (the file
|
|
867
|
+
view uses a fake .code-cursor). caret-color inherits, so restore it here — a focused textarea must show
|
|
868
|
+
its real caret. The single-caret rule hides the FILE caret while composing, not the textarea's own. */
|
|
869
|
+
caret-color: var(--text);
|
|
851
870
|
border: 1px solid var(--border); border-radius: 7px; padding: 9px 11px;
|
|
852
871
|
font: 12px/1.55 Monaco, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
853
872
|
transition: border-color 120ms ease, box-shadow 120ms ease;
|
|
@@ -882,6 +901,38 @@ body.mc-composing .source-row.cursor-line .num { color: inherit; }
|
|
|
882
901
|
.mc-memo-preview { height: 100%; overflow: auto; padding: 12px 16px; background: var(--panel); color: var(--text); }
|
|
883
902
|
.mc-memo-preview > :first-child { margin-top: 0; }
|
|
884
903
|
.mc-memo-empty { color: var(--muted); font-size: 12px; font-style: italic; }
|
|
904
|
+
|
|
905
|
+
/* ===== Merged-prompt & memo: a large FLOATING overlay (~90% of the window), sharing ONE slot with the
|
|
906
|
+
terminal (opening one closes the others). Cmd/Ctrl+Shift+' maximizes the active panel to full screen. =====
|
|
907
|
+
(These used to dock below the editor; they now float so a big writing surface doesn't shrink the diff.) */
|
|
908
|
+
.dock-panel {
|
|
909
|
+
position: fixed; inset: 5vh 5vw; z-index: 60; min-height: 0; /* ~90vw x 90vh, centered */
|
|
910
|
+
display: flex; flex-direction: column; background: var(--panel);
|
|
911
|
+
border: 1px solid var(--border); border-radius: 12px;
|
|
912
|
+
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.55);
|
|
913
|
+
overflow: hidden;
|
|
914
|
+
}
|
|
915
|
+
/* Dim the editor behind the floating panel; clicking it closes (handler wired in mountDock). */
|
|
916
|
+
.dock-backdrop { position: fixed; inset: 0; z-index: 59; background: color-mix(in srgb, #000 40%, transparent); }
|
|
917
|
+
.dock-resizer { display: none; } /* a centered ~90% floating panel has no bottom edge to drag */
|
|
918
|
+
.dock-bar {
|
|
919
|
+
flex: none; display: flex; align-items: center; gap: 8px; padding: 6px 10px;
|
|
920
|
+
background: var(--sidebar); border-bottom: 1px solid var(--border);
|
|
921
|
+
color: var(--text); font-weight: 650; font-size: 12px;
|
|
922
|
+
}
|
|
923
|
+
.dock-title { margin-right: auto; letter-spacing: 0.02em; }
|
|
924
|
+
.dock-btn { border: 0; background: transparent; color: var(--muted); cursor: pointer; padding: 2px 7px; border-radius: 5px; font: inherit; font-weight: 500; }
|
|
925
|
+
.dock-btn:hover { color: var(--text); background: color-mix(in srgb, var(--muted) 22%, transparent); }
|
|
926
|
+
.dock-max { font-size: 14px; line-height: 1; }
|
|
927
|
+
.dock-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
|
|
928
|
+
.dock-body > .mc-modal-text { flex: 1 1 auto; height: auto; }
|
|
929
|
+
.dock-body > .mc-memo-body { flex: 1 1 auto; height: auto; }
|
|
930
|
+
/* Maximize. The floating merged/memo panel fills the whole viewport. The terminal is still a grid dock, so
|
|
931
|
+
its maximize hides the editor area (next rule) — but the floating panels do NOT hide content (their overlay
|
|
932
|
+
already covers it, and hiding the diff would force an expensive reflow on every maximize toggle). */
|
|
933
|
+
body.dock-maximized .dock-panel { inset: 0; border-radius: 0; }
|
|
934
|
+
body.dock-maximized:not(.floating-dock) .content { display: none; }
|
|
935
|
+
body.dock-maximized .terminal-panel:not(.hidden) { grid-row: 1 / 3; height: auto; }
|
|
885
936
|
.tok-comment { color: var(--token-comment); font-style: italic; }
|
|
886
937
|
.tok-keyword { color: var(--token-keyword); font-weight: 650; }
|
|
887
938
|
.tok-string { color: var(--token-string); }
|
|
@@ -996,6 +1047,7 @@ body.mc-composing .source-row.cursor-line .num { color: inherit; }
|
|
|
996
1047
|
.sidebar { grid-row: 1; grid-column: 1; height: auto; border-right: 0; border-bottom: 1px solid var(--border); }
|
|
997
1048
|
.content { grid-row: 2; grid-column: 1; padding: 16px; }
|
|
998
1049
|
.terminal-panel { grid-column: 1; grid-row: 3; }
|
|
1050
|
+
.dock-panel { grid-column: 1; grid-row: 3; }
|
|
999
1051
|
.toolbar { margin: -16px -16px 16px; padding: 12px 16px; }
|
|
1000
1052
|
.d2h-files-diff { grid-template-columns: 1fr; }
|
|
1001
1053
|
.d2h-file-side-diff:first-child { border-right: 0; border-bottom: 1px solid var(--border); }
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happy-nut/monacori",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "Validation control plane for AI-generated code changes.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"main": "dist/app-main.js",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
9
|
"url": "git+https://github.com/happy-nut/monacori.git"
|
|
@@ -35,7 +36,9 @@
|
|
|
35
36
|
"prepare": "npm run build",
|
|
36
37
|
"smoke": "npm run build && node dist/cli.js --help",
|
|
37
38
|
"pretest": "npm run build",
|
|
38
|
-
"test": "node --test test/*.test.mjs"
|
|
39
|
+
"test": "node --test test/*.test.mjs",
|
|
40
|
+
"dist:mac": "npm run build && electron-packager . Monacori --platform=darwin --arch=arm64 --icon=assets/icon.icns --app-bundle-id=dev.happynut.monacori --app-category-type=public.app-category.developer-tools --out=release --overwrite --no-asar --ignore=/src/ --ignore=/test/ --ignore=/release --ignore=/.git --ignore=/.github --ignore=/.omc --ignore=/openspec --ignore=/node_modules/electron/ --ignore=/node_modules/@electron --ignore=/node_modules/typescript --ignore=/node_modules/jsdom",
|
|
41
|
+
"dist:mac:dmg": "npm run dist:mac && create-dmg --overwrite --no-code-sign --dmg-title=Monacori 'release/Monacori-darwin-arm64/Monacori.app' release/ || true"
|
|
39
42
|
},
|
|
40
43
|
"keywords": [
|
|
41
44
|
"ai",
|
|
@@ -47,6 +50,7 @@
|
|
|
47
50
|
"author": "happy-nut <happynut.dev@gmail.com>",
|
|
48
51
|
"license": "MIT",
|
|
49
52
|
"devDependencies": {
|
|
53
|
+
"@electron/packager": "^20.0.1",
|
|
50
54
|
"@types/node": "^22.15.21",
|
|
51
55
|
"jsdom": "^29.1.1",
|
|
52
56
|
"typescript": "^5.8.3"
|
|
@@ -62,5 +66,8 @@
|
|
|
62
66
|
"electron": "^42.4.1",
|
|
63
67
|
"highlight.js": "^11.11.1",
|
|
64
68
|
"node-pty": "^1.1.0"
|
|
69
|
+
},
|
|
70
|
+
"optionalDependencies": {
|
|
71
|
+
"create-dmg": "^8.1.0"
|
|
65
72
|
}
|
|
66
73
|
}
|
|
@@ -29,36 +29,45 @@ function main() {
|
|
|
29
29
|
const oldExe = join(macosDir, "Electron");
|
|
30
30
|
const newExe = join(macosDir, APP_NAME);
|
|
31
31
|
const pathTxt = join(root, "path.txt");
|
|
32
|
-
if (!existsSync(plistPath))
|
|
32
|
+
if (!existsSync(plistPath)) {
|
|
33
|
+
console.warn('monacori: Electron.app not found at ' + appDir + ' — skipping rebrand (Dock/menu may show "Electron")');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
33
36
|
|
|
34
37
|
try {
|
|
38
|
+
let changed = false;
|
|
35
39
|
// 1. Bundle metadata: name, display name, AND executable -> monacori.
|
|
36
40
|
const before = readFileSync(plistPath, "utf8");
|
|
37
41
|
const after = before
|
|
38
42
|
.replace(/(<key>CFBundleName<\/key>\s*<string>)[^<]*(<\/string>)/, "$1" + APP_NAME + "$2")
|
|
39
43
|
.replace(/(<key>CFBundleDisplayName<\/key>\s*<string>)[^<]*(<\/string>)/, "$1" + APP_NAME + "$2")
|
|
40
44
|
.replace(/(<key>CFBundleExecutable<\/key>\s*<string>)[^<]*(<\/string>)/, "$1" + APP_NAME + "$2");
|
|
41
|
-
if (after !== before) writeFileSync(plistPath, after);
|
|
45
|
+
if (after !== before) { writeFileSync(plistPath, after); changed = true; }
|
|
42
46
|
|
|
43
47
|
// 2. Rename the executable so the directly-spawned process is "monacori" (idempotent).
|
|
44
|
-
if (existsSync(oldExe) && !existsSync(newExe)) renameSync(oldExe, newExe);
|
|
48
|
+
if (existsSync(oldExe) && !existsSync(newExe)) { renameSync(oldExe, newExe); changed = true; }
|
|
45
49
|
|
|
46
50
|
// 3. Repoint electron's path.txt at the renamed binary so require("electron") resolves it.
|
|
47
51
|
if (existsSync(pathTxt)) {
|
|
48
52
|
const pt = readFileSync(pathTxt, "utf8");
|
|
49
53
|
const fixed = pt.replace("MacOS/Electron", "MacOS/" + APP_NAME);
|
|
50
|
-
if (fixed !== pt) writeFileSync(pathTxt, fixed);
|
|
54
|
+
if (fixed !== pt) { writeFileSync(pathTxt, fixed); changed = true; }
|
|
51
55
|
}
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
|
|
57
|
+
// Only when something actually changed: refresh LaunchServices so the Dock / Cmd+Tab show "monacori"
|
|
58
|
+
// instead of a cached "Electron". Skipping it when already-branded keeps the startup re-run cheap.
|
|
59
|
+
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 + '"');
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
// Surface the reason (perms / read-only) instead of failing SILENTLY — otherwise the Dock/Cmd+Tab/menu
|
|
69
|
+
// 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));
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
|