Skip to content

Commit 46a59ac

Browse files
committed
docs: update spatial parity checklist with test coverage for PageToolbarActions and SpatialPageView components
1 parent dabb0c0 commit 46a59ac

15 files changed

Lines changed: 252 additions & 30 deletions

docs/SPATIAL_PARITY_CHECKLIST.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@
174174
|---|---------|------------------|-------------|--------|-----------|
175175
| 12.1 | Fullscreen immersive shell | `PagesLayout.vue` | `PageLayout.vue` | **Done** | `PageLayout.test.ts` (10 tests) |
176176
| 12.2 | Main toolbar (logo, breadcrumb, global nav) | `MainToolbar.vue` | `MainToolbar.vue` | **Done** | `MainToolbar.test.ts` (7 tests) |
177-
| 12.3 | Toolbar page action buttons | `MainToolbar/Actions.vue` | | **Not started** | |
177+
| 12.3 | Toolbar page action buttons | `MainToolbar/Actions.vue` | `PageToolbarActions.vue` | **Done** | `PageToolbarActions.test.ts` (7 tests) |
178178
| 12.4 | Left sidebar (resizable, collapsible) | `LeftSidebar.vue` | `PageLayout.vue:left aside` | **Done** | `PageLayout.test.ts` |
179179
| 12.5 | Left sidebar — Current path | `LeftSidebar/CurrentPath.vue` | `PageEditorView.vue:Path card` | **Done** ||
180180
| 12.6 | Left sidebar — Recent pages | `LeftSidebar/RecentPages.vue` | `RecentPagesCard.vue` | **Done** | `RecentPagesCard.test.ts` (5 tests) |

docs/restart-plan/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# DeepNotes Restart Plan — Index
22

3-
> **Last updated:** 2026-05-31 (Phase 6 in progress. **New this session:** `MainToolbar.test.ts` (7 tests), `PageLayout.test.ts` (10 tests), `useCanvasActions.test.ts` (5 tests total), `RecentPagesCard.test.ts` (5 tests), `FavoritePagesCard.test.ts` (5 tests), `SelectedPagesCard.test.ts` (5 tests), `useNoteContextMenu.test.ts` (5 tests). `DisplayNote.test.ts` expanded to 21 tests. `fitToScreen` now reads actual note heights from reactive map. **Per-note context menu implemented:** `NoteContextMenu.vue` + `useNoteContextMenu.ts` composable wired into `DisplayNote.vue` and `SpatialPageView.vue`. See `phase-6-spatial-polish.md` and `SPATIAL_PARITY_CHECKLIST.md` for details. Phase 9 pending.)
3+
> **Last updated:** 2026-05-31 (Phase 6 in progress. **New this session:** `PageToolbarActions.vue` (insert note/arrow, zoom in/out, fit-to-screen) with `PageToolbarActions.test.ts` (7 tests). `SpatialPageView.test.ts` expanded to 8 tests (zoomIn/zoomOut/insertArrowBetweenSelected exposed). `MainToolbar.test.ts` (7 tests), `PageLayout.test.ts` (10 tests), `useCanvasActions.test.ts` (5 tests total), `RecentPagesCard.test.ts` (5 tests), `FavoritePagesCard.test.ts` (5 tests), `SelectedPagesCard.test.ts` (5 tests), `useNoteContextMenu.test.ts` (5 tests). `DisplayNote.test.ts` expanded to 21 tests. `fitToScreen` now reads actual note heights from reactive map. **Per-note context menu implemented:** `NoteContextMenu.vue` + `useNoteContextMenu.ts` composable wired into `DisplayNote.vue` and `SpatialPageView.vue`. See `phase-6-spatial-polish.md` and `SPATIAL_PARITY_CHECKLIST.md` for details. Phase 9 pending.)
44
> **This document replaces `docs/RESTART_PLAN.md`.** If a prior statement conflicts with this one, this version wins.
55
66
---
@@ -77,10 +77,10 @@ A criterion is **not met** until the verification command or check passes in CI.
7777

7878
### Phase 6 — Spatial canvas polish (in progress)
7979

