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