Skip to content
Merged
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
2 changes: 1 addition & 1 deletion content/docs/api/0-26-2/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Memos API Reference (0.26.x)

## Overview

This reference matches the Memos `0.26.x` release line.
This reference matches the Memos `0.26.x` release.

## Base URL

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## Background & Context

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.

## Issue Statement

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.

## Current State

- [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.
- [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.
- [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.
- [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.
- [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.
- [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.
- [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.
- [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.

## Non-Goals

- Do not redesign the Memos API protocol in [src/lib/scratch/api.ts](/Users/steven/Projects/usememos/dotcom/src/lib/scratch/api.ts).
- Do not introduce collaborative sync, undo/redo history, connectors, or freehand drawing in this change.
- Do not replace card rendering with HTML `<canvas>` or WebGL rendering in this change.
- Do not change the existing persisted card schema in a way that invalidates stored user content.

## Open Questions

- Should viewport state persist with the document or remain transient UI state? (default: persist viewport locally with the document experience)
- 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)
- 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)

## Scope

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).
41 changes: 41 additions & 0 deletions docs/issues/2026-04-01-scratchpad-editor-architecture/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## References

- [tldraw README](https://github.com/tldraw/tldraw)
- [tldraw Store docs](https://tldraw.dev/reference/store/Store)
- [React Flow: Panning and Zooming](https://reactflow.dev/learn/concepts/the-viewport)
- [Excalidraw API: excalidrawAPI](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props/excalidraw-api)

## Industry Baseline

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.

## Research Summary

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.

## Design Goals

- 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.
- Viewport state must be modeled explicitly and persisted through one storage abstraction rather than local component keys.
- `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.
- 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).
- Existing persisted cards and instance configuration must continue loading without migration loss.

## Non-Goals

- Do not replace DOM card rendering with a vector or bitmap renderer.
- Do not add collaborative transport, operational transforms, or multiplayer presence.
- Do not add undo/redo history, lasso selection, or connectors in this refactor.
- Do not redesign Memos save semantics or attachment upload endpoints.

## Proposed Design

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.

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.

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.

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.

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.
42 changes: 42 additions & 0 deletions docs/issues/2026-04-01-scratchpad-editor-architecture/execution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## Execution Log

### T1: Add scratchpad editor primitives

**Status**: Completed
**Files Changed**:
- `src/lib/scratch/types.ts`
- `src/lib/scratch/storage.ts`
- `src/lib/scratch/editor.ts`
- `src/lib/scratch/viewport.ts`
**Validation**: `pnpm exec tsc --noEmit` — PASS
**Path Corrections**: None
**Deviations**: None

### T2: Move feature state into an editor hook

**Status**: Completed
**Files Changed**:
- `src/hooks/use-scratchpad-editor.ts`
- `src/hooks/use-scratchpad.ts`
**Validation**: `pnpm exec tsc --noEmit` — PASS
**Path Corrections**: None
**Deviations**: None

### T3: Thin page/workspace UI around editor state

**Status**: Completed
**Files Changed**:
- `src/components/scratch/workspace.tsx`
- `src/components/scratch/scratchpad-toolbar.tsx`
- `src/app/scratchpad/page.tsx`
- `src/components/scratch/card-item.tsx`
- `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`
**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
**Path Corrections**: None
**Deviations**: None

## Completion Declaration

All tasks completed successfully
63 changes: 63 additions & 0 deletions docs/issues/2026-04-01-scratchpad-editor-architecture/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
## Task List

T1: Add scratchpad editor primitives [L] — T2: Move feature state into an editor hook [M] — T3: Thin page/workspace UI around editor state [L]

### T1: Add scratchpad editor primitives [L]

**Objective**: Introduce a central scratchpad editor model for items, selection, and viewport.
**Size**: L
**Files**:
- Create: `src/lib/scratch/editor.ts`
- Create: `src/lib/scratch/viewport.ts`
- Modify: `src/lib/scratch/types.ts`
- Modify: `src/lib/scratch/storage.ts`
**Implementation**:
1. In `src/lib/scratch/types.ts`, add explicit viewport typing used by document/editor state.
2. In `src/lib/scratch/editor.ts`, add editor state, reducer actions, selectors, item factory helpers, dirty-state helpers, and z-index/selection logic.
3. In `src/lib/scratch/viewport.ts`, add pure pan/zoom and screen-to-canvas math helpers.
4. In `src/lib/scratch/storage.ts`, add viewport storage and align item clearing with the versioned item envelope.
**Boundaries**: Do not change Memos API behavior or card rendering markup here.
**Dependencies**: None
**Expected Outcome**: Scratchpad has reusable editor/document and viewport logic outside of React components.
**Validation**: `pnpm exec tsc --noEmit` — exits 0

### T2: Move feature state into an editor hook [M]

**Objective**: Isolate scratchpad document state, hydration, persistence, and local editing commands behind a dedicated hook.
**Size**: M
**Files**:
- Create: `src/hooks/use-scratchpad-editor.ts`
- Modify: `src/hooks/use-scratchpad.ts`
**Implementation**:
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.
2. In `src/hooks/use-scratchpad.ts`, compose the editor hook with instance storage and remote-save orchestration while shrinking direct document-mutation logic.
**Boundaries**: Do not redesign connection testing or remote save semantics.
**Dependencies**: T1
**Expected Outcome**: `useScratchpad` becomes a composition root over a narrower editor command surface.
**Validation**: `pnpm exec tsc --noEmit` — exits 0

### T3: Thin page/workspace UI around editor state [L]

**Objective**: Make scratchpad components consume explicit editor state instead of owning editor logic ad hoc.
**Size**: L
**Files**:
- Create: `src/components/scratch/scratchpad-toolbar.tsx`
- Modify: `src/components/scratch/workspace.tsx`
- Modify: `src/app/scratchpad/page.tsx`
- Modify: `src/components/scratch/card-item.tsx`
**Implementation**:
1. In `src/components/scratch/workspace.tsx`, consume external viewport state and pure viewport helpers while keeping only transient pointer-session state locally.
2. In `src/components/scratch/scratchpad-toolbar.tsx`, extract top-bar UI and menu chrome from the page component.
3. In `src/app/scratchpad/page.tsx`, reduce the page to feature composition.
4. In `src/components/scratch/card-item.tsx`, keep drag/resize behavior compatible with externally managed viewport scale.
**Boundaries**: Do not add new editor capabilities such as connectors, drawing tools, or collaboration.
**Dependencies**: T1, T2
**Expected Outcome**: Page-level code is thinner and editor state flow is explicit at the component boundary.
**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

## Out-of-Scope Tasks

- Add undo/redo history
- Add lasso or marquee multi-select
- Replace DOM card rendering with `<canvas>` or WebGL
- Add multiplayer or collaborative syncing
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
4 changes: 3 additions & 1 deletion openapi/latest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2396,7 +2396,9 @@ components:
backgroundColor:
allOf:
- $ref: '#/components/schemas/Color'
description: Background color for the tag label.
description: |-
Optional background color for the tag label.
When unset, the default tag color is used.
blurContent:
type: boolean
description: Whether memos with this tag should have their content blurred.
Expand Down
Loading
Loading