80-
- **`docs/SPATIAL_PARITY_CHECKLIST.md` created.** 82+ rows. Schema diff table complete. **Test coverage improved this session:** `MainToolbar.test.ts` (7), `PageLayout.test.ts` (10), `RecentPagesCard.test.ts` (5), `FavoritePagesCard.test.ts` (5), `SelectedPagesCard.test.ts` (5), `useNoteContextMenu.test.ts` (5), `useCanvasActions.test.ts` (5), `DisplayNote.test.ts` (21), `DisplayArrow.test.ts` (12), `useCanvasContextMenu.test.ts` (6), `note-geometry.test.ts` (10), `useBoxSelection.test.ts` (6), `arrow-geometry.test.ts` (5), `useSpatialEditing.test.ts` (4), `selection.test.ts` (12). Remaining gaps: `SpatialPageView.vue` (no component/integration tests), drag/resize end-to-end interaction, arrow creation/reconnection, sidebar properties cards.
80+
- **`docs/SPATIAL_PARITY_CHECKLIST.md` created.** 82+ rows. Schema diff table complete. **Test coverage improved this session:** `PageToolbarActions.test.ts` (7), `SpatialPageView.test.ts` (8), `MainToolbar.test.ts` (7), `PageLayout.test.ts` (10), `RecentPagesCard.test.ts` (5), `FavoritePagesCard.test.ts` (5), `SelectedPagesCard.test.ts` (5), `useNoteContextMenu.test.ts` (5), `useCanvasActions.test.ts` (5), `DisplayNote.test.ts` (21), `DisplayArrow.test.ts` (12), `useCanvasContextMenu.test.ts` (6), `note-geometry.test.ts` (10), `useBoxSelection.test.ts` (6), `arrow-geometry.test.ts` (5), `useSpatialEditing.test.ts` (4), `selection.test.ts` (12). Remaining gaps: drag/resize end-to-end interaction, arrow creation/reconnection, sidebar properties cards.
8181
- **Left sidebar panels now load real data.** `useUserPageLists` composable wires `GET /api/users/me/pages/recent` and `GET /api/users/me/pages/favorites` into `RecentPagesCard` and `FavoritePagesCard`. Clear handlers call API-backed `clearRecent`/`clearFavorites`.
8282
- **Right sidebar properties panels exist but lack depth.** `NotePropertiesCard.vue`, `ArrowPropertiesCard.vue`, `PagePropertiesCard.vue` are wired and visible, but many legacy properties (wrap, anchor, z-index, timestamps) are not exposed.
83-
- **`MainToolbar.vue` extracted as standalone component.** `PageLayout.vue` now delegates to `MainToolbar.vue` for the header shell. Still missing: page action buttons (insert note/arrow, alignment, formatting), zoom controls other than reset, fit-to-screen, screenshot.
83+
- **`MainToolbar.vue` extracted as standalone component.** `PageLayout.vue` now delegates to `MainToolbar.vue` for the header shell. `PageToolbarActions.vue` provides insert note, insert arrow, zoom in/out, and fit-to-screen buttons. Still missing: alignment/formatting buttons, screenshot.
8484
- **Arrow labels fixed.** `DisplayArrow.vue` now uses `NoteTiptapEditor` on `Y.XmlFragment` instead of raw `<input>`. Proper collaborative rich-text editing.
8585
- **Arrow geometry and fitToScreen now read actual note heights.** `DisplayNote.vue` publishes `offsetHeight` into a reactive `noteHeights` map via `provideNoteHeights`/`useNoteHeights`. `DisplayArrow.vue` and `useCanvasActions.ts:fitToScreen` read heights from the map instead of hardcoding `80px`.
8686
- **Note drag `Teleport` overlay fixed.** Overlay now applies `scale(zoom)` and uses `posOverride` so the preview tracks the cursor correctly at all zoom levels.

