diff --git a/src/renderer/components/tools/ReplPanel.tsx b/src/renderer/components/tools/ReplPanel.tsx
new file mode 100644
index 0000000..e474e7f
--- /dev/null
+++ b/src/renderer/components/tools/ReplPanel.tsx
@@ -0,0 +1,57 @@
+import { useState, useEffect } from 'react';
+import { X } from 'lucide-react';
+import { Textarea } from '@/components/ui/textarea';
+import { Button } from '@/components/ui/button';
+import { useSettingsStore } from '@/stores/settings-store';
+import { renderMarkdown } from '@/lib/markdown';
+
+export function ReplPanel() {
+ const replOpen = useSettingsStore((s) => s.replOpen);
+ const setSetting = useSettingsStore((s) => s.setSetting);
+ const [source, setSource] = useState('# Markdown preview\n\nType here…');
+ const [debouncedSource, setDebouncedSource] = useState(source);
+
+ useEffect(() => {
+ const t = setTimeout(() => setDebouncedSource(source), 300);
+ return () => clearTimeout(t);
+ }, [source]);
+
+ if (!replOpen) return null;
+
+ const html = renderMarkdown(debouncedSource);
+
+ return (
+
+
+ REPL — Markdown snippet preview
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/tests/component/tools/ReplPanel.test.tsx b/tests/component/tools/ReplPanel.test.tsx
new file mode 100644
index 0000000..1230f42
--- /dev/null
+++ b/tests/component/tools/ReplPanel.test.tsx
@@ -0,0 +1,34 @@
+import { describe, it, expect, beforeEach } from 'vitest';
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { ReplPanel } from '@/components/tools/ReplPanel';
+import { useSettingsStore } from '@/stores/settings-store';
+
+describe('ReplPanel', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ useSettingsStore.setState({ ...useSettingsStore.getInitialState(), replOpen: true });
+ });
+
+ it('renders the textarea and preview when replOpen is true', () => {
+ render();
+ expect(screen.getByRole('textbox')).toBeInTheDocument();
+ expect(screen.getByText(/repl/i)).toBeInTheDocument();
+ });
+
+ it('does not render when replOpen is false', () => {
+ useSettingsStore.setState({ ...useSettingsStore.getInitialState(), replOpen: false });
+ const { container } = render();
+ expect(container.firstChild).toBeNull();
+ });
+
+ it('typing in the textarea updates the preview', async () => {
+ render();
+ const ta = screen.getByRole('textbox');
+ await userEvent.clear(ta);
+ await userEvent.type(ta, '# Hello');
+ // Wait for 300ms debounce to fire
+ await new Promise((r) => setTimeout(r, 400));
+ expect(screen.getByRole('heading', { name: /hello/i })).toBeInTheDocument();
+ });
+});
\ No newline at end of file