Skip to content

Commit 396f521

Browse files
DLhuglyclaude
andcommitted
feat: auto-open diffs, diff highlighting, fix Monaco workers, rename to ClifPad
- Auto-open externally modified tracked files in diff mode instead of regular editor - Upgrade already-open files to diff view when modified externally - Add diff editor colors (inserted/removed line/text backgrounds) to all 10 themes - Add CSS fallback diff highlighting for line-insert/line-delete/char-insert/char-delete - Fix Monaco web workers: use Vite ?worker imports instead of broken new URL() pattern - Add renderIndicators and ignoreTrimWhitespace to DiffView config - Fix DiffView cleanup order: dispose editor before models to prevent crashes - Rename "Clif" to "ClifPad" in title bar and status bar Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d96b0b0 commit 396f521

9 files changed

Lines changed: 112 additions & 23 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
---
3535

36-
Cursor is 400MB. VS Code is 350MB. Zed doesn't do AI.
36+
Cursor is 400MB. VS Code is 350MB. Zed is 100MB and climbing.
3737

3838
**ClifPad is ~20MB.** A native Rust IDE with a 7KB SolidJS frontend. VS Code-quality editing via Monaco. Real terminal via PTY. Git built into the backend. AI via [ClifCode](#-clifcode) and [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview) — both integrated, both optional.
3939

clif-pad-ide/src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clif-pad-ide/src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ const App: Component = () => {
9898
const { getCurrentWindow } = await import("@tauri-apps/api/window");
9999
if (root) {
100100
const folder = root.split("/").pop() || root;
101-
getCurrentWindow().setTitle(`${folder}Clif`);
101+
getCurrentWindow().setTitle(`${folder}ClifPad`);
102102
} else {
103-
getCurrentWindow().setTitle("Clif");
103+
getCurrentWindow().setTitle("ClifPad");
104104
}
105105
});
106106

clif-pad-ide/src/components/editor/DiffView.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const DiffView: Component<DiffViewProps> = (props) => {
3333
fontLigatures: true,
3434
readOnly: true,
3535
renderSideBySide: true,
36+
renderIndicators: true,
37+
ignoreTrimWhitespace: false,
3638
scrollBeyondLastLine: false,
3739
minimap: { enabled: false },
3840
padding: { top: 8 },
@@ -91,15 +93,15 @@ const DiffView: Component<DiffViewProps> = (props) => {
9193
});
9294

9395
onCleanup(() => {
96+
if (diffEditor) {
97+
diffEditor.dispose();
98+
}
9499
if (originalModel && !originalModel.isDisposed()) {
95100
originalModel.dispose();
96101
}
97102
if (modifiedModel && !modifiedModel.isDisposed()) {
98103
modifiedModel.dispose();
99104
}
100-
if (diffEditor) {
101-
diffEditor.dispose();
102-
}
103105
});
104106

105107
return (

clif-pad-ide/src/components/layout/StatusBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const StatusBar: Component<{ onShowAbout?: () => void }> = (props) => {
7878
case "error":
7979
return "Update failed";
8080
default:
81-
return `Clif v${appVersion()}`;
81+
return `ClifPad v${appVersion()}`;
8282
}
8383
};
8484

clif-pad-ide/src/lib/monaco-setup.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
11
import * as monaco from "monaco-editor";
22

3-
// Configure Monaco workers
4-
// Monaco needs web workers for language services
3+
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
4+
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
5+
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
6+
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
7+
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
8+
9+
// Configure Monaco workers using Vite's ?worker import syntax
510
self.MonacoEnvironment = {
611
getWorker(_: unknown, label: string) {
7-
const getWorkerModule = (moduleUrl: string) => {
8-
return new Worker(new URL(moduleUrl, import.meta.url), { type: "module" });
9-
};
10-
1112
switch (label) {
1213
case "json":
13-
return getWorkerModule("monaco-editor/esm/vs/language/json/json.worker?worker");
14+
return new jsonWorker();
1415
case "css":
1516
case "scss":
1617
case "less":
17-
return getWorkerModule("monaco-editor/esm/vs/language/css/css.worker?worker");
18+
return new cssWorker();
1819
case "html":
1920
case "handlebars":
2021
case "razor":
21-
return getWorkerModule("monaco-editor/esm/vs/language/html/html.worker?worker");
22+
return new htmlWorker();
2223
case "typescript":
2324
case "javascript":
24-
return getWorkerModule(
25-
"monaco-editor/esm/vs/language/typescript/ts.worker?worker"
26-
);
25+
return new tsWorker();
2726
default:
28-
return getWorkerModule("monaco-editor/esm/vs/editor/editor.worker?worker");
27+
return new editorWorker();
2928
}
3029
},
3130
};
3231

