mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-20 17:43:26 +00:00
3bf1b60365
Adds a drag-to-crop modal on top of the AttachmentImage toolbar
introduced in TASK-879. The /transform endpoint already accepted
the "crop" operation shape from TASK-879 — this PR wires the editor
UI plus the supporting tests.
Editor:
- attachment-crop-modal.ts (new): pure-DOM crop modal in the same
style as the existing image lightbox. Returns a Promise that
resolves to the crop rect in ORIGINAL-IMAGE pixel coordinates
when the user clicks Apply, or null on cancel / dismiss /
image-load failure.
- Image fits to a centered <dialog> via flex layout; backdrop
click and Esc both cancel cleanly.
- Crop rectangle starts at 80% of the image, centered. Body is
a "move" handle; four corner handles resize.
- Aspect presets: Free, 1:1, 4:3, 16:9. Preset clicks snap the
current rect to the new ratio while preserving its center;
subsequent corner drags clamp to the locked ratio.
- Pointer events (touch + mouse for free) with setPointerCapture
so drag continues even if the cursor leaves the handle.
- Coordinate translation: rect in preview-pixel space →
naturalWidth / offsetWidth scale → original-image pixel
space. Result is clamped to natural bounds so a fractional-
rounding overrun doesn't push the rect off-image.
- attachment-image.ts: extracts swapNodeUuid() helper from
runRotate so runCrop can share the setNodeMarkup +
invalidate-old-metadata flow. The toolbar gains a fourth
button (⌶ Crop…) that opens the modal pointed at the original
variant. Per-format gating (refreshToolbarState) treats the
crop button identically to the rotate trio — both go through
/transform, so a libvips-only format (e.g. WebP on the pure-Go
build) disables the whole toolbar with the same explanatory
tooltip.
- app.css: full styling for the crop modal — header with aspect
toolbar, image stage with shadow-cutout overlay around the
crop rect, four corner handles, footer with Cancel + Apply.
Uses the existing CSS-variable palette so light/dark mode
track automatically.
Server tests (3 new):
- TestTransform_CropProducesNewBlobAtRectDimensions: end-to-end
PNG crop, verify the response dimensions AND that the served
bytes decode at the same dimensions (guards against an
encode-pipeline off-by-one).
- TestTransform_CropClipsToImageBounds: rect that extends past
the image boundary clips rather than 400ing — the editor's
rounding can produce rect+1px past natural width/height in
rare fractional-scale cases, and the processor's Crop
intersects with image bounds for exactly this reason.
- TestTransform_CropRejectsBadRect: missing rect, zero width,
negative xy, rect entirely outside → 400.
Parent: PLAN-866. Closes the editor-side image-tools track on top of
TASK-878 (Processor) and TASK-879 (rotate / transform endpoint).
Pad Web UI
SvelteKit 2 + Svelte 5 frontend for Pad, compiled to static files and embedded into the Go binary.
Development
npm install
npm run dev # Dev server at localhost:5173 (proxies API to localhost:7777)
npm run build # Production build to build/
npm run check # Type checking with svelte-check
When developing, run the Go backend separately with make dev from the project root.
Building for Production
Do not build in isolation. Always use make build from the project root — this builds the web frontend, then compiles the Go binary with the build output embedded via //go:embed.
Stack
- Svelte 5 with runes (
$state,$derived,$effect) - SvelteKit 2 with
adapter-static(SPA mode) - Tiptap block editor with markdown round-trip
- svelte-dnd-action for drag-and-drop in board/list views
- SSE for real-time updates
- TypeScript throughout
Structure
src/
routes/ SvelteKit pages
+layout.svelte App shell (sidebar + main)
+page.svelte Landing/redirect
[workspace]/
+page.svelte Dashboard (collections, phases, activity)
+layout.svelte SSE connection per workspace
[collection]/
+page.svelte Collection view (board/list)
[collection]/[item]/
+page.svelte Item detail + editor
conventions/ Purpose-built conventions page
playbooks/ Purpose-built playbooks page
settings/ Workspace settings
lib/
api/client.ts HTTP API client
components/
layout/ Sidebar, navigation
editor/ Tiptap editor, raw markdown editor
fields/ FieldEditor, relation picker
items/ ItemCard, ItemDetail
collections/ BoardView, ListView
common/ StatusBadge, badges, modals
search/ CommandPalette
activity/ ActivityFeed
stores/ Svelte 5 reactive stores
workspace.svelte.ts Workspace state
collections.svelte.ts Collection + item state
ui.svelte.ts Sidebar, mobile state
types/index.ts TypeScript types and constants
app.css Global styles and design tokens