docs/restart-plan/phase-6-spatial-polish.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Achieve parity with the legacy `/pages/:pageId` immersive spatial canvas experie
5353
| Item | Status | Notes |
5454
|------|--------|-------|
5555
| Fullscreen `PageEditorView.vue` shell | **Done** | `PageLayout.vue` replaces `DefaultLayout.vue` for `/pages/:pageId` via route meta |
56-
| `MainToolbar` (shadcn) | **Partial** | Standalone `MainToolbar.vue` extracted from `PageLayout.vue`. Still missing: page action buttons, insert dialogs, zoom other than reset/fit-to-screen |
56+
| `MainToolbar` (shadcn) | **Done** | Standalone `MainToolbar.vue` extracted from `PageLayout.vue`. `PageToolbarActions.vue` component provides insert note, insert arrow, zoom in/out, and fit-to-screen buttons. 7 tests in `PageToolbarActions.test.ts`. |
5757
| `LeftSidebar` (shadcn) — Recent, Favorites, Selected, Current path | **Partial** | Resizable collapsible sidebar shell exists. `CurrentPath` and `CollabStatus` wired. `RecentPagesCard` and `FavoritePagesCard` now load real data via `useUserPageLists` composable. `SelectedPagesCard` remains client-side only |
5858
| `RightSidebar` (shadcn) — Note/Page/Arrow properties | **Partial** | `NotePropertiesCard`, `ArrowPropertiesCard`, `PagePropertiesCard` are wired and visible. Many legacy fields (wrap, anchor, z-index, timestamps) not exposed. Snapshots, management, backlinks exist |
5959
| `TableContextMenu` (shadcn) — right-click on canvas | **Partial** | `CanvasContextMenu.vue` exists for canvas background. Per-note context menu (`NoteContextMenu.vue`) implemented and tested. |
@@ -110,7 +110,7 @@ Achieve parity with the legacy `/pages/:pageId` immersive spatial canvas experie
110110

111111
## Verification
112112

113-
- [ ] Each deliverable has a test (unit, component, or integration). **Partially improved.** New tests this session: `MainToolbar.test.ts` (7), `PageLayout.test.ts` (10), `RecentPagesCard.test.ts` (5), `FavoritePagesCard.test.ts` (5), `SelectedPagesCard.test.ts` (5), `useNoteContextMenu.test.ts` (5), `useCanvasActions.test.ts` (5), `DisplayNote.test.ts` (21), `DisplayArrow.test.ts` (12), `useCanvasContextMenu.test.ts` (6), `note-geometry.test.ts` (10), `useBoxSelection.test.ts` (6), `arrow-geometry.test.ts` (5), `useSpatialEditing.test.ts` (4), `selection.test.ts` (12). Remaining gaps: `SpatialPageView.vue` (no component/integration tests), drag/resize end-to-end interaction, arrow creation/reconnection flow, sidebar properties cards.
113+
- [ ] Each deliverable has a test (unit, component, or integration). **Partially improved.** New tests this session: `PageToolbarActions.test.ts` (7), `SpatialPageView.test.ts` (8), `MainToolbar.test.ts` (7), `PageLayout.test.ts` (10), `RecentPagesCard.test.ts` (5), `FavoritePagesCard.test.ts` (5), `SelectedPagesCard.test.ts` (5), `useNoteContextMenu.test.ts` (5), `useCanvasActions.test.ts` (5), `DisplayNote.test.ts` (21), `DisplayArrow.test.ts` (12), `useCanvasContextMenu.test.ts` (6), `note-geometry.test.ts` (10), `useBoxSelection.test.ts` (6), `arrow-geometry.test.ts` (5), `useSpatialEditing.test.ts` (4), `selection.test.ts` (12). Remaining gaps: drag/resize end-to-end interaction, arrow creation/reconnection flow, sidebar properties cards.
114114
- [ ] Phase 1 checklist is >80% marked done. **NOT MET.** Strict enforcement of the checklist's "Done = implemented + passing test" rule drops the true completion rate well below 80%.
115115