3332
export function configureMonaco() {
3433
// Additional Monaco configuration can go here
35-
// For example, registering custom languages, themes, etc.
3634
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
3735
noSemanticValidation: false,
3836
noSyntaxValidation: false,

clif-pad-ide/src/lib/themes.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
import type * as monaco from "monaco-editor";
22
import type { Theme } from "../stores/uiStore";
33

4+
const darkDiffColors = {
5+
"diffEditor.insertedTextBackground": "#2ea04366",
6+
"diffEditor.removedTextBackground": "#f8514966",
7+
"diffEditor.insertedLineBackground": "#2ea04340",
8+
"diffEditor.removedLineBackground": "#f8514940",
9+
"diffEditorGutter.insertedLineBackground": "#2ea04350",
10+
"diffEditorGutter.removedLineBackground": "#f8514950",
11+
"diffEditor.diagonalFill": "#2d333b55",
12+
};
13+
14+
const lightDiffColors = {
15+
"diffEditor.insertedTextBackground": "#2ea04355",
16+
"diffEditor.removedTextBackground": "#f8514955",
17+
"diffEditor.insertedLineBackground": "#2ea04330",
18+
"diffEditor.removedLineBackground": "#f8514930",
19+
"diffEditorGutter.insertedLineBackground": "#2ea04340",
20+
"diffEditorGutter.removedLineBackground": "#f8514940",
21+
"diffEditor.diagonalFill": "#d0d7de55",
22+
};
23+
424
export const monacoThemes: Record<Theme, monaco.editor.IStandaloneThemeData> = {
525
midnight: {
626
base: "vs-dark",
@@ -43,6 +63,7 @@ export const monacoThemes: Record<Theme, monaco.editor.IStandaloneThemeData> = {
4363
"editorHoverWidget.background": "#161b22",
4464
"editorHoverWidget.border": "#30363d",
4565
"minimap.background": "#0d1117",
66+
...darkDiffColors,
4667
},
4768
},
4869

@@ -87,6 +108,7 @@ export const monacoThemes: Record<Theme, monaco.editor.IStandaloneThemeData> = {
87108
"editorHoverWidget.background": "#2c2c2e",
88109
"editorHoverWidget.border": "#3a3a3c",
89110
"minimap.background": "#1c1c1e",
111+
...darkDiffColors,
90112
},
91113
},
92114

@@ -131,6 +153,7 @@ export const monacoThemes: Record<Theme, monaco.editor.IStandaloneThemeData> = {
131153
"editorHoverWidget.background": "#ffffff",
132154
"editorHoverWidget.border": "#d0d7de",
133155
"minimap.background": "#ffffff",
156+
...lightDiffColors,
134157
},
135158
},
136159

@@ -175,6 +198,7 @@ export const monacoThemes: Record<Theme, monaco.editor.IStandaloneThemeData> = {
175198
"editorHoverWidget.background": "#f8fafc",
176199
"editorHoverWidget.border": "#cbd5e1",
177200
"minimap.background": "#f0f4f8",
201+
...lightDiffColors,
178202
},
179203
},
180204

@@ -219,6 +243,7 @@ export const monacoThemes: Record<Theme, monaco.editor.IStandaloneThemeData> = {
219243
"editorHoverWidget.background": "#231e30",
220244
"editorHoverWidget.border": "#2d263c",
221245
"minimap.background": "#1a1625",
246+
...darkDiffColors,
222247
},
223248
},
224249

@@ -263,6 +288,7 @@ export const monacoThemes: Record<Theme, monaco.editor.IStandaloneThemeData> = {
263288
"editorHoverWidget.background": "#150025",
264289
"editorHoverWidget.border": "#2a0050",
265290
"minimap.background": "#0d0015",
291+
...darkDiffColors,
266292
},
267293
},
268294

@@ -307,6 +333,7 @@ export const monacoThemes: Record<Theme, monaco.editor.IStandaloneThemeData> = {
307333
"editorHoverWidget.background": "#241a16",
308334
"editorHoverWidget.border": "#3a2c24",
309335
"minimap.background": "#1a1210",
336+
...darkDiffColors,
310337
},
311338
},
312339

