Skip to content

Commit 7e2158a

Browse files
erictliclaude
andauthored
Release v0.8.0 (#80)
* Bump version to v0.8.0 and fix KaTeX/mermaid/Ollama issues Version bump: - 0.7.1 → 0.8.0 in package.json, tauri.conf.json, Cargo.toml KaTeX block math fixes: - Fix native DOM selection highlight bleed when navigating to or clicking block math nodes (ProseMirror plugin clears DOM selection) - Add Enter/Space keyboard shortcut to open editor on selected block - Move cursor after node on submit/cancel to avoid re-triggering highlight - Add ProseMirror-selectednode outline style for visual feedback Mermaid fixes: - Start in edit mode when switching empty code block to mermaid - Show "Empty mermaid diagram" placeholder instead of collapsing Ollama error handling: - Strip ANSI escape codes from CLI stdout/stderr - Detect model-not-found errors and show actionable message Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: align mermaid edit button with language dropdown Co-Authored-By: Claude Opus 4.6 <[email protected]> * refactor: simplify CodeBlockView toolbar Co-Authored-By: Claude Opus 4.6 <[email protected]> * Codeblock dropdown styling adjustment. * fix: don't trim stdout in AI CLI, narrow Ollama error matching - Stop trimming stdout in execute_ai_cli (callers trim as needed, and trimming here could strip meaningful whitespace) - Replace broad "not found" check with specific "model not found" and "model does not exist" patterns Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
1 parent 8b249a2 commit 7e2158a

File tree

11 files changed

+125
-42
lines changed

11 files changed

+125
-42
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "tauri-app",
33
"private": true,
4-
"version": "0.7.1",
4+
"version": "0.8.0",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

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.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "Scratch"
3-
version = "0.7.1"
3+
version = "0.8.0"
44
description = "An offline markdown-based notes app"
55
authors = ["erictli"]
66
edition = "2021"

src-tauri/src/lib.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2347,17 +2347,22 @@ async fn execute_ai_cli(
23472347
.map(|s| s.success())
23482348
.unwrap_or(false);
23492349

2350+
// Strip ANSI escape sequences from output (e.g. Ollama progress spinners)
2351+
let ansi_re = regex::Regex::new(r"\x1b\[[0-9;?]*[A-Za-z]|\x1b\].*?\x07").unwrap();
2352+
let stdout_clean = ansi_re.replace_all(&stdout_str, "").to_string();
2353+
let stderr_clean = ansi_re.replace_all(&stderr_str, "").trim().to_string();
2354+
23502355
if success {
23512356
AiExecutionResult {
23522357
success: true,
2353-
output: stdout_str,
2358+
output: stdout_clean,
23542359
error: None,
23552360
}
23562361
} else {
23572362
AiExecutionResult {
23582363
success: false,
2359-
output: stdout_str,
2360-
error: Some(stderr_str),
2364+
output: stdout_clean,
2365+
error: Some(stderr_clean),
23612366
}
23622367
}
23632368
});
@@ -2559,7 +2564,7 @@ async fn ai_execute_ollama(
25592564
let result = execute_ai_cli(
25602565
"Ollama",
25612566
"ollama".to_string(),
2562-
vec!["run".to_string(), model_name],
2567+
vec!["run".to_string(), model_name.clone()],
25632568
stdin_input,
25642569
"Ollama CLI not found. Please install it from https://ollama.com".to_string(),
25652570
)
@@ -2568,6 +2573,21 @@ async fn ai_execute_ollama(
25682573
// Improve error messages for common Ollama failures
25692574
if !result.success {
25702575
if let Some(ref err) = result.error {
2576+
let err_lower = err.to_lowercase();
2577+
if err_lower.contains("file does not exist")
2578+
|| err_lower.contains("pull model manifest")
2579+
|| err_lower.contains("model not found")
2580+
|| err_lower.contains("model does not exist")
2581+
{
2582+
return Ok(AiExecutionResult {
2583+
success: false,
2584+
output: String::new(),
2585+
error: Some(format!(
2586+
"Model '{}' not found. Run `ollama pull {}` in your terminal to download it.",
2587+
model_name, model_name
2588+
)),
2589+
});
2590+
}
25712591
if err.contains("401") || err.contains("Unauthorized") {
25722592
return Ok(AiExecutionResult {
25732593
success: false,

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "Scratch",
4-
"version": "0.7.1",
4+
"version": "0.8.0",
55
"identifier": "com.scratch.app",
66
"build": {
77
"beforeDevCommand": "npm run dev",

src/App.css

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,8 @@ html.dark {
251251
}
252252

253253
@keyframes bounce-gentle {
254-
0%, 100% {
254+
0%,
255+
100% {
255256
transform: translateY(0);
256257
}
257258
50% {
@@ -556,6 +557,13 @@ html.dark {
556557
max-width: 100%;
557558
}
558559

560+
.prose
561+
.tiptap-mathematics-render[data-type="block-math"].ProseMirror-selectednode {
562+
outline: 2px solid var(--color-bg-emphasis);
563+
outline-offset: 2px;
564+
border-radius: 0.25rem;
565+
}
566+
559567
.prose .tiptap-mathematics-render--editable {
560568
cursor: pointer;
561569
}

src/components/editor/CodeBlockView.tsx

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import { SUPPORTED_LANGUAGES } from "./lowlight";
55
import { MermaidRenderer } from "./MermaidRenderer";
66
import { ChevronDownIcon, PencilIcon, EyeIcon } from "../icons";
77

8+
const btnClass =
9+
"code-block-mermaid-btn inline-flex items-center gap-1 text-xs h-6 px-1.5 text-text-muted rounded cursor-pointer transition-colors hover:text-text hover:bg-bg-emphasis";
10+
811
export function CodeBlockView({ node, updateAttributes }: ReactNodeViewProps) {
912
const language: string = node.attrs.language || "";
1013
const isMermaid = language === "mermaid";
11-
const [showSource, setShowSource] = useState(false);
14+
const [showSource, setShowSource] = useState(!node.textContent.trim());
1215
const codeContent = node.textContent;
1316

1417
const handleLanguageChange = useCallback(
@@ -18,46 +21,41 @@ export function CodeBlockView({ node, updateAttributes }: ReactNodeViewProps) {
1821
[updateAttributes],
1922
);
2023

21-
const mermaidButton = isMermaid ? (
22-
showSource ? (
23-
<button
24-
contentEditable={false}
25-
onClick={() => setShowSource(false)}
26-
className="code-block-mermaid-btn inline-flex items-center gap-1 text-sm px-1.5 py-0.5 text-text-muted rounded cursor-pointer transition-colors hover:text-text hover:bg-bg-emphasis"
27-
type="button"
28-
>
29-
<EyeIcon className="w-3.75 h-3.75 stroke-[1.7]" />
30-
Preview
31-
</button>
32-
) : (
33-
<button
34-
contentEditable={false}
35-
onClick={() => setShowSource(true)}
36-
className="code-block-mermaid-btn inline-flex items-center gap-1 text-sm px-1.5 py-0.5 text-text-muted rounded cursor-pointer transition-colors hover:text-text hover:bg-bg-emphasis"
37-
type="button"
38-
>
39-
<PencilIcon className="w-3.75 h-3.75 stroke-[1.7]" />
40-
Edit
41-
</button>
42-
)
43-
) : null;
44-
4524
const toolbar = (
4625
<div className="code-block-language-selector" contentEditable={false}>
47-
{mermaidButton}
48-
<div className="relative">
26+
{isMermaid && (
27+
<button
28+
contentEditable={false}
29+
onClick={() => setShowSource(!showSource)}
30+
className={btnClass}
31+
type="button"
32+
>
33+
{showSource ? (
34+
<>
35+
<EyeIcon className="w-3.5 h-3.5 stroke-[1.7]" />
36+
Preview
37+
</>
38+
) : (
39+
<>
40+
<PencilIcon className="w-3.5 h-3.5 stroke-[1.7]" />
41+
Edit
42+
</>
43+
)}
44+
</button>
45+
)}
46+
<div className="relative flex items-center h-6">
4947
<select
5048
value={language}
5149
onChange={handleLanguageChange}
52-
className="appearance-none bg-transparent text-text-muted text-sm cursor-pointer outline-none pr-4 pl-1.5 py-0.5 rounded hover:bg-bg-emphasis transition-colors"
50+
className="appearance-none bg-transparent text-text-muted text-xs h-6 cursor-pointer outline-none pr-4 pl-1.5 rounded hover:bg-bg-emphasis transition-colors"
5351
>
5452
{SUPPORTED_LANGUAGES.map((lang) => (
5553
<option key={lang.value} value={lang.value}>
5654
{lang.label}
5755
</option>
5856
))}
5957
</select>
60-
<ChevronDownIcon className="w-3 h-3 absolute right-0.5 top-1/2 -translate-y-1/2 pointer-events-none text-text-muted" />
58+
<ChevronDownIcon className="w-3.25 h-3.25 stroke-[1.7] absolute right-1.25 top-1/2 -translate-y-1/2 pointer-events-none text-text-muted" />
6159
</div>
6260
</div>
6361
);

src/components/editor/Editor.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -742,11 +742,18 @@ export function Editor({
742742
.chain()
743743
.focus()
744744
.updateBlockMath({ pos, latex: trimmed })
745+
.setTextSelection(pos + node.nodeSize)
745746
.run();
746747
closeBlockMathPopup();
747748
},
748749
onCancel: () => {
749-
currentEditor.commands.focus();
750+
// Move cursor after the node instead of restoring the NodeSelection,
751+
// which would re-trigger native DOM selection highlight bleed
752+
currentEditor
753+
.chain()
754+
.focus()
755+
.setTextSelection(pos + node.nodeSize)
756+
.run();
750757
closeBlockMathPopup();
751758
},
752759
},

src/components/editor/MathExtensions.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { InputRule } from "@tiptap/core";
22
import { BlockMath } from "@tiptap/extension-mathematics";
3+
import { Plugin, PluginKey, NodeSelection } from "@tiptap/pm/state";
34

45
export function normalizeBlockMath(value: string): string {
56
const trimmed = value.trim();
@@ -25,4 +26,46 @@ export const ScratchBlockMath = BlockMath.extend({
2526
}),
2627
];
2728
},
29+
30+
addProseMirrorPlugins() {
31+
const onClick = this.options.onClick;
32+
return [
33+
new Plugin({
34+
key: new PluginKey("blockMathSelection"),
35+
view() {
36+
return {
37+
// Clear native DOM selection when a blockMath node is selected.
38+
// ProseMirror's NodeSelection sets a DOM selection that spans all
39+
// content before the atom node, causing a visible highlight bleed.
40+
update(view) {
41+
const { selection } = view.state;
42+
if (
43+
selection instanceof NodeSelection &&
44+
selection.node.type.name === "blockMath"
45+
) {
46+
window.getSelection()?.removeAllRanges();
47+
}
48+
},
49+
};
50+
},
51+
props: {
52+
// Open editor on Enter or Space when a blockMath node is selected
53+
handleKeyDown(view, event) {
54+
if (event.key !== "Enter" && event.key !== " ") return false;
55+
const { selection } = view.state;
56+
if (
57+
selection instanceof NodeSelection &&
58+
selection.node.type.name === "blockMath" &&
59+
onClick
60+
) {
61+
event.preventDefault();
62+
onClick(selection.node, selection.from);
63+
return true;
64+
}
65+
return false;
66+
},
67+
},
68+
}),
69+
];
70+
},
2871
});

src/components/editor/MermaidRenderer.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,19 @@ export function MermaidRenderer({ code }: MermaidRendererProps) {
2929

3030
if (error) {
3131
return (
32-
<div className="text-xs text-[var(--color-text-muted)] italic px-2 py-1">
32+
<div className="text-xs text-text-muted italic px-2 pt-6 pb-3 text-center">
3333
Mermaid syntax error
3434
</div>
3535
);
3636
}
3737

38-
if (!svg) return null;
38+
if (!svg) {
39+
return (
40+
<div className="text-xs text-text-muted italic px-2 pt-6 pb-3 text-center">
41+
Empty mermaid diagram
42+
</div>
43+
);
44+
}
3945

4046
return (
4147
<div

0 commit comments

Comments
 (0)