@peaske7/readit 0.1.8 → 0.2.0

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.
Files changed (118) hide show
  1. package/README.md +0 -3
  2. package/biome.json +1 -1
  3. package/bun.lock +43 -185
  4. package/docs/perf-baseline.md +75 -0
  5. package/docs/superpowers/plans/2026-03-26-surgical-pruning.md +1176 -0
  6. package/e2e/perf/add-comment.spec.ts +118 -0
  7. package/e2e/perf/fixtures/generate.ts +331 -0
  8. package/e2e/perf/initial-load.spec.ts +49 -0
  9. package/e2e/perf/perf.setup.ts +23 -0
  10. package/e2e/perf/perf.teardown.ts +9 -0
  11. package/e2e/perf/scroll.spec.ts +39 -0
  12. package/e2e/perf/tab-switch.spec.ts +69 -0
  13. package/e2e/perf/text-selection.spec.ts +119 -0
  14. package/e2e/perf/utils/metrics.ts +286 -0
  15. package/e2e/perf/utils/perf-cli.ts +86 -0
  16. package/package.json +9 -18
  17. package/playwright.config.ts +12 -0
  18. package/src/App.tsx +124 -172
  19. package/src/{cli/index.ts → cli.ts} +37 -53
  20. package/src/components/ActionsMenu.tsx +6 -27
  21. package/src/components/DocumentViewer/DocumentViewer.tsx +77 -106
  22. package/src/components/DocumentViewer/MermaidDiagram.tsx +6 -7
  23. package/src/components/Header.tsx +9 -20
  24. package/src/components/InlineEditor.tsx +5 -5
  25. package/src/components/MarginNote.tsx +71 -93
  26. package/src/components/MarginNotes.tsx +7 -34
  27. package/src/components/RawModal.tsx +9 -8
  28. package/src/components/ReanchorConfirm.tsx +2 -2
  29. package/src/components/SettingsModal.tsx +11 -89
  30. package/src/components/TabBar.tsx +4 -4
  31. package/src/components/TableOfContents.tsx +5 -5
  32. package/src/components/comments/CommentInput.tsx +7 -35
  33. package/src/components/comments/CommentListItem.tsx +9 -11
  34. package/src/components/comments/CommentManager.tsx +53 -37
  35. package/src/components/comments/CommentNav.tsx +14 -14
  36. package/src/components/ui/ActionLink.tsx +14 -18
  37. package/src/components/ui/Button.tsx +42 -43
  38. package/src/components/ui/Dialog.tsx +73 -113
  39. package/src/components/ui/DropdownMenu.tsx +113 -69
  40. package/src/components/ui/Text.tsx +30 -37
  41. package/src/contexts/CommentContext.tsx +75 -106
  42. package/src/contexts/LocaleContext.tsx +45 -4
  43. package/src/contexts/PositionsContext.tsx +16 -0
  44. package/src/contexts/SettingsContext.tsx +133 -0
  45. package/src/hooks/useClickOutside.ts +0 -4
  46. package/src/hooks/useCommentNavigation.ts +6 -29
  47. package/src/hooks/useComments.ts +6 -18
  48. package/src/hooks/useDocument.ts +35 -34
  49. package/src/hooks/useHeadings.test.ts +8 -50
  50. package/src/hooks/useHeadings.ts +5 -88
  51. package/src/hooks/useScrollSpy.ts +10 -14
  52. package/src/hooks/useTextSelection.ts +1 -38
  53. package/src/lib/__fixtures__/bench-data.ts +1 -41
  54. package/src/lib/anchor.bench.ts +57 -67
  55. package/src/lib/anchor.test.ts +5 -1
  56. package/src/lib/anchor.ts +13 -93
  57. package/src/lib/comment-storage.test.ts +4 -4
  58. package/src/lib/comment-storage.ts +2 -46
  59. package/src/lib/export.ts +7 -13
  60. package/src/lib/highlight/core.test.ts +1 -1
  61. package/src/lib/highlight/dom.ts +5 -68
  62. package/src/lib/highlight/highlighter.ts +102 -262
  63. package/src/lib/highlight/resolver.ts +112 -0
  64. package/src/lib/highlight/types.ts +0 -35
  65. package/src/lib/highlight/worker.ts +45 -0
  66. package/src/lib/i18n/en.ts +1 -50
  67. package/src/lib/i18n/ja.ts +1 -50
  68. package/src/lib/i18n/types.ts +1 -49
  69. package/src/lib/margin-layout.ts +5 -27
  70. package/src/lib/positions.ts +150 -0
  71. package/src/lib/utils.ts +2 -19
  72. package/src/schema.ts +81 -0
  73. package/src/{server/index.ts → server.ts} +74 -74
  74. package/src/{store/index.ts → store.ts} +14 -46
  75. package/vite.config.ts +8 -0
  76. package/src/components/DocumentViewer/IframeContainer.tsx +0 -251
  77. package/src/components/DocumentViewer/InlineCode.tsx +0 -60
  78. package/src/components/DocumentViewer/index.ts +0 -1
  79. package/src/components/FloatingTOC.tsx +0 -61
  80. package/src/components/ShortcutCapture.tsx +0 -48
  81. package/src/components/ShortcutList.tsx +0 -198
  82. package/src/components/comments/CommentMinimap.tsx +0 -62
  83. package/src/components/ui/ActionBar.tsx +0 -16
  84. package/src/components/ui/SeparatorDot.tsx +0 -9
  85. package/src/contexts/LayoutContext.tsx +0 -88
  86. package/src/hooks/useClipboard.ts +0 -82
  87. package/src/hooks/useEditorScheme.ts +0 -51
  88. package/src/hooks/useFontPreference.ts +0 -59
  89. package/src/hooks/useKeybindings.ts +0 -108
  90. package/src/hooks/useKeyboardShortcuts.ts +0 -63
  91. package/src/hooks/useLayoutMode.ts +0 -44
  92. package/src/hooks/useLocalePreference.ts +0 -42
  93. package/src/hooks/useReanchorMode.ts +0 -33
  94. package/src/hooks/useScrollMetrics.ts +0 -56
  95. package/src/hooks/useThemePreference.ts +0 -66
  96. package/src/lib/comment-storage.bench.ts +0 -63
  97. package/src/lib/context.bench.ts +0 -41
  98. package/src/lib/context.test.ts +0 -224
  99. package/src/lib/context.ts +0 -193
  100. package/src/lib/editor-links.ts +0 -59
  101. package/src/lib/export.bench.ts +0 -35
  102. package/src/lib/highlight/colors.ts +0 -37
  103. package/src/lib/highlight/core.ts +0 -54
  104. package/src/lib/highlight/index.ts +0 -23
  105. package/src/lib/highlight/script-builder.ts +0 -485
  106. package/src/lib/html-processor.test.tsx +0 -170
  107. package/src/lib/html-processor.tsx +0 -95
  108. package/src/lib/i18n/completeness.test.ts +0 -51
  109. package/src/lib/i18n/translations.test.ts +0 -39
  110. package/src/lib/layout-constants.ts +0 -12
  111. package/src/lib/margin-layout.bench.ts +0 -28
  112. package/src/lib/scroll.test.ts +0 -118
  113. package/src/lib/scroll.ts +0 -47
  114. package/src/lib/shortcut-registry.test.ts +0 -173
  115. package/src/lib/shortcut-registry.ts +0 -209
  116. package/src/lib/utils.test.ts +0 -110
  117. package/src/store/index.test.ts +0 -242
  118. package/src/types/index.ts +0 -127
