Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@tiptap/starter-kit": "^3.18.0",
"@tiptap/suggestion": "^3.18.0",
"clsx": "^2.1.1",
"prosemirror-changeset": "^2.4.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"sonner": "^2.0.7",
Expand Down
158 changes: 158 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
--color-border-solid: #d6d3d1; /* stone-300 - for table borders */
--color-accent: #1c1917;
--color-selection: rgba(250, 204, 21, 0.4); /* Tailwind yellow-400 */
--color-ai-diff-add: #34d399;
--color-ai-diff-modify: #f59e0b;
--color-ai-diff-delete: #ef4444;

/* Editor font settings - simplified (overridden by ThemeContext) */
--editor-font-family:
Expand Down Expand Up @@ -49,6 +52,9 @@
--color-border-solid: #57534e; /* stone-600 - for table borders */
--color-accent: #fafaf9;
--color-selection: rgba(253, 224, 71, 0.35); /* Tailwind yellow-300 */
--color-ai-diff-add: #6ee7b7;
--color-ai-diff-modify: #fbbf24;
--color-ai-diff-delete: #f87171;
}

/* Register theme colors with Tailwind */
Expand Down Expand Up @@ -243,6 +249,7 @@ html.dark {
.ProseMirror {
direction: var(--editor-direction, ltr);
max-width: var(--editor-max-width, 48rem) !important;
--ai-diff-indicator-left: 0.5rem;
}

/* Editor font customization - simplified */
Expand Down Expand Up @@ -423,6 +430,153 @@ html.dark {
margin-bottom: var(--editor-paragraph-spacing);
}

.ai-diff-indicator-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
pointer-events: none;
z-index: 1;
}

.ai-diff-indicator-marker {
position: absolute;
border: 0;
padding: 0;
width: 3px;
border-radius: 9999px;
pointer-events: auto;
cursor: pointer;
transition:
width 120ms ease,
filter 120ms ease;
}

.ai-diff-indicator-marker:hover {
width: 5px;
}

.ai-diff-indicator-marker--active {
width: 5px;
filter: brightness(1.15);
}

.ai-diff-indicator-marker--add {
background: var(--color-ai-diff-add);
}

.ai-diff-indicator-marker--modify {
background: var(--color-ai-diff-modify);
}

.ai-diff-deletion-divider {
position: absolute;
display: flex;
align-items: center;
gap: 0.625rem;
padding: 0.35rem 0;
transform: translateY(-40%);
}

.ai-diff-deletion-divider::before,
.ai-diff-deletion-divider::after {
content: "";
height: 1px;
flex: 1;
background: var(--color-ai-diff-delete);
}

.ai-diff-deletion-divider__label {
color: var(--color-ai-diff-delete);
font-size: 0.75rem;
line-height: 1;
font-weight: 600;
background: var(--color-bg);
padding: 0 0.375rem;
}

.ai-diff-deletion-anchor-block {
margin-top: 2.25rem !important;
}

.ai-diff-word-add {
background: color-mix(
in oklab,
var(--color-ai-diff-add) 26%,
transparent 74%
);
border-radius: 0.125rem;
}

.ai-diff-word-delete {
display: inline;
color: var(--color-ai-diff-delete);
background: color-mix(
in oklab,
var(--color-ai-diff-delete) 24%,
transparent 76%
);
border-radius: 0.125rem;
text-decoration: line-through;
padding: 0 0.12em;
margin-right: 0.08em;
white-space: pre-wrap;
}

.ai-diff-word-delete--code {
font-family:
ui-monospace, "SF Mono", SFMono-Regular, Menlo, Monaco, "Courier New",
monospace;
font-size: 0.92em;
line-height: 1.3;
white-space: pre;
border: 1px solid color-mix(in oklab, var(--color-ai-diff-delete) 35%, transparent 65%);
}

.ai-diff-table-delete-cell {
background: color-mix(
in oklab,
var(--color-ai-diff-delete) 12%,
var(--color-bg) 88%
) !important;
}