@@ -351,6 +378,7 @@ export const monacoThemes: Record<Theme, monaco.editor.IStandaloneThemeData> = {
351378
"editorHoverWidget.background": "#162416",
352379
"editorHoverWidget.border": "#283a28",
353380
"minimap.background": "#0f1a0f",
381+
...darkDiffColors,
354382
},
355383
},
356384

@@ -395,6 +423,7 @@ export const monacoThemes: Record<Theme, monaco.editor.IStandaloneThemeData> = {
395423
"editorHoverWidget.background": "#073642",
396424
"editorHoverWidget.border": "#0a4050",
397425
"minimap.background": "#002b36",
426+
...darkDiffColors,
398427
},
399428
},
400429

@@ -439,6 +468,7 @@ export const monacoThemes: Record<Theme, monaco.editor.IStandaloneThemeData> = {
439468
"editorHoverWidget.background": "#2f302a",
440469
"editorHoverWidget.border": "#49483e",
441470
"minimap.background": "#272822",
471+
...darkDiffColors,
442472
},
443473
},
444474
};

clif-pad-ide/src/stores/fileStore.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,30 @@ async function openProject(path: string) {
6969
if (openFiles[existingIdx].content !== content) {
7070
setOpenFiles(existingIdx, "content", content);
7171
setOpenFiles(existingIdx, "isDirty", false);
72+
73+
// If it's a regular file (not already a diff) and has a committed version, upgrade to diff view
74+
const root = projectRoot();
75+
if (root && !openFiles[existingIdx].isDiff && !openFiles[existingIdx].isPreview && !openFiles[existingIdx].isBrowser) {
76+
try {
77+
const relativePath = event.path.startsWith(root)
78+
? event.path.slice(root.length + 1)
79+
: event.path;
80+
const original = await gitShow(root, relativePath);
81+
if (original !== undefined && original !== null) {
82+
setOpenFiles(existingIdx, "isDiff", true);
83+
setOpenFiles(existingIdx, "originalContent", original);
84+
setOpenFiles(existingIdx, "name", getFileName(event.path) + " (diff)");
85+
// Update the path to use diff path convention
86+
const diffPath = event.path + "::diff";
87+
setOpenFiles(existingIdx, "path", diffPath);
88+
if (activeFilePath() === event.path) {
89+
setActiveFilePath(diffPath);
90+
}
91+
}
92+
} catch {
93+
// Not tracked in git, keep as regular file
94+
}
95+
}
7296
}
7397
} catch {
7498
// File might be temporarily locked during write
@@ -81,8 +105,23 @@ async function openProject(path: string) {
81105
// File might not be fully written yet
82106
}
83107
} else if (event.kind === "modify") {
84-
// Auto-open modified files that we're not already viewing
108+
// Auto-open modified files — use diff mode if tracked in git
85109
try {
110+
const root = projectRoot();
111+
if (root) {
112+
try {
113+
const relativePath = event.path.startsWith(root)
114+
? event.path.slice(root.length + 1)
115+
: event.path;
116+
const original = await gitShow(root, relativePath);
117+
if (original !== undefined && original !== null) {
118+
await openDiff(event.path, root);
119+
return;
120+
}
121+
} catch {
122+
// Not tracked, fall through to regular open
123+
}
124+
}
86125
await openFile(event.path);
87126
} catch {
88127
// File might not be readable yet

clif-pad-ide/src/styles/global.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,4 +404,24 @@
404404
.monaco-editor .overflow-guard {
405405
border-radius: 0 !important;
406406
}
407+
408+
/* ─── Diff editor highlighting (fallback if theme colors don't apply) ─── */
409+
.monaco-editor .line-insert, .monaco-diff-editor .line-insert {
410+
background-color: rgba(46, 160, 67, 0.25) !important;
411+
}
412+
.monaco-editor .line-delete, .monaco-diff-editor .line-delete {
413+
background-color: rgba(248, 81, 73, 0.25) !important;
414+
}
415+
.monaco-editor .char-insert, .monaco-diff-editor .char-insert {
416+
background-color: rgba(46, 160, 67, 0.45) !important;
417+
}
418+
.monaco-editor .char-delete, .monaco-diff-editor .char-delete {
419+
background-color: rgba(248, 81, 73, 0.45) !important;
420+
}
421+
.monaco-editor .gutter-insert, .monaco-diff-editor .gutter-insert {
422+
background-color: rgba(46, 160, 67, 0.30) !important;
423+
}
424+
.monaco-editor .gutter-delete, .monaco-diff-editor .gutter-delete {
425+
background-color: rgba(248, 81, 73, 0.30) !important;
426+
}
407427
}

0 commit comments

Comments
 (0)