@@ -1,17 +1,13 @@
1
1
  import {
2
- BotMessageSquare,
2
+ ClipboardCopy,
3
3
  FileDown,
4
4
  FileText,
5
- Maximize2,
6
- Minimize2,
7
5
  MoreHorizontal,
8
6
  RefreshCw,
9
7
  Settings,
10
- TextQuote,
11
8
  } from "lucide-react";
12
9
  import { useState } from "react";
13
- import { useCommentContext } from "../contexts/CommentContext";
14
- import { useLayoutContext } from "../contexts/LayoutContext";
10
+ import { useCommentData } from "../contexts/CommentContext";
15
11
  import { useLocale } from "../contexts/LocaleContext";
16
12
  import { RawModal } from "./RawModal";
17
13
  import { SettingsModal } from "./SettingsModal";
@@ -26,19 +22,16 @@ import {
26
22
 
27
23
  interface ActionsMenuProps {
28
24
  onCopyAll: () => void;
29
- onCopyAllRaw: () => void;
30
25
  onExportJson: () => void;
31
26
  onReload: () => void;
32
27
  }
33
28
 
34
29
  export function ActionsMenu({
35
30
  onCopyAll,
36
- onCopyAllRaw,
37
31
  onExportJson,
38
32
  onReload,
39
33
  }: ActionsMenuProps) {
40
- const { commentCount } = useCommentContext();
41
- const { isFullscreen, toggleLayoutMode } = useLayoutContext();
34
+ const { commentCount } = useCommentData();
42
35
  const { t } = useLocale();
43
36
 
44
37
  const [menuOpen, setMenuOpen] = useState(false);
@@ -59,10 +52,6 @@ export function ActionsMenu({
59
52
  </Button>
60
53
  </DropdownMenuTrigger>
61
54
  <DropdownMenuContent align="end" className="min-w-[160px]">
62
- <DropdownMenuItem onSelect={() => toggleLayoutMode()}>
63
- {isFullscreen ? <Minimize2 /> : <Maximize2 />}
64
- {isFullscreen ? t("actions.centered") : t("actions.fullscreen")}
65
- </DropdownMenuItem>
66
55
  <DropdownMenuItem onSelect={() => setSettingsOpen(true)}>
67
56
  <Settings />
68
57
  {t("actions.settings")}
@@ -74,19 +63,9 @@ export function ActionsMenu({
74
63
  </DropdownMenuItem>
75
64
  {commentCount > 0 && (
76
65
  <>
77
- <DropdownMenuItem
78
- onSelect={() => onCopyAll()}
79
- title={t("actions.copyAllAITitle")}
80
- >
81
- <BotMessageSquare />
82
- {t("actions.copyAllAI")}
83
- </DropdownMenuItem>
84
- <DropdownMenuItem
85
- onSelect={() => onCopyAllRaw()}
86
- title={t("actions.copyAllRawTitle")}
87
- >
88
- <TextQuote />
89
- {t("actions.copyAllRaw")}
66
+ <DropdownMenuItem onSelect={() => onCopyAll()}>
67
+ <ClipboardCopy />
68
+ {t("actions.copyAll")}
90
69
  </DropdownMenuItem>
91
70
  <DropdownMenuItem onSelect={() => onExportJson()}>
92
71
  <FileDown />
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type ComponentPropsWithoutRef,
3
3
  type MutableRefObject,
4
+ memo,
4
5
  useEffect,
5
6
  useMemo,
6
7
  useRef,
@@ -8,24 +9,39 @@ import {
8
9
  import Markdown from "react-markdown";
9
10
  import rehypeRaw from "rehype-raw";
10
11
  import remarkGfm from "remark-gfm";
11
- import { useLayoutContext } from "../../contexts/LayoutContext";
12
+ import { usePositions } from "../../contexts/PositionsContext";
13
+ import { useSettings } from "../../contexts/SettingsContext";
12
14
  import type { Heading } from "../../hooks/useHeadings";
13
15
  import {
14
16
  createHighlighter,
15
- type HighlightComment,
16
17
  type Highlighter,
17
- } from "../../lib/highlight";
18
+ } from "../../lib/highlight/highlighter";
19
+ import type { HighlightComment } from "../../lib/highlight/types";
18
20
  import { cn, getTextContent } from "../../lib/utils";
19
- import { useAppStore } from "../../store";
20
- import {
21
- AnchorConfidences,
22
- type Comment,
23
- type DocumentType,
24
- FontFamilies,
25
- type SelectionRange,
26
- } from "../../types";
27
- import { IframeContainer } from "./IframeContainer";
28
- import { createCodeComponent } from "./InlineCode";
21
+ import { AnchorConfidences, type Comment, FontFamilies } from "../../schema";
22
+ import { CodeBlock } from "./CodeBlock";
23
+
24
+ const REMARK_PLUGINS = [remarkGfm];
25
+ const REHYPE_PLUGINS = [rehypeRaw];
26
+
27
+ /** Memoized Markdown renderer — skips reconciliation when only comments change. */
28
+ const MemoizedMarkdown = memo(function MemoizedMarkdown({
29
+ content,
30
+ components,
31
+ }: {
32
+ content: string;
33
+ components: ComponentPropsWithoutRef<typeof Markdown>["components"];
34
+ }) {
35
+ return (
36
+ <Markdown
37
+ components={components}
38
+ remarkPlugins={REMARK_PLUGINS}
39
+ rehypePlugins={REHYPE_PLUGINS}
40
+ >
41
+ {content}
42
+ </Markdown>
43
+ );
44
+ });
29
45
 
30
46
  function createHeadingComponent(
31
47
  level: 1 | 2 | 3 | 4 | 5 | 6,
@@ -52,7 +68,6 @@ function createHeadingComponent(
52
68
  }
53
69
  }
54
70
 
55
- // Fallback: if not found (shouldn't happen), search from beginning
56
71
  if (!id) {
57
72
  for (const heading of headings) {
58
73
  if (heading.level === level && heading.text === text) {
@@ -72,49 +87,48 @@ function createHeadingComponent(
72
87
 
73
88
  interface DocumentViewerProps {
74
89
  content: string;
75
- type: DocumentType;
76
90
  comments: Comment[];
77
91
  headings: Heading[];
78
- pendingSelection?: SelectionRange;
92
+ isActive: boolean;
79
93
  onTextSelect: (
80
94
  text: string,
81
95
  startOffset: number,
82
96
  endOffset: number,
83
97
  selectionTop: number,
84
98
  ) => void;
85
- onHighlightPositionsChange?: (
86
- positions: Record<string, number>,
87
- documentPositions: Record<string, number>,
88
- pendingTop?: number,
89
- ) => void;
90
99
  onHighlightHover?: (commentId: string | undefined) => void;
91
100
  onHighlightClick?: (commentId: string) => void;
92
101
  }
93
102
 
94
103
  export function DocumentViewer({
95
104
  content,
96
- type,
97
105
  comments,
98
106
  headings,
99
- pendingSelection,
107
+ isActive,
100
108
  onTextSelect,
101
- onHighlightPositionsChange,
102
109
  onHighlightHover,
103
110
  onHighlightClick,
104
111
  }: DocumentViewerProps) {
105
- const { isFullscreen, fontFamily, editorScheme } = useLayoutContext();
106
- const workingDirectory = useAppStore((s) => s.workingDirectory);
112
+ const { fontFamily } = useSettings();
113
+ const pos = usePositions();
107
114
  const contentRef = useRef<HTMLDivElement>(null);
108
115
  const containerRef = useRef<HTMLDivElement>(null);
109
116
  const adapterRef = useRef<Highlighter | null>(null);
110
117
  const headingIndexRef = useRef(0);
111
118
 
119
+ // Attach/detach pos to DOM elements — only when tab is visible
120
+ // (getBoundingClientRect returns zero rects on display:none elements)
121
+ useEffect(() => {
122
+ if (!isActive || !contentRef.current || !containerRef.current) return;
123
+ pos.attach(contentRef.current, containerRef.current);
124
+ pos.cache();
125
+ return () => pos.detach();
126
+ }, [pos, isActive]);
127
+
112
128
  useEffect(() => {
113
- if (type !== "markdown") return;
114
129
  if (!contentRef.current || !containerRef.current) return;
115
130
 
116
131
  const adapter = createHighlighter({
117
- type: "markdown",
118
132
  root: contentRef.current,
119
133
  container: containerRef.current,
120
134
  onSelect: onTextSelect,
@@ -122,16 +136,6 @@ export function DocumentViewer({
122
136
 
123
137
  adapterRef.current = adapter;
124
138
 
125
- const unsubPositions = onHighlightPositionsChange
126
- ? adapter.onPositionsChange((pos) => {
127
- onHighlightPositionsChange(
128
- pos.positions,
129
- pos.documentPositions,
130
- pos.pendingTop,
131
- );
132
- })
133
- : () => {};
134
-
135
139
  const unsubHover = onHighlightHover
136
140
  ? adapter.onHighlightHover(onHighlightHover)
137
141
  : () => {};
@@ -141,58 +145,40 @@ export function DocumentViewer({
141
145
  : () => {};
142
146
 
143
147
  return () => {
144
- unsubPositions();
145
148
  unsubHover();
146
149
  unsubClick();
147
150
  adapter.dispose();
148
151
  adapterRef.current = null;
149
152
  };
150
- }, [
151
- type,
152
- onTextSelect,
153
- onHighlightPositionsChange,
154
- onHighlightHover,
155
- onHighlightClick,
156
- ]);
153
+ }, [onTextSelect, onHighlightHover, onHighlightClick]);
157
154
 
158
- // Double RAF: ensures React commit phase completes before DOM queries.
159
- // See: https://github.com/facebook/react/issues/20863
155
+ // Apply highlights after React commit completes (single rAF).
156
+ // Skip when comments is empty to avoid wasted DOM walk.
160
157
  // biome-ignore lint/correctness/useExhaustiveDependencies: must reapply highlights when content or components change
161
158
  useEffect(() => {
162
- if (type !== "markdown") return;
163
-
164
- let outerFrameId: number;
165
- let innerFrameId: number;
166
-
167
- outerFrameId = requestAnimationFrame(() => {
168
- innerFrameId = requestAnimationFrame(() => {
169
- const adapter = adapterRef.current;
170
- if (!adapter) return;
171
-
172
- const highlightComments: HighlightComment[] = comments
173
- .filter((c) => c.anchorConfidence !== AnchorConfidences.UNRESOLVED)
174
- .map((c) => ({
175
- id: c.id,
176
- selectedText: c.selectedText,
177
- startOffset: c.startOffset,
178
- endOffset: c.endOffset,
179
- }));
180
-
181
- adapter.applyHighlights(highlightComments);
182
- });
159
+ if (!isActive) return;
160
+ if (comments.length === 0) return;
161
+
162
+ const rafId = requestAnimationFrame(() => {
163
+ const adapter = adapterRef.current;
164
+ if (!adapter) return;
165
+
166
+ const highlightComments: HighlightComment[] = comments
167
+ .filter((c) => c.anchorConfidence !== AnchorConfidences.UNRESOLVED)
168
+ .map((c) => ({
169
+ id: c.id,
170
+ selectedText: c.selectedText,
171
+ startOffset: c.startOffset,
172
+ endOffset: c.endOffset,
173
+ }));
174
+
175
+ adapter.applyHighlights(highlightComments);
183
176
  });
184
177
 
185
- return () => {
186
- cancelAnimationFrame(outerFrameId);
187
- cancelAnimationFrame(innerFrameId);
188
- };
189
- // editorScheme/workingDirectory: when these change, markdownComponents memo recomputes,
190
- // react-markdown replaces the DOM, so highlights must be reapplied
191
- }, [comments, content, type, editorScheme, workingDirectory]);
178
+ return () => cancelAnimationFrame(rafId);
179
+ }, [comments, content, isActive, pos]);
192
180
 
193
181
  useEffect(() => {
194
- if (type !== "markdown") return;
195
-
196
182
  const handleTestSelect = (e: Event) => {
197
183
  const { text, startOffset, endOffset } = (e as CustomEvent).detail;
198
184
  onTextSelect(text, startOffset, endOffset, 0);
@@ -201,7 +187,7 @@ export function DocumentViewer({
201
187
  window.addEventListener("test:select-text", handleTestSelect);
202
188
  return () =>
203
189
  window.removeEventListener("test:select-text", handleTestSelect);
204
- }, [type, onTextSelect]);
190
+ }, [onTextSelect]);
205
191
 
206
192
  // Memoized to prevent DOM node replacement (breaks highlight persistence)
207
193
  const markdownComponents = useMemo(
@@ -212,28 +198,20 @@ export function DocumentViewer({
212
198
  h4: createHeadingComponent(4, headings, headingIndexRef),
213
199
  h5: createHeadingComponent(5, headings, headingIndexRef),
214
200
  h6: createHeadingComponent(6, headings, headingIndexRef),
215
- code: createCodeComponent(editorScheme, workingDirectory),
201
+ code: ({
202
+ children,
203
+ className,
204
+ ...props
205
+ }: ComponentPropsWithoutRef<"code">) => {
206
+ if (className || String(children).includes("\n")) {
207
+ return <CodeBlock className={className}>{children}</CodeBlock>;
208
+ }
209
+ return <code {...props}>{children}</code>;
210
+ },
216
211
  }),
217
- [headings, editorScheme, workingDirectory],
212
+ [headings],
218
213
  );
219
214
 
220
- if (type === "html") {
221
- return (
222
- <main className="flex-1 min-w-0 flex flex-col">
223
- <IframeContainer
224
- html={content}
225
- comments={comments}
226
- pendingSelection={pendingSelection}
227
- onTextSelect={onTextSelect}
228
- onHighlightPositionsChange={onHighlightPositionsChange}
229
- onHighlightHover={onHighlightHover}
230
- onHighlightClick={onHighlightClick}
231
- fontFamily={fontFamily}
232
- />
233
- </main>
234
- );
235
- }
236
-
237
215
  headingIndexRef.current = 0;
238
216
 
239
217
  return (
@@ -242,17 +220,10 @@ export function DocumentViewer({
242
220
  ref={contentRef}
243
221
  className={cn(
244
222
  "prose",
245
- isFullscreen && "prose-fullscreen",
246
223
  fontFamily === FontFamilies.SANS_SERIF ? "prose-sans" : "prose-serif",
247
224
  )}
248
225
  >
249
- <Markdown
250
- components={markdownComponents}
251
- remarkPlugins={[remarkGfm]}
252
- rehypePlugins={[rehypeRaw]}
253
- >
254
- {content}
255
- </Markdown>
226
+ <MemoizedMarkdown content={content} components={markdownComponents} />
256
227
  </article>
257
228
  </div>
258
229
  );
@@ -1,4 +1,3 @@
1
- import DOMPurify from "dompurify";
2
1
  import { useEffect, useId, useState } from "react";
3
2
 
4
3
  interface MermaidDiagramProps {
@@ -83,14 +82,14 @@ export function MermaidDiagram({ code }: MermaidDiagramProps) {
83
82
  },
84
83
  });
85
84
 
86
- const { svg: rawSvg } = await mermaid.render(`mermaid-${id}`, code);
87
- // Sanitize SVG output with DOMPurify before storing in state
88
- const sanitizedSvg = DOMPurify.sanitize(rawSvg, {
89
- USE_PROFILES: { svg: true },
90
- });
85
+ // securityLevel: "strict" prevents script injection in mermaid output
86
+ const { svg: renderedSvg } = await mermaid.render(
87
+ `mermaid-${id}`,
88
+ code,
89
+ );
91
90
 
92
91
  if (!cancelled) {
93
- setSvg(sanitizedSvg);
92
+ setSvg(renderedSvg);
94
93
  setError(null);
95
94
  }
96
95
  } catch (err) {
@@ -1,7 +1,5 @@
1
- import { useCommentContext } from "../contexts/CommentContext";
2
- import { useLayoutContext } from "../contexts/LayoutContext";
1
+ import { useCommentData } from "../contexts/CommentContext";
3
2
  import { useLocale } from "../contexts/LocaleContext";
4
- import { cn } from "../lib/utils";
5
3
  import { ActionsMenu } from "./ActionsMenu";
6
4
  import { CommentBadge } from "./comments/CommentBadge";
7
5
  import { Text } from "./ui/Text";
@@ -9,7 +7,6 @@ import { Text } from "./ui/Text";
9
7
  interface HeaderProps {
10
8
  fileName: string;
11
9
  onCopyAll: () => void;
12
- onCopyAllRaw: () => void;
13
10
  onExportJson: () => void;
14
11
  onReload: () => void;
15
12
  }
@@ -17,36 +14,29 @@ interface HeaderProps {
17
14
  export function Header({
18
15
  fileName,
19
16
  onCopyAll,
20
- onCopyAllRaw,
21
17
  onExportJson,
22
18
  onReload,
23
19
  }: HeaderProps) {
24
- const { reanchorTarget } = useCommentContext();
25
- const { isFullscreen } = useLayoutContext();
20
+ const { reanchorTarget } = useCommentData();
26
21
  const { t } = useLocale();
27
22
 
28
23
  return (
29
24
  <header className="sticky top-0 z-50 bg-white/95 dark:bg-zinc-900/95 backdrop-blur-sm border-b border-zinc-100 dark:border-zinc-800">
30
- <div
31
- className={cn(
32
- "px-6 py-3 flex items-center justify-between",
33
- !isFullscreen && "max-w-7xl mx-auto",
34
- )}
35
- >
25
+ <div className="px-6 py-3 flex items-center justify-between max-w-7xl mx-auto">
36
26
  <div className="flex items-center gap-3">
37
- <Text variant="title" asChild>
38
- <h1>readit</h1>
27
+ <Text variant="title" as="h1">
28
+ readit
39
29
  </Text>
40
30
  <span className="text-zinc-200 dark:text-zinc-700 font-light">—</span>
41
- <Text variant="caption" asChild>
42
- <span className="truncate max-w-[200px]">{fileName}</span>
31
+ <Text variant="caption" as="span" className="truncate max-w-[200px]">
32
+ {fileName}
43
33
  </Text>
44
34
  </div>
45
35
 
46
36
  <div className="flex items-center gap-3">
47
37
  {reanchorTarget && (
48
- <Text variant="caption" asChild>
49
- <span className="italic">{t("header.selectTextToReanchor")}</span>
38
+ <Text variant="caption" as="span" className="italic">
39
+ {t("header.selectTextToReanchor")}
50
40
  </Text>
51
41
  )}
52
42
 
@@ -54,7 +44,6 @@ export function Header({
54
44
 
55
45
  <ActionsMenu
56
46
  onCopyAll={onCopyAll}
57
- onCopyAllRaw={onCopyAllRaw}
58
47
  onExportJson={onExportJson}
59
48
  onReload={onReload}
60
49
  />
@@ -1,8 +1,8 @@
1
1
  import { use, useEffect, useRef, useState } from "react";
2
- import { LayoutContext } from "../contexts/LayoutContext";
3
2
  import { useLocale } from "../contexts/LocaleContext";
3
+ import { SettingsContext } from "../contexts/SettingsContext";
4
4
  import { cn } from "../lib/utils";
5
- import { FontFamilies } from "../types";
5
+ import { FontFamilies } from "../schema";
6
6
  import { Button } from "./ui/Button";
7
7
 
8
8
  interface InlineEditorProps {
@@ -20,10 +20,10 @@ export function InlineEditor({
20
20
  rows = 2,
21
21
  className,
22
22
  }: InlineEditorProps) {
23
- const layout = use(LayoutContext);
23
+ const settings = use(SettingsContext);
24
24
  const { t } = useLocale();
25
- const fontClass = layout
26
- ? layout.fontFamily === FontFamilies.SANS_SERIF
25
+ const fontClass = settings
26
+ ? settings.fontFamily === FontFamilies.SANS_SERIF
27
27
  ? "font-sans"
28
28
  : "font-serif"
29
29
  : undefined;