Skip to content

Commit 0ac0373

Browse files
committed
refactor(scratchpad): unify editor transactions and interaction state
1 parent 5ffd0e1 commit 0ac0373

File tree

18 files changed

+1731
-579
lines changed

18 files changed

+1731
-579
lines changed

content/docs/api/0-26-2/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Memos API Reference (0.26.x)
55

66
## Overview
77

8-
This reference matches the Memos `0.26.x` release line.
8+
This reference matches the Memos `0.26.x` release.
99

1010
## Base URL
1111

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
## Background & Context
2+
3+
The `scratchpad` feature is a browser-based note board for creating free-positioned cards, attaching files, and saving selected cards into a Memos instance. The current implementation grew from a simple local card board into an infinite-canvas-like surface with attachment previews, Memos connectivity, and viewport controls. The feature now spans UI rendering, editor interactions, local persistence, IndexedDB attachment storage, and remote save orchestration.
4+
5+
## Issue Statement
6+
7+
Scratchpad editor behavior is distributed across page, hook, and component layers without a single document state boundary, causing viewport state, selection state, item mutations, persistence timing, attachment handling, and remote-save transitions to be updated through overlapping ad hoc callbacks instead of a unified editor model.
8+
9+
## Current State
10+
11+
- [src/hooks/use-scratchpad.ts](/Users/steven/Projects/usememos/dotcom/src/hooks/use-scratchpad.ts#L62) owns local document data, selection state, instance storage, attachment persistence, save orchestration, keyboard shortcuts, and UI flags in one hook.
12+
- [src/hooks/use-scratchpad.ts](/Users/steven/Projects/usememos/dotcom/src/hooks/use-scratchpad.ts#L161) creates items directly inside the orchestration hook, while [src/hooks/use-scratchpad.ts](/Users/steven/Projects/usememos/dotcom/src/hooks/use-scratchpad.ts#L171) handles file ingestion and decides whether files attach to an existing item or create a new item.
13+
- [src/hooks/use-scratchpad.ts](/Users/steven/Projects/usememos/dotcom/src/hooks/use-scratchpad.ts#L245) updates remote sync state inline with local item mutations and instance refresh logic.
14+
- [src/components/scratch/workspace.tsx](/Users/steven/Projects/usememos/dotcom/src/components/scratch/workspace.tsx#L44) owns viewport state, pan/zoom math, coordinate transforms, local viewport persistence, paste handling, drag-and-drop routing, and canvas HUD rendering.
15+
- [src/components/scratch/card-item.tsx](/Users/steven/Projects/usememos/dotcom/src/components/scratch/card-item.tsx#L47) owns card drag/resize interaction, textarea sizing, attachment preview loading from IndexedDB, and sync-status display.
16+
- [src/app/scratchpad/page.tsx](/Users/steven/Projects/usememos/dotcom/src/app/scratchpad/page.tsx#L13) wires a large callback surface directly into `Workspace` and mixes top-bar UI with feature orchestration.
17+
- [src/lib/scratch/storage.ts](/Users/steven/Projects/usememos/dotcom/src/lib/scratch/storage.ts#L226) persists items, but viewport persistence is not modeled alongside the document. [src/lib/scratch/storage.ts](/Users/steven/Projects/usememos/dotcom/src/lib/scratch/storage.ts#L275) clears items using a payload shape that differs from the versioned envelope used elsewhere.
18+
- [src/lib/scratch/types.ts](/Users/steven/Projects/usememos/dotcom/src/lib/scratch/types.ts#L59) models cards and sync state, but there is no explicit editor/document or viewport type for the scratchpad feature.
19+
20+
## Non-Goals
21+
22+
- Do not redesign the Memos API protocol in [src/lib/scratch/api.ts](/Users/steven/Projects/usememos/dotcom/src/lib/scratch/api.ts).
23+
- Do not introduce collaborative sync, undo/redo history, connectors, or freehand drawing in this change.
24+
- Do not replace card rendering with HTML `<canvas>` or WebGL rendering in this change.
25+
- Do not change the existing persisted card schema in a way that invalidates stored user content.
26+
27+
## Open Questions
28+
29+
- Should viewport state persist with the document or remain transient UI state? (default: persist viewport locally with the document experience)
30+
- Should scratchpad orchestration expose one public hook or multiple hooks at the page boundary? (default: keep one public feature hook composed from smaller internal hooks)
31+
- Should attachment preview loading remain card-local or move to a shared cache layer? (default: keep previews card-local for now, but isolate them from editor state)
32+
33+
## Scope
34+
35+
L — the current state spans multiple files with cross-cutting editor behavior, no explicit editor/document model, and at least two viable architectural directions (continued callback accretion vs. reducer/editor refactor).
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
## References
2+
3+
- [tldraw README](https://github.com/tldraw/tldraw)
4+
- [tldraw Store docs](https://tldraw.dev/reference/store/Store)
5+
- [React Flow: Panning and Zooming](https://reactflow.dev/learn/concepts/the-viewport)
6+
- [Excalidraw API: excalidrawAPI](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/excalidraw-api)
7+
8+
## Industry Baseline
9+
10+
tldraw positions an infinite-canvas app around a central engine and explicitly separates extensible canvas behavior from UI surface concerns; its README describes a “feature-complete infinite canvas engine” and “DOM canvas” support, while its Store docs define a central reactive container with history, schema validation, side effects, and scoped persistence. React Flow documents viewport as a first-class concept independent from nodes and recommends explicit pan/zoom/select control schemes rather than scroll-container behavior. Excalidraw exposes scene and app state separately through `updateScene`, `getSceneElements`, `getAppState`, and history access, which reflects a clear editor API boundary between document content and editor state.
11+
12+
## Research Summary
13+
14+
Across these projects, the recurring pattern is not “use `<canvas>` everywhere,” but “define a central editor model.” Document content, viewport, and editor UI state are modeled explicitly; rendering technology is a downstream choice. The common baseline is a state container or editor API that owns mutations, while components consume selectors and dispatch commands. Viewport is handled as camera math rather than scroll offsets, and controls are treated as external UI around that camera. This fits the current scratchpad codebase because the main maintenance problem is architectural diffusion of state and commands, not raw rendering performance.
15+
16+
## Design Goals
17+
18+
- Scratchpad document mutations must flow through pure editor helpers or reducer actions so item creation, selection, z-index changes, and viewport updates are testable and inspectable without DOM access.
19+
- Viewport state must be modeled explicitly and persisted through one storage abstraction rather than local component keys.
20+
- `useScratchpad` must stop owning low-level item mutation rules directly; it should orchestrate persistence, attachment IO, and remote save flows on top of editor commands.
21+
- Page-level components must consume a narrower surface than the current callback fan-out in [src/app/scratchpad/page.tsx](/Users/steven/Projects/usememos/dotcom/src/app/scratchpad/page.tsx).
22+
- Existing persisted cards and instance configuration must continue loading without migration loss.
23+
24+
## Non-Goals
25+
26+
- Do not replace DOM card rendering with a vector or bitmap renderer.
27+
- Do not add collaborative transport, operational transforms, or multiplayer presence.
28+
- Do not add undo/redo history, lasso selection, or connectors in this refactor.
29+
- Do not redesign Memos save semantics or attachment upload endpoints.
30+
31+
## Proposed Design
32+
33+
Introduce an editor layer under `src/lib/scratch/` that defines scratchpad document state, viewport state, reducer actions, selectors, and coordinate helpers. This layer will own item factories, dirty-state transitions, z-index sequencing, selection logic, and viewport math. The design follows the central-store pattern shown in tldraw’s Store docs and the scene/app-state split surfaced by Excalidraw’s API.
34+
35+
Move viewport persistence into [src/lib/scratch/storage.ts](/Users/steven/Projects/usememos/dotcom/src/lib/scratch/storage.ts) so document-adjacent state is saved through one abstraction instead of ad hoc `localStorage` keys inside rendering components. Keep item persistence backward-compatible by preserving the existing item envelope and extending storage with a dedicated viewport API.
36+
37+
Refactor [src/hooks/use-scratchpad.ts](/Users/steven/Projects/usememos/dotcom/src/hooks/use-scratchpad.ts) into a composition root over a smaller editor hook. The internal editor hook will manage reducer state, hydration, debounced persistence, and keyboard behavior. `useScratchpad` will remain the public feature hook and will compose editor commands with IndexedDB attachment IO and Memos instance/save orchestration.
38+
39+
Thin [src/components/scratch/workspace.tsx](/Users/steven/Projects/usememos/dotcom/src/components/scratch/workspace.tsx) into a viewport interaction component that receives `viewport`, `setViewport`, item data, and editor commands via props. It will keep transient pointer-session state only. This matches React Flow’s viewport-first model and keeps camera logic reusable.
40+
41+
Extract page-level chrome into a dedicated toolbar component so [src/app/scratchpad/page.tsx](/Users/steven/Projects/usememos/dotcom/src/app/scratchpad/page.tsx) becomes a composition shell instead of a feature controller. Keep attachment preview loading card-local for now, but isolate it from editor/document state because it is presentational asset hydration rather than core editor state.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
## Execution Log
2+
3+
### T1: Add scratchpad editor primitives
4+
5+
**Status**: Completed
6+
**Files Changed**:
7+
- `src/lib/scratch/types.ts`
8+
- `src/lib/scratch/storage.ts`
9+
- `src/lib/scratch/editor.ts`
10+
- `src/lib/scratch/viewport.ts`
11+
**Validation**: `pnpm exec tsc --noEmit` — PASS
12+
**Path Corrections**: None
13+
**Deviations**: None
14+
15+
### T2: Move feature state into an editor hook
16+
17+
**Status**: Completed
18+
**Files Changed**:
19+
- `src/hooks/use-scratchpad-editor.ts`
20+
- `src/hooks/use-scratchpad.ts`
21+
**Validation**: `pnpm exec tsc --noEmit` — PASS
22+
**Path Corrections**: None
23+
**Deviations**: None
24+
25+
### T3: Thin page/workspace UI around editor state
26+
27+
**Status**: Completed
28+
**Files Changed**:
29+
- `src/components/scratch/workspace.tsx`
30+
- `src/components/scratch/scratchpad-toolbar.tsx`
31+
- `src/app/scratchpad/page.tsx`
32+
- `src/components/scratch/card-item.tsx`
33+
- `docs/issues/2026-04-01-scratchpad-editor-architecture/definition.md`
34+
- `docs/issues/2026-04-01-scratchpad-editor-architecture/design.md`
35+
- `docs/issues/2026-04-01-scratchpad-editor-architecture/plan.md`
36+
**Validation**: `pnpm exec biome check src/app/scratchpad/page.tsx src/components/scratch/workspace.tsx src/components/scratch/card-item.tsx src/components/scratch/scratchpad-toolbar.tsx src/hooks/use-scratchpad.ts src/hooks/use-scratchpad-editor.ts src/lib/scratch/editor.ts src/lib/scratch/viewport.ts src/lib/scratch/storage.ts src/lib/scratch/types.ts docs/issues/2026-04-01-scratchpad-editor-architecture/definition.md docs/issues/2026-04-01-scratchpad-editor-architecture/design.md docs/issues/2026-04-01-scratchpad-editor-architecture/plan.md && pnpm exec tsc --noEmit` — PASS
37+
**Path Corrections**: None
38+
**Deviations**: None
39+
40+
## Completion Declaration
41+
42+
All tasks completed successfully
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
## Task List
2+
3+
T1: Add scratchpad editor primitives [L] — T2: Move feature state into an editor hook [M] — T3: Thin page/workspace UI around editor state [L]
4+
5+
### T1: Add scratchpad editor primitives [L]
6+
7+
**Objective**: Introduce a central scratchpad editor model for items, selection, and viewport.
8+
**Size**: L
9+
**Files**:
10+
- Create: `src/lib/scratch/editor.ts`
11+
- Create: `src/lib/scratch/viewport.ts`
12+
- Modify: `src/lib/scratch/types.ts`
13+
- Modify: `src/lib/scratch/storage.ts`
14+
**Implementation**:
15+
1. In `src/lib/scratch/types.ts`, add explicit viewport typing used by document/editor state.
16+
2. In `src/lib/scratch/editor.ts`, add editor state, reducer actions, selectors, item factory helpers, dirty-state helpers, and z-index/selection logic.
17+
3. In `src/lib/scratch/viewport.ts`, add pure pan/zoom and screen-to-canvas math helpers.
18+
4. In `src/lib/scratch/storage.ts`, add viewport storage and align item clearing with the versioned item envelope.
19+
**Boundaries**: Do not change Memos API behavior or card rendering markup here.
20+
**Dependencies**: None
21+
**Expected Outcome**: Scratchpad has reusable editor/document and viewport logic outside of React components.
22+
**Validation**: `pnpm exec tsc --noEmit` — exits 0
23+
24+
### T2: Move feature state into an editor hook [M]
25+
26+
**Objective**: Isolate scratchpad document state, hydration, persistence, and local editing commands behind a dedicated hook.
27+
**Size**: M
28+
**Files**:
29+
- Create: `src/hooks/use-scratchpad-editor.ts`
30+
- Modify: `src/hooks/use-scratchpad.ts`
31+
**Implementation**:
32+
1. In `src/hooks/use-scratchpad-editor.ts`, use the new reducer/state helpers to hydrate items and viewport, persist them, and expose item/viewport commands plus local keyboard/delete behavior.
33+
2. In `src/hooks/use-scratchpad.ts`, compose the editor hook with instance storage and remote-save orchestration while shrinking direct document-mutation logic.
34+
**Boundaries**: Do not redesign connection testing or remote save semantics.
35+
**Dependencies**: T1
36+
**Expected Outcome**: `useScratchpad` becomes a composition root over a narrower editor command surface.
37+
**Validation**: `pnpm exec tsc --noEmit` — exits 0
38+
39+
### T3: Thin page/workspace UI around editor state [L]
40+
41+
**Objective**: Make scratchpad components consume explicit editor state instead of owning editor logic ad hoc.
42+
**Size**: L
43+
**Files**:
44+
- Create: `src/components/scratch/scratchpad-toolbar.tsx`
45+
- Modify: `src/components/scratch/workspace.tsx`
46+
- Modify: `src/app/scratchpad/page.tsx`
47+
- Modify: `src/components/scratch/card-item.tsx`
48+
**Implementation**:
49+
1. In `src/components/scratch/workspace.tsx`, consume external viewport state and pure viewport helpers while keeping only transient pointer-session state locally.
50+
2. In `src/components/scratch/scratchpad-toolbar.tsx`, extract top-bar UI and menu chrome from the page component.
51+
3. In `src/app/scratchpad/page.tsx`, reduce the page to feature composition.
52+
4. In `src/components/scratch/card-item.tsx`, keep drag/resize behavior compatible with externally managed viewport scale.
53+
**Boundaries**: Do not add new editor capabilities such as connectors, drawing tools, or collaboration.
54+
**Dependencies**: T1, T2
55+
**Expected Outcome**: Page-level code is thinner and editor state flow is explicit at the component boundary.
56+
**Validation**: `pnpm exec biome check src/app/scratchpad/page.tsx src/components/scratch/workspace.tsx src/components/scratch/card-item.tsx src/components/scratch/scratchpad-toolbar.tsx src/hooks/use-scratchpad.ts src/hooks/use-scratchpad-editor.ts src/lib/scratch/editor.ts src/lib/scratch/viewport.ts src/lib/scratch/storage.ts src/lib/scratch/types.ts && pnpm exec tsc --noEmit` — exits 0
57+
58+
## Out-of-Scope Tasks
59+
60+
- Add undo/redo history
61+
- Add lasso or marquee multi-select
62+
- Replace DOM card rendering with `<canvas>` or WebGL
63+
- Add multiplayer or collaborative syncing

next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./.next/types/routes.d.ts";
3+
import "./.next/dev/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

openapi/latest.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2396,7 +2396,9 @@ components:
23962396
backgroundColor:
23972397
allOf:
23982398
- $ref: '#/components/schemas/Color'
2399-
description: Background color for the tag label.
2399+
description: |-
2400+
Optional background color for the tag label.
2401+
When unset, the default tag color is used.
24002402
blurContent:
24012403
type: boolean
24022404
description: Whether memos with this tag should have their content blurred.

0 commit comments

Comments
 (0)