Commit Graph
238 Commits
Author SHA1 Message Date
Hermes 5a4cfcbf64 feat: AYON single-sign-on (ticket exchange, task boards, browser entry)
- POST /api/auth/ayon/exchange: redeem single-use ticket (issued by the
  AYON addon) via AYON_EXCHANGE_URL, mint session JWT, get-or-create
  internal user row and the <project>/<task> board in the AYON collection
- db: getOrCreateAyonUser / getOrCreateAyonBoard / grantAyonCollectionAccess
- frontend: /b route redeems ticket from URL and forwards to the board
- password login/register paths untouched (legacy instance support)
2026-09-04 13:40:33 +00:00
Hiren KangadandClaude Opus 4.7 33d3109136 docs: deployment templates for Cloudflare Tunnel, Caddy, Fly.io, Render
examples/compose/cloudflared-paired.yml pairs RefBoard with a cloudflared
sidecar reading a TUNNEL_TOKEN env var — no inbound ports needed, suitable
for home / studio servers.

examples/compose/behind-caddy.yml + Caddyfile fronts RefBoard with Caddy
for automatic Let's Encrypt TLS on a public hostname. Caddy 2's
reverse_proxy transparently upgrades Socket.IO WebSockets.

examples/fly.toml deploys to Fly.io using the GHCR image and a persistent
volume mounted at /app/data. STORAGE_BACKEND=fs so the whole stack is one
machine with one disk.

examples/render.yaml is a Render.com Blueprint targeting the same shape
(GHCR image + attached disk + FS storage).

examples/README.md indexes everything and adds notes for Coolify /
Dokploy / CapRover / Railway, which consume the existing compose file
directly without a dedicated template.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
upstream-v0.5.0
2026-05-21 09:51:52 +05:30
Hiren KangadandClaude Opus 4.7 548d0c753a feat: one-click setup scripts for macOS, Linux, and Windows
scripts/setup.sh (bash) and scripts/setup.ps1 (PowerShell) wrap the
canonical Docker Compose flow: detect Docker + Compose v2 (with legacy
docker-compose fallback), copy .env.example to .env if missing, pull the
GHCR image, bring the stack up, poll /health until it's ready (90s cap),
print a summary, and open the URL in the default browser. Idempotent —
safe to re-run.

README quick-start now points designers at the one-liner as the primary
path, with the raw docker compose commands kept underneath for reference.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 09:51:39 +05:30
Hiren KangadandClaude Opus 4.7 76a6e462ee feat: zero-config first boot and pluggable storage backend
Two changes that drop the friction in self-hosting RefBoard so the install
story becomes "docker compose up, open the URL".

1. JWT_SECRET is now optional. On first boot the backend generates a
   64-byte random secret and persists it in the existing settings table.
   process.env.JWT_SECRET still wins when set, so ops setups that manage
   secrets out-of-band are unaffected. The prod-throws-without-env guard
   is gone (auto-generation is a strictly safer default than the previous
   hardcoded dev fallback).

2. STORAGE_BACKEND=fs|minio picks between MinIO (default, unchanged) and
   a new local-filesystem adapter. The FS adapter exposes a fake minioClient
   that mirrors the methods RefBoard calls (statObject, getObject,
   getPartialObject, listObjectsV2, putObject, removeObject(s), bucketExists,
   makeBucket), so consumers swap require('./minio') for require('./storage')
   and nothing else changes. Sidecar .mime files hold Content-Type so the
   range-aware media proxy still serves the right response headers.

examples/compose/minimal-fs.yml is the single-container variant that uses
the FS adapter. The default docker-compose.yml still spins up MinIO.

README quick-start collapses to one block (cp .env, docker compose pull,
docker compose up -d). The first user you register on the Login screen is
auto-promoted to admin, same as before.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 09:51:20 +05:30
Hiren KangadandClaude Opus 4.7 9328713ae5 ci: publish multi-arch container image to GHCR
Build linux/amd64 + linux/arm64 from the existing Dockerfile on every push
to main, on git tags v*.*.*, and on manual workflow_dispatch. Pushes to
ghcr.io/metalfinger/refboard with tags :latest (default branch), :sha-XXX
(every push), and semver tags on releases. Uses GHA cache for speed.