116116
---
@@ -124,7 +124,7 @@ Achieve parity with the legacy `/pages/:pageId` immersive spatial canvas experie
124124
- [x] All 8 dedicated page-state screens exist and are reachable. (`page-deleted`/`group-deleted`/`invited`/`rejected` are indistinguishable without richer API error codes.)
125125
- [ ] `DisplayNote.vue` matches legacy note visuals. **PARTIAL.** Colors use hardcoded 10-color map instead of legacy `colorNameToColorHex` with `lightenByRatio`. Drop zones, arrow handles, and frame styling are simplified. No custom scrollbar handling.
126126
- [ ] `DisplayArrow.vue` supports full legacy arrow behavior. **PARTIAL.** Curve/line bodies and heads work; line body now has rectangle-edge intersection. Interregional arrows don't transform coordinate spaces; `fakePos`/`looseEndpoint` are not rendered.
127-
- [x] `MainToolbar`, `LeftSidebar`, `RightSidebar`, and `TableContextMenu` are implemented as standalone shadcn components and visible on `/pages/:pageId`.
127+
- [x] `MainToolbar`, `LeftSidebar`, `RightSidebar`, and `TableContextMenu` are implemented as standalone shadcn components and visible on `/pages/:pageId`. `PageToolbarActions.vue` (insert note/arrow, zoom in/out, fit-to-screen) is wired into the toolbar actions slot.
128128
- [x] Sidebar panels (`RecentPages`, `FavoritePages`) display real data from API.
129129
- [x] Arrow geometry and `fitToScreen` read actual note heights instead of hardcoding `80px`.
130130
- [x] Per-note context menu (`NoteContextMenu.vue`) implemented with bring-to-front, send-to-back, and delete actions. Tested via `useNoteContextMenu.test.ts` (5 tests).

