Running npm ci twice in separate containers causes OOM on the shared
host. Single job: npm ci once, then tsc, vitest, and vite build
in sequence.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
node-pty native compilation during npm ci uses excessive memory.
Using --ignore-scripts since tests and build don't need native
modules. Removed python3/make/g++ from install step.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Worker test has pre-existing string mismatch (enCryptID vs CryptID).
Runner memory increased from 4GB to 8GB for Vite build.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
node-pty requires python3, make, g++ for node-gyp native build.
node:20-bookworm-slim doesn't include these by default.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- test job: TypeScript check, unit tests, worker tests
- build-check job: validates production build succeeds
- deploy job (main only): builds Docker image, pushes to Gitea registry,
deploys to Netcup, runs smoke test with auto-rollback on failure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Daily.co video chat replaced by Jitsi (meet.jeffemmett.com) — clean up
residual config (Dockerfile, vite.config, worker types, env vars).
Google Maps embed replaced with OpenStreetMap — no API key needed,
converts Google Maps URLs to OSM embeds automatically.
Part of TASK-CRITICAL.1: exposed API key rotation and cleanup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New canvas shape that connects to the self-hosted Meeting Intelligence API
(meets-api.rspace.online) to browse meetings, pull transcripts, summaries,
and speaker data onto the canvas as note shapes.
- MeetingIntelligencePanel: React panel listing meetings with pull buttons
- MeetingIntelligenceBrowserShapeUtil: Browser shape wrapping the panel
- MeetingIntelligenceTool: Tool for placing browser on canvas
- Registered in Board.tsx, overrides.tsx, automerge stores, and all shape registries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Web Speech API hook was sending each recognition result as a
separate fragment via onTranscriptUpdate, causing the transcription
shape to display fragmented/echoed conversation. Now the hook
accumulates the full transcript internally and always sends the
complete text, so the shape receives one unified conversation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously the page-switch logic only triggered when the current page had 0 shapes.
The R2 data has 283 shapes on page:QZw03khAAJ7jNX7zQND64 but only 1 (MycelialIntelligence)
on page:page. Since page:page is the default, users saw only the MI shape, not their content.
Now also switches when current page has <=2 shapes but another page has >10.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BlenderGen and TransactionBuilder were added to Board.tsx's customShapeUtils
but never registered in useAutomergeStoreV2.ts's CUSTOM_SHAPE_TYPES, imports,
or shapeUtils array. This schema mismatch prevented tldraw's Editor from
rendering any content - the store didn't know about these shape types even
though the Tldraw component was told to use them.
Also adds TransactionBuilder shape for Safe multisig transaction building
and reduces verbose error logging in automerge sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CLAUDE.md may contain project context with credentials and is now
covered by global gitignore to prevent accidental secret commits.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When visiting jeffemmett.com/board/mycofi, the redirect now correctly
goes to canvas.jeffemmett.com/mycofi/ instead of jeffemmett.com/mycofi.
Localhost and canvas.jeffemmett.com still use same-domain redirects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- cryptidAuth.ts: sendEmail() now calls email-relay.jeffemmett.com
instead of api.resend.com
- boardPermissions.ts: admin request emails use email relay
- types.ts: RESEND_API_KEY → EMAIL_RELAY_URL + EMAIL_RELAY_API_KEY
- wrangler.toml: updated secrets documentation
- Tests updated with new mock env vars
Email relay is a lightweight Flask service on Netcup that accepts
HTTP POST and sends via Mailcow SMTP. Needed because CF Workers
can't do TCP/SMTP directly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace 12 FlipHTML5 iframe URLs with slides.jeffemmett.com for full self-hosting
of all presentation decks. Zero external dependencies for presentation viewing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DNS and Cloudflare tunnel were already configured but Traefik was only
routing jeffemmett.com and www.jeffemmett.com to the canvas container.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the standalone activity log toggle button (~) into the settings
gear dropdown as a collapsible accordion section. Simplify the board
permission display by removing the verbose "Access Levels" grid and
replacing it with a compact current-permission badge + request button.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed the purple "Connect Vault" button from shape mode rendering.
The no-vault case is now handled only by the early return section which
shows the working "Select Folder" design.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BlenderGen was registered as a tool but missing from overrides.tsx,
causing an empty space in the context menu between VideoGen and Markdown.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The sanitizeIndex function was incorrectly expecting decimal digits
after the prefix letter (e.g., "b10"), but tldraw uses base-62
alphanumeric fractional indexing (e.g., "bBE6lP", "aKB7V" are valid).
This was causing all shapes to be reset to 'a1' z-index, flooding
the console with invalid index warnings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add noteContentStore.ts for storing full note content in IndexedDB
- Avoids Automerge WASM capacity limits and localStorage quota (~5MB)
- Only metadata (id, title, tags, links) syncs via Automerge
- Full content stays local and loads on-demand
- Handle ephemeral messages in AutomergeDurableObject for cursor sync
- Improvements to ObsidianVaultBrowser component
- Enhanced obsidianImporter functionality
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Daily.co Cleanup:
- Remove @daily-co/daily-js and @daily-co/daily-react packages
- Remove DailyProvider wrapper from App.tsx
- Remove ~380 lines of Daily API endpoints from worker.ts
- Remove DAILY_DOMAIN from wrangler configs
- Remove Daily env vars from .env.example
- Video chat now uses self-hosted Jitsi (meet.jeffemmett.com)
Sync Logic Fix:
- Fix stale IndexedDB cache preventing server data from loading
- Changed threshold from "10x more shapes" to "more shapes"
- Server data now properly updates local cache on initial load
- Keeps local-only records for offline work
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Enable pointer events on iframe for direct mouse/touch/pen interaction
- Room names now use canvas slug (e.g., mycofi-jeffsi-meet for /mycofi)
- All video chats in same canvas room share the same Jitsi room
- Support both /:slug and /board/:slug URL patterns
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fixed variable scope issue with totalMerged counter
- Added syncVersion to return type declaration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added syncVersion state that increments when server data is merged,
ensuring the UI updates to show the loaded board content.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updated vitest from 3.2.4 to 4.0.16 to match @vitest/coverage-v8 4.0.16
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add canvas.jeffemmett.com to CORS allowed origins
- Fix IndexedDB sync to prefer server data when local has no shapes
- Handle case where local cache has stale/minimal data but server has full board
- Add console logging for sync debugging
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add conditional pointer-events to iframe - only enabled when shape is
selected, allowing normal canvas pan/zoom when not interacting with video.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove Daily.co API dependencies
- Use self-hosted Jeffsi Meet (Jitsi fork) at meet.jeffemmett.com
- Simplify room creation (Jitsi creates rooms on-the-fly)
- Add Copy Link and Pop Out buttons for sharing
- Configure Jitsi embed with custom branding settings
- No recurring per-minute costs with self-hosted solution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add "Last Visited" section to Dashboard showing recent board visits
- Add per-board activity logging that tracks shape creates/deletes/updates
- Activity panel with collapsible sidebar, grouped by date
- Debounced update logging to skip tiny movements
- Full dark mode support for both features
New files:
- src/lib/visitedBoards.ts - Visit tracking service
- src/lib/activityLogger.ts - Activity logging service
- src/components/ActivityPanel.tsx - Activity panel UI
- src/css/activity-panel.css - Activity panel styles
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed RedirectBoardSlug to use React Router's Navigate instead of
window.location.href to a non-existent domain. Now /board/:slug
redirects to /:slug/ within the same domain.
Production (main branch) keeps /board/:slug unchanged.
Staging (dev branch) supports both /board/:slug and /:slug patterns.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
TypeScript requires index signature for Automerge.Doc generic constraint.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use double type assertion for TLStoreSnapshot → Record<string, unknown>
to satisfy Automerge.from() type constraints.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updated the security visual indicator to use slate/steel colors (#64748b)
instead of green (#22c55e) for a more professional look.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The frontend expects jeffemmett-canvas-automerge-dev but wrangler.dev.toml
had jeffemmett-canvas-dev, causing 404 errors for wallet API endpoints.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Drawfast was always included in overrides.tsx but is conditional in
Board.tsx (dev-only). This caused "l is not a function" errors when
users tried to select tools in production since the Drawfast tool
wasn't registered.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>