Tightens .dockerignore so multi-arch builds don't ship docs/, examples/,
scripts/, .github/, or .docker-data into the image context.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 09:50:59 +05:30
Vivek Shukla 9a02edd9dc fix: polyfill crypto.randomUUID for insecure-context origins (#1)
crypto.randomUUID is restricted to secure contexts (HTTPS or localhost).
When RefBoard is served over plain HTTP on a LAN IP (e.g.
http://192.168.x.x:8000), the function is undefined and the editor crashes
on first ID generation with:

  Uncaught TypeError: crypto.randomUUID is not a function

The frontend calls crypto.randomUUID in ~20 places (uploadManager,
SceneManager, Editor, canvas tools, grouping, scene-format, etc.), so a
single polyfill at the entry point is the smallest fix.

The polyfill uses crypto.getRandomValues — available on insecure origins —
to build an RFC 4122 v4 UUID with the correct version/variant bits. It is
a no-op when the native function exists, so HTTPS and localhost paths are
unchanged.

No crypto.subtle usage exists in the source, so randomUUID is the only
secure-context API the frontend depends on today.
2026-05-20 18:58:35 +05:30
Hiren Kangad 8d4068e220 docs: roadmap for designer-friendly install paths
Captures two install upgrades in dependency order so a future session can
pick them up without re-thinking:

  Tier 1 — scripts/setup.sh: one-liner installer for terminal-comfortable
  designers. Auto-generates JWT_SECRET, prompts for admin email/password,
  brings up docker compose, opens the browser. Floor: Docker Desktop must
  be installed (script exits with a friendly install link if not).

  Tier 2 — native installer: .dmg / .exe with no Docker, no terminal.
  Replaces MinIO with a local-filesystem storage adapter, bundles the
  Node backend + frontend dist into a single binary via pkg, wraps in a
  Tauri tray app. Designer double-clicks, gets a menu-bar icon, clicks
  "Open RefBoard."

Both unchecked in README roadmap and detailed in docs/install-roadmap.md
with file layout, estimated effort, and open questions.
2026-04-29 12:57:56 +05:30
Hiren Kangad eb0bd210ac feat: per-board activity log
Adds an audit trail per board, visible from a new clock-icon button on the
toolbar. Useful for review-style work where someone wants to see who
contributed which references and when.

Logged events (high-signal only — canvas-edit noise intentionally skipped):
- image / video / pdf added (whether dropped, pasted, or pulled from a URL)
- board created / renamed / deleted
- thread started, resolved, reopened
- comment posted on a thread

Backend:
- new activity_logs table (id, board_id, user_id, denormalised actor name +
  email, action, target_type/id/label, metadata JSON, created_at) with an
  index on (board_id, created_at DESC).
- logActivity helper resolves the user once at log time and stores their
  display name + email so entries survive deactivation/rename.
- recordActivity wraps logActivity + a Socket.IO emit to the board's room
  so the panel updates live without polling.
- GET /api/boards/:id/activity?limit=&before= for pagination
  (collection-membership gated, viewer+).

Frontend:
- ActivityPanel side-drawer: time-grouped feed (Today / Yesterday / older),
  per-action icons + tone colours (add/remove/edit/comment), pagination
  via "Load older", live append on Socket.IO 'activity:new'.
- Relative timestamps refresh every 30s.
- Wired into Editor + Toolbar.

README updated; roadmap entry checked off.
2026-04-28 21:29:48 +05:30
Hiren Kangad 2fb71b4ae1 feat: runtime self-registration toggle in admin dashboard
Self-registration is now controlled at runtime from the admin panel rather
than at build time via an env var. Default: off.

- New `settings` table (key/value/updated_at) plus getSetting/setSetting
  helpers. Idempotent first-boot migration seeds allow_self_registration
  from the ALLOW_SELF_REGISTRATION env var; after first boot the env var
  is ignored and admins control the toggle from the UI.
- New public GET /api/auth/config (no auth) — returns
  { allowSelfRegistration, hasUsers }. The Login page polls this on mount
  to decide whether to show a Register link, and to render
  "Create the first admin account" mode when the install is empty.
- New admin GET /api/admin/settings + PUT /api/admin/settings/:key for
  the dashboard. Constrained to a known-keys allowlist with type coercion
  so unrecognized keys can't be stored.
- POST /api/auth/register now reads the toggle from the database instead
  of process.env. The first user is still always allowed and is auto-
  promoted to admin.
- Admin.tsx grows a "Settings" card with a labelled toggle switch and
  toast feedback. The card sits above the user table.
- VITE_ALLOW_SELF_REGISTRATION dropped — runtime fetch replaces it.

Docs: README + .env.example clarify that ALLOW_SELF_REGISTRATION is now
an initial seed only, the going-public checklist points at the dashboard
toggle, and the features list calls out runtime control.
2026-04-28 20:56:52 +05:30
Hiren Kangad 782d6df9e0 feat: admin dashboard for user management
Adds an /admin route, visible only to users with role=admin, that lets an
operator manage the user base from the UI:
- list / search users (active + inactive)
- create new accounts (with role and optional display name)
- reset a user's password
- promote/demote between admin and member
- deactivate / reactivate (soft-delete via is_active flag)

Backend changes:
- New adminOrApiKeyMiddleware accepts EITHER a Bearer JWT belonging to a
  role=admin user (UI path) OR the existing X-API-Key (bot/server-to-server).
- Existing /api/admin/* routes switched to the hybrid middleware, so the same
  endpoints serve both the dashboard and any external scripts.
- Added PUT /api/admin/users/:id/role and PUT /api/admin/users/:id/reactivate.
- Self-deactivation and self-demotion are explicitly blocked so an admin can't
  lock themselves out.

Frontend changes:
- New Admin.tsx page (table view, modals for create + reset, toast feedback).
- Admin button in CollectionList header, only rendered for admin role.
- Wired into App.tsx routing.

Also: friendly error when poppler-utils is missing on the host (PDF uploads
return 501 POPPLER_MISSING with a one-line install hint instead of crashing
the request); README clarifies poppler is required for the manual install.
2026-04-28 20:34:49 +05:30
Hiren Kangad f2260a9e35 docs: add domain / tunnel deployment guide
Cover Cloudflare Tunnel (the path I run myself), Caddy + Let's Encrypt,
nginx with WebSocket headers, Tailscale for tailnet-private access, and
a 'going public' checklist (JWT_SECRET, CORS, registration, MinIO scope,
backups).
2026-04-28 20:10:01 +05:30
Hiren Kangad 69b58f73f8 chore: prepare standalone public repo
- Remove Mattermost integration (OAuth, channel bridge, file sync watcher,
  frontend import modal). RefBoard now ships as a self-contained app.
- Replace SSO Login screen with email/password form (+ optional register link
  gated by ALLOW_SELF_REGISTRATION).
- Add SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD env-var bootstrap so a fresh
  install ships with an admin account on first boot (idempotent).
- ALLOW_SELF_REGISTRATION flag (default false) gates POST /api/auth/register.
  First user can always register (auto-promoted to admin).
- Drop mattermost_id and mm_file_id columns + board_channel_links table
  from the schema; remove related db helpers and exports.
- Add MIT LICENSE, comprehensive README, .env.example, docker-compose.yml
  (bundles MinIO so one command boots a working stack).
- Expand .gitignore for typical Node + Docker dev artefacts.
2026-04-28 19:58:53 +05:30
Hiren 24fa9d1252 fix(refboard): remove loading overlay that got stuck due to viewport culling
The asset progress overlay (e.g. "Loading assets 6/22") was incompatible
with the viewport culling system — culling only loads nearby textures and
unloads distant ones, so loaded count could never reach total. Simplified
to a brief spinner during scene data parsing only.
2026-03-16 16:23:57 +05:30
Hiren 7bcb0fd071 fix(refboard): fix WebGL null texture crash and add loading overlay
- Set texture to Texture.EMPTY before destroying sprites in
  AnimatedGifSprite and PdfPageSprite (matches VideoSprite pattern)
- Add full-screen loading overlay that blocks interaction until
  scene and assets finish loading
2026-03-16 11:10:19 +05:30
Hiren Kangad 019e17ae59 fix: allow spacebar in contentEditable elements (markdown editor)
Space-to-pan handler was intercepting spacebar globally but only
excluded INPUT/TEXTAREA — missed contentEditable (BlockNote editor).
2026-03-15 13:15:00 +05:30
Hiren Kangad 479feb991c fix: block flip/rotate in SelectionToolbar for sticky, text, markdown, pdf-page
The toolbar buttons called ops directly without type filtering.
Now filters NON_TRANSFORMABLE types before applying flip/rotate ops.
2026-03-14 16:51:02 +05:30
Hiren Kangad 94ea1e3123 docs: mark Phase 3 (PDF support) as completed in expansion plan 2026-03-14 12:08:41 +05:30
Hiren Kangad 6f01c0fa54 feat: PDF support — upload, rasterize, page picker, canvas placement
Upload PDFs → server-side page rasterization via pdftoppm (poppler-utils)
→ page picker modal for selecting pages → placed on canvas as pdf-page
objects with lazy texture loading and progressive high-res upgrades.

Key features:
- New pdf-page scene object type with source PDF metadata
- Page picker modal with lazy thumbnail loading (first 20 + on-scroll)
- Priority queue: hires jobs (priority 10) jump ahead of thumbnails (0)
- Single-page PDFs skip picker, place directly
- No crop/rotate/flip on PDF pages (enforced in TransformBox, context
  menu, and keyboard shortcuts)
- Per-page socket events for progressive texture upgrades
- 500-page cap, 50-page batch placement limit
- Early validation (before MinIO upload) prevents orphaned assets
2026-03-14 12:07:15 +05:30
Hiren Kangad 4dbd755c41 fix: PdfPickerModal thumbnail URL uses /api/images/ not /api/assets/ 2026-03-14 11:40:49 +05:30
Hiren Kangad 9c8b3cc5f5 feat: wire PdfPickerModal into Editor with hires texture upgrade via socket 2026-03-14 11:31:45 +05:30
Hiren Kangad 656b0a9875 feat: PdfPickerModal — page selection grid with lazy thumbnail loading 2026-03-14 11:29:31 +05:30
Hiren Kangad bbf9b9484a feat: PDF upload response forks to picker callback or direct placement 2026-03-14 11:28:29 +05:30
Hiren Kangad 3bd6270990 feat: frontend accepts PDF uploads, UploadManager detects PDF type 2026-03-14 11:26:29 +05:30
Hiren Kangad e118ad632a feat: block rotate/flip for pdf-page in context menu and shortcuts 2026-03-14 11:22:29 +05:30
Hiren Kangad 07953ab18d feat: SceneManager creates pdf-page items with culling support 2026-03-14 11:21:08 +05:30
Hiren Kangad 332f12cafe feat: add PdfPageSprite — placeholder, lazy texture, page badge 2026-03-14 11:19:55 +05:30
Hiren Kangad 3511c54658 feat: add PdfPageObject type to scene format 2026-03-14 11:19:04 +05:30
Hiren Kangad 247145f545 fix: early PDF validation, type-aware errors, exact job dedup
- Move PDF page count check before MinIO upload and DB record creation
  to prevent orphaned objects when >500 page PDFs are rejected
- Use job type label (PDF page / Video) in media-worker error messages
  instead of hardcoded "Video processing failed"
- Replace LIKE-based idempotency check with exact JSON match to prevent
  page 1 matching page 11/12/etc substring collisions
2026-03-14 11:16:58 +05:30
Hiren Kangad 4b68e337cf feat: media worker dispatches pdf-thumbnail and pdf-hires jobs 2026-03-14 11:12:41 +05:30
Hiren Kangad 1250ad1b72 feat: add PDF page selection and lazy thumbnail endpoints 2026-03-14 11:11:50 +05:30
Hiren Kangad 0b56ec7f6f feat: accept PDF uploads — pdfinfo extraction, pdf_pages rows, thumbnail job queuing 2026-03-14 11:10:45 +05:30
Hiren Kangad d28998e4d7 feat: add pdf-utils.js — pdfInfo, pdfRenderPage, bufferToTempFile 2026-03-14 11:10:01 +05:30
Hiren Kangad e1aedd721b feat(db): add pdf_pages table, priority column, and helper functions 2026-03-14 11:08:45 +05:30
Hiren Kangad 0e14790990 build: add poppler-utils to Dockerfile for PDF support 2026-03-14 11:07:03 +05:30
Hiren Kangad a901833b72 fix: rotated transform box, video texture crash, paste duplication, and loading UX
- TransformBox rotates with single-item selection (Figma-style), with
  handles and resize math projected into rotated coordinate space
- Fix VideoSprite alphaMode crash by swapping texture to EMPTY before
  destroying, preventing PixiJS render loop from reading null source
- Fix Ctrl+V double-paste: internal clipboard now always takes priority
  over system clipboard PNG, with wasRecentInternalPaste() guard
- Add asset loading progress bar and suppress "Drop images here" flash
  during initial scene load
2026-03-13 17:37:21 +05:30
Hiren Kangad e7e217d3d4 fix: restrict rotation to image/video selections only
Hide rotate handles and block rotation drag for sticky, markdown,
text, drawing, and group item types. Only images and videos support
rotation.
2026-03-13 17:07:26 +05:30
Hiren Kangad 05118330d6 feat: unified composition renderer for clipboard/export
Replace the split snapshot/native renderer paths with a single
composition pipeline (compositionRenderer.ts) that:

- Loads actual source images and uses naturalWidth/Height for
  correct full-resolution sampling (fixes top-left-corner-only bug
  caused by data.w/h being capped to 600px display dimensions)
- Routes image-only selections through native Canvas 2D composition
- Falls back to viewport snapshot for mixed/unsupported selections
  with explicit warnings instead of silent degradation
- Resolves group children via itemResolver for proper group export
- Rejects group children from native composition (local coords
  incompatible with world-space drawing)
- Adds canvas size safety limits with auto-downscale
- Guards VideoSprite._drawFrame against null texture source race
  condition during zoom-triggered culling

New files:
- compositionRenderer.ts — unified composition module
- compositionRenderer.test.ts — 16 tests for entry flattening,
  bounds, dimensions, group handling

Modified:
- clipboard.ts — uses composeSelection() instead of direct renderers
- export.ts — uses composeSelection() + getCompositionDimensions()
- Editor.tsx, useShortcutHandler.ts — pass scene for group resolution
- VideoSprite.ts — null guard on texture source in frame loop
2026-03-13 16:59:11 +05:30
Hiren Kangad 1ceafec67f refboard: fix native export pixel bounds rounding 2026-03-13 15:51:12 +05:30
Hiren Kangad 7b2fcfd6a8 refboard: add native image copy and export renderer 2026-03-13 15:48:12 +05:30
Hiren Kangad ae9b1ecefe refboard: avoid eager extract fallback for canvas snapshots 2026-03-13 15:36:12 +05:30
Hiren Kangad 80ec4808e8 refboard: await clipboard writes before copy success 2026-03-13 15:33:37 +05:30
Hiren Kangad 3dd110f491 refboard: restore clipboard copy and native paste flows 2026-03-13 15:30:23 +05:30
Hiren Kangad e9e0c25491 refboard: standardize clipboard copy on rendered canvas 2026-03-13 15:27:41 +05:30
Hiren Kangad cd8207e984 refboard: capture board previews from rendered canvas 2026-03-13 15:23:08 +05:30
Hiren Kangad c21a0c386d refboard: add corner rotation grips 2026-03-13 15:10:45 +05:30
Hiren Kangad 8d34d42377 refboard: preserve center when rotating items 2026-03-13 15:05:54 +05:30
Hiren Kangad 98fb7be77e refboard: add visible rotate controls 2026-03-13 15:01:25 +05:30
Hiren Kangad cb59fbb150 fix(crop): fix editor positioning for flipped images with asymmetric crops
The crop editor (getImageEditorGeometry) was anchoring on a view-space
point that maps differently in cropped vs uncropped local space when
flipped. For asymmetric crops + flip, the full image would appear
shifted from where the visible image was — the user saw the image
"jump" when entering crop mode.

Fix: anchor on the container's local origin instead. In the cropped
sprite, local (0,0) corresponds to source pixel (srcRect.x, srcRect.y).
Position the editor so that same source pixel maps to the same world
point in both cropped and uncropped states. This works correctly for
all flip/rotation/crop combinations.
2026-03-13 14:54:36 +05:30
Hiren Kangad 21a724c0f4 fix: address code review findings from crop/text commit
- Fix crop confirm anchor drift for rotated images by using crop corner
  world point instead of AABB min in useCanvasSetup.ts
- Document ungroup shear limitation for non-uniform scale + rotation
- Add 6 integration tests covering crop confirm and ungroup paths
2026-03-13 13:47:14 +05:30
Hiren Kangad 9a4edd90c7 refboard: complete image geometry rework — consumer adoption + tests
- Fix grouping.ts ungroup: transform display position (with flip
  compensation) through group transform, then back-calculate canonical
  data.x/y. Fixes visual jump for flipped images in rotated groups.
- Fix clipboard.ts + export.ts resolution estimation: account for
  scale when computing effective rendered width for texture ratio.
- Add imageTransforms.test.ts: 32 unit tests covering source rect,
  visible local rect, display transform, display geometry, editor
  geometry anchor preservation, crop rect round-trips, coordinate
  conversions, negative scale normalization, and world bounds invariants.
- Add vitest as dev dependency with "test" script.
- Include canonical geometry layer (imageTransforms.ts) and crop
  session rewrite (CropOverlay.ts) from prior work.
- Add image geometry rework plan document.
2026-03-13 13:37:42 +05:30