new-deepnotes/apps/web/src/features/pages/PageEditorView.vue

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import {
99
CardHeader,
1010
CardTitle,
1111
} from "@/components/ui/card";
12-
import { Plus } from "lucide-vue-next";
13-
1412
import { useSession } from "../auth/useSession";
13+
import PageToolbarActions from "../spatial/PageToolbarActions.vue";
1514
import SpatialPageView from "../spatial/SpatialPageView.vue";
1615
import { useUserTemplates } from "../spatial/useUserTemplates";
1716
import PageEditorBacklinksCard from "./PageEditorBacklinksCard.vue";
@@ -236,15 +235,13 @@ onMounted(() => {
236235

237236
<!-- === Toolbar actions === -->
238237
<template #toolbar-actions>
239-
<Button
240-
variant="ghost"
241-
size="sm"
242-
class="gap-1"
243-
@click="spatialViewRef?.insertNoteAtCenter()"
244-
>
245-
<Plus class="h-4 w-4" />
246-
<span class="hidden sm:inline">Note</span>
247-
</Button>
238+
<PageToolbarActions
239+
@insert-note="spatialViewRef?.insertNoteAtCenter()"
240+
@insert-arrow="spatialViewRef?.insertArrowBetweenSelected()"
241+
@zoom-in="spatialViewRef?.zoomIn()"
242+
@zoom-out="spatialViewRef?.zoomOut()"
243+
@fit-to-screen="spatialViewRef?.fitToScreen()"
244+
/>
248245
</template>
249246

250247
<!-- === Toolbar center: breadcrumb path === -->
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { describe, expect, it } from "vitest";
2+
import { mount } from "@vue/test-utils";
3+
4+
import PageToolbarActions from "./PageToolbarActions.vue";
5+
6+
describe("PageToolbarActions", () => {
7+
function mountComponent() {
8+
return mount(PageToolbarActions);
9+
}
10+
11+
it("renders insert note button", () => {
12+
const wrapper = mountComponent();
13+
const btn = wrapper.find('[data-testid="toolbar-insert-note"]');
14+
expect(btn.exists()).toBe(true);
15+
expect(btn.text()).toContain("Note");
16+
});
17+
18+
it("renders insert arrow button", () => {
19+
const wrapper = mountComponent();
20+
const btn = wrapper.find('[data-testid="toolbar-insert-arrow"]');
21+
expect(btn.exists()).toBe(true);
22+
expect(btn.text()).toContain("Arrow");
23+
});
24+
25+
it("emits insert-note on click", async () => {
26+
const wrapper = mountComponent();
27+
await wrapper.find('[data-testid="toolbar-insert-note"]').trigger("click");
28+
expect(wrapper.emitted("insert-note")).toHaveLength(1);
29+
});
30+
31+
it("emits insert-arrow on click", async () => {
32+
const wrapper = mountComponent();
33+
await wrapper.find('[data-testid="toolbar-insert-arrow"]').trigger("click");
34+
expect(wrapper.emitted("insert-arrow")).toHaveLength(1);
35+
});
36+
37+
it("emits zoom-in on click", async () => {
38+
const wrapper = mountComponent();
39+
await wrapper.find('[data-testid="toolbar-zoom-in"]').trigger("click");
40+
expect(wrapper.emitted("zoom-in")).toHaveLength(1);
41+
});
42+
43+
it("emits zoom-out on click", async () => {
44+
const wrapper = mountComponent();
45+
await wrapper.find('[data-testid="toolbar-zoom-out"]').trigger("click");
46+
expect(wrapper.emitted("zoom-out")).toHaveLength(1);
47+
});
48+
49+
it("emits fit-to-screen on click", async () => {
50+
const wrapper = mountComponent();
51+
await wrapper.find('[data-testid="toolbar-fit-to-screen"]').trigger("click");
52+
expect(wrapper.emitted("fit-to-screen")).toHaveLength(1);
53+
});
54+
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<script setup lang="ts">
2+
import { Button } from "@/components/ui/button";
3+
import {
4+
Plus,
5+
ArrowRight,
6+
ZoomIn,
7+
ZoomOut,
8+
Maximize,
9+
} from "lucide-vue-next";
10+
11+
const emit = defineEmits<{
12+
"insert-note": [];
13+
"insert-arrow": [];
14+
"zoom-in": [];
15+
"zoom-out": [];
16+
"fit-to-screen": [];
17+
}>();
18+
</script>
19+
20+
<template>
21+
<div class="flex flex-none items-center gap-1">
22+
<Button
23+
variant="ghost"
24+
size="sm"
25+
class="gap-1"
26+
data-testid="toolbar-insert-note"
27+
@click="emit('insert-note')"
28+
>
29+
<Plus class="h-4 w-4" />
30+
<span class="hidden sm:inline">Note</span>
31+
</Button>
32+
33+
<Button
34+
variant="ghost"
35+
size="sm"
36+
class="gap-1"
37+
data-testid="toolbar-insert-arrow"
38+
@click="emit('insert-arrow')"
39+
>
40+
<ArrowRight class="h-4 w-4" />
41+
<span class="hidden sm:inline">Arrow</span>
42+
</Button>
43+
44+
<div class="bg-border mx-1 h-4 w-px" />
45+
46+
<Button
47+
variant="ghost"
48+
size="icon"
49+
class="h-8 w-8"
50+
title="Zoom in"
51+
data-testid="toolbar-zoom-in"
52+
@click="emit('zoom-in')"
53+
>
54+
<ZoomIn class="h-4 w-4" />
55+
</Button>
56+
57+
<Button
58+
variant="ghost"
59+
size="icon"
60+
class="h-8 w-8"
61+
title="Zoom out"
62+
data-testid="toolbar-zoom-out"
63+
@click="emit('zoom-out')"
64+
>
65+
<ZoomOut class="h-4 w-4" />
66+
</Button>
67+
68+
<Button
69+
variant="ghost"
70+
size="icon"
71+
class="h-8 w-8"
72+
title="Fit to screen"
73+
data-testid="toolbar-fit-to-screen"
74+
@click="emit('fit-to-screen')"
75+
>
76+
<Maximize class="h-4 w-4" />
77+
</Button>
78+
</div>
79+
</template>

new-deepnotes/apps/web/src/features/spatial/SpatialPageView.test.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { afterEach, describe, expect, it, vi } from "vitest";
1+
import { afterEach, describe, expect, it } from "vitest";
22
import { mount } from "@vue/test-utils";
33
import * as Y from "yjs";
44

@@ -18,11 +18,6 @@ function setupDocWithNotes(...positions: { x: number; y: number }[]) {
1818
return { ydoc, ids };
1919
}
2020

21-
function mockPointerCapture(el: Element) {
22-
const htmlEl = el as HTMLDivElement;
23-
htmlEl.setPointerCapture = vi.fn();
24-
htmlEl.releasePointerCapture = vi.fn();
25-
}
2621

2722
describe("SpatialPageView", () => {
2823
let wrapper: ReturnType<typeof mount> | undefined;
@@ -98,4 +93,34 @@ describe("SpatialPageView", () => {
9893

9994
expect(typeof (wrapper.vm as any).insertNoteAtCenter).toBe("function");
10095
});
96+
97+
it("exposes zoomIn method", () => {
98+
const { ydoc } = setupDocWithNotes();
99+
wrapper = mount(SpatialPageView, {
100+
props: { ydoc },
101+
global: { stubs: { Teleport: true } },
102+
});
103+
104+
expect(typeof (wrapper.vm as any).zoomIn).toBe("function");
105+
});
106+
107+
it("exposes zoomOut method", () => {
108+
const { ydoc } = setupDocWithNotes();
109+
wrapper = mount(SpatialPageView, {
110+
props: { ydoc },
111+
global: { stubs: { Teleport: true } },
112+
});
113+
114+
expect(typeof (wrapper.vm as any).zoomOut).toBe("function");
115+
});
116+
117+
it("exposes insertArrowBetweenSelected method", () => {
118+
const { ydoc } = setupDocWithNotes();
119+
wrapper = mount(SpatialPageView, {
120+
props: { ydoc },
121+
global: { stubs: { Teleport: true } },
122+
});
123+
124+
expect(typeof (wrapper.vm as any).insertArrowBetweenSelected).toBe("function");
125+
});
101126
});

new-deepnotes/apps/web/src/features/spatial/SpatialPageView.vue

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ const canvasRef = ref<{
4444
zoom: number;
4545
rootEl: HTMLElement | null;
4646
resetView: () => void;
47+
zoomIn: () => void;
48+
zoomOut: () => void;
4749
fitToScreen: (bounds: { minX: number; minY: number; maxX: number; maxY: number }, padding?: number) => void;
4850
} | null>(null);
4951
@@ -181,8 +183,29 @@ function insertNoteAtCenter() {
181183
createNoteAt(camX, camY, props.defaultNoteTemplate);
182184
}
183185
186+
function insertArrowBetweenSelected() {
187+
const noteIds = selection.selectedOfKind('note');
188+
if (noteIds.length === 2) {
189+
createArrow(noteIds[0]!, noteIds[1]!, props.defaultArrowTemplate);
190+
return true;
191+
}
192+
return false;
193+
}
194+
195+
function zoomIn() {
196+
canvasRef.value?.zoomIn();
197+
}
198+
199+
function zoomOut() {
200+
canvasRef.value?.zoomOut();
201+
}
202+
184203
defineExpose({
185204
insertNoteAtCenter,
205+
insertArrowBetweenSelected,
206+
zoomIn,
207+
zoomOut,
208+
fitToScreen,
186209
});
187210
188211
const {

new-deepnotes/apps/web/src/features/spatial/SpatialWorldCanvas.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ const {
1414
onPointerMove,
1515
onPointerUp,
1616
resetView,
17+
zoomIn,
18+
zoomOut,
1719
fitToScreen,
1820
} = useSpatialViewport(rootRef);
1921
2022
defineExpose({
2123
resetView,
2224
fitToScreen,
25+
zoomIn,
26+
zoomOut,
2327
camX,
2428
camY,
2529
zoom,

new-deepnotes/apps/web/src/features/spatial/clipboard.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ async function writeToClipboard(payload: ClipboardPayload): Promise<void> {
8989
'text/plain': new Blob([json], { type: 'text/plain' }),
9090
});
9191
await navigator.clipboard.write([clipboardItem]);
92-
} catch (e) {
92+
} catch {
9393
// Fallback to localStorage if clipboard API fails
9494
try {
9595
localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
@@ -113,7 +113,7 @@ async function readFromClipboard(): Promise<ClipboardPayload | null> {
113113
}
114114
}
115115
}
116-
} catch (e) {
116+
} catch {
117117
// Fallback to localStorage if clipboard API fails
118118
try {
119119
const stored = localStorage.getItem(STORAGE_KEY);

0 commit comments

Comments
 (0)