.ai-diff-table-delete-cell--header {
background: color-mix(
in oklab,
var(--color-ai-diff-delete) 16%,
var(--color-bg-muted) 84%
) !important;
}

.ai-diff-table-delete-cell > .ai-diff-word-delete {
margin-right: 0;
}

.ai-diff-menu {
position: absolute;
pointer-events: auto;
z-index: 2;
}

.ai-diff-menu-trigger {
width: 1.3rem;
height: 1.3rem;
border-radius: 0.35rem;
border: 1px solid var(--color-border);
background: var(--color-bg);
color: var(--color-text-muted);
display: inline-flex;
align-items: center;
justify-content: center;
transition: all 120ms ease;
}

.ai-diff-menu-trigger:hover {
color: var(--color-text);
background: var(--color-bg-muted);
}

.prose ul,
.prose ol {
margin-top: 0;
Expand Down Expand Up @@ -969,4 +1123,8 @@ table.not-prose th {
page-break-inside: avoid;
break-inside: avoid;
}

.ai-diff-indicator-overlay {
display: none !important;
}
}
33 changes: 32 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
import { getCurrentWindow } from "@tauri-apps/api/window";
import * as aiService from "./services/ai";
import type { AiProvider } from "./services/ai";
import { createAiDiffSession, type AiDiffSession } from "./lib/diff";
import { useWaitForUpdatedEditorSnapshot } from "./hooks/useWaitForUpdatedEditorSnapshot";

// Detect preview mode from URL search params
function getWindowMode(): {
Expand Down Expand Up @@ -59,9 +61,14 @@ function AppContent() {
const [sidebarVisible, setSidebarVisible] = useState(true);
const [aiModalOpen, setAiModalOpen] = useState(false);
const [aiEditing, setAiEditing] = useState(false);
const [aiDiffSession, setAiDiffSession] = useState<AiDiffSession | null>(
null,
);
const [focusMode, setFocusMode] = useState(false);
const [aiProvider, setAiProvider] = useState<AiProvider>("claude");
const editorRef = useRef<TiptapEditor | null>(null);
const waitForUpdatedEditorSnapshot =
useWaitForUpdatedEditorSnapshot(editorRef);

const toggleSidebar = useCallback(() => {
setSidebarVisible((prev) => !prev);
Expand Down Expand Up @@ -100,9 +107,18 @@ function AppContent() {
toast.error("No note selected");
return;
}
if (!editorRef.current) {
toast.error("Editor not ready");
return;
}

setAiDiffSession(null);
setAiEditing(true);

// Capture snapshot of the original document
const beforeJson = editorRef.current.getJSON();
const beforeSerialized = JSON.stringify(beforeJson);

try {
const result =
aiProvider === "codex"
Expand All @@ -112,8 +128,17 @@ function AppContent() {
// Reload the current note from disk
await reloadCurrentNote();

const afterJson = await waitForUpdatedEditorSnapshot(beforeSerialized);

const nextAiDiffSession = createAiDiffSession({
schema: editorRef.current.state.schema,
before: beforeJson,
after: afterJson,
});

// Show results
if (result.success) {
setAiDiffSession(nextAiDiffSession);
// Close modal after success
setAiModalOpen(false);

Expand Down Expand Up @@ -144,9 +169,13 @@ function AppContent() {
setAiEditing(false);
}
},
[aiProvider, currentNote, reloadCurrentNote],
[aiProvider, currentNote, reloadCurrentNote, waitForUpdatedEditorSnapshot],
);

useEffect(() => {
setAiDiffSession(null);
}, [selectedNoteId]);

// Memoize display items to prevent unnecessary recalculations
const displayItems = useMemo(() => {
return searchQuery.trim() ? searchResults : notes;
Expand Down Expand Up @@ -380,6 +409,8 @@ function AppContent() {
onToggleSidebar={toggleSidebar}
sidebarVisible={sidebarVisible && !focusMode}
focusMode={focusMode}
aiDiffSession={aiDiffSession}
onAiDiffSessionChange={setAiDiffSession}
onEditorReady={(editor) => {
editorRef.current = editor;
}}
Expand Down
Loading