Add Cloudflare email and collaboration workflows

This commit is contained in:
2026-07-22 12:59:16 -04:00
parent 09f51d1ef4
commit 4454e918c2
35 changed files with 1596 additions and 487 deletions

View File

@@ -1,64 +1,74 @@
# Project Status Report
**Generated:** 2026-06-24
**Updated:** 2026-07-22
**Branch:** main
**Last Commit:** b6d2ded - passkey existing (2026-06-16)
**Last Commit:** 09f51d1 - Add queued email notifications for collaboration events
## Quick Stats
| Metric | Value |
|--------|-------|
| Total Tracked Files | 189 |
| Go Files | 46 |
| Swift Files | 14 (macOS app) |
| Test Files | 10 |
| Avg Test Coverage | ~39% (markdown 87%, store 70%, auth 55%, sync 54%) |
| Total Tracked Files | 201 |
| Go Files | 63 |
| Swift Files | 15 (macOS app and package manifest) |
| Go Test Files | 18 |
| Aggregate Go Test Coverage | 52.4% |
| Milestones Complete | 3/6 (50%) |
## Recent Activity (since 2026-06-01 report)
## Verification
16 commits on `main` between `fb0673a` (2026-06-01) and `b6d2ded` (2026-06-16):
71 files changed, +7908 / -812 lines.
- `go test -coverpkg=./... -coverprofile=... ./...` passes with 52.4% aggregate statement coverage.
- `swift build` passes for the macOS app and CLI.
- Focused race tests pass for email, collaboration, and HTTP packages. A full `go test -race ./...` exceeds the current five-minute local timeout.
- `govulncheck` currently fails because Go 1.24.2 has 31 reachable standard-library vulnerabilities; the pinned Go toolchain must be upgraded before production sign-off.
- Highest package coverage: Markdown 87.0%, email 77.9%, store 70.0%, config 67.9%, auth 54.9%, and sync 54.2%.
- Collaboration, configuration, email-provider, and collaboration HTTP integration tests are present; database, logging, and realtime still lack direct suites.
Highlights:
- `b6d2ded` passkey existing-user login flow
- `4655008` text editor link autocomplete
- `0adb980` CodeMirror editor integration
- `c39c50b` dropdown menu + comment side panel (wires M4 comment UI)
- `6c30fd8` sync service update
- `04a1f2b` ops design + app scaffolding (`entrypoint.sh`)
- `632621b`/`1e939f3` macOS menu-bar app: live search popover, drop-target icon
- `ddc7d5c` accounts and email wiring
- `93e96be`/`73d505a` UI CSS expansion (~1900 lines) and workspace browser polish
- New untracked subsystems: `permissions_handlers.go` (RBAC sharing UI), `tags.js`/`tag.gohtml` (tagging), `keyboard-nav.js`, `mobile.js`, `archive.js`
## Recent Activity
Recent work has moved the project beyond the previous 2026-06-24 report:
- Durable email queue with retry/backoff and failed-delivery inspection.
- Postmark outbound sender with API-level tests.
- Cloudflare Email Service REST sender with retry/permanent-error classification and API-level tests.
- File-change and comment notifications now enqueue email delivery.
- Notification bell/list/read UI is active.
- Document watch/unwatch controls are active, watcher subscriptions are deduplicated, and ancestor-folder matching is fixed.
- Nested comment replies, whole-thread resolution, and resolved-history UI are active.
- Notification dispatch rechecks the watcher's current document permission.
- Existing users can add another passkey from their account page.
- Resource permission UI and private-by-default filtering cover documents, folders, navigation, search, API document lists, and native sync snapshots.
- CodeMirror editor, wiki-link autocomplete, keyboard navigation, mobile drawers, archive UI, and tag pages are present.
- The macOS menu-bar app and CLI compile successfully.
## Milestone Progress
- [x] Milestone 1: Foundation
- [x] Milestone 2: Sync Protocol
- [x] Milestone 3: Authentication
- [ ] Milestone 4: Collaboration (comments UI wired; email integration in progress; digest/preferences pending)
- [ ] Milestone 5: Search & UI (server FTS5 done; design-system CSS, keyboard nav, mobile responsive shipped; `/design` route, Flexsearch, a11y audits pending)
- [ ] Milestone 6: Production (not started)
- [ ] Milestone 4: Collaboration (threaded comments, notification bell, watch controls, and queued Postmark/Cloudflare/SMTP delivery shipped; preferences, digest, inbound replies, mention/conflict triggers, and live-provider tests remain)
- [ ] Milestone 5: Search & UI (server FTS5, tags, keyboard navigation, and responsive foundations shipped; offline client search, `/design`, performance, and accessibility validation remain)
- [ ] Milestone 6: Production (health/readiness endpoints, CI, deployment scaffolding, and some integration tests exist; hardening acceptance criteria remain open)
**Current Focus:** M4 Email Integration (Postmark adapter, templates, queue with retry)
## Current Focus
## Untracked Subsystems (not in original plan)
Milestone 4 collaboration completion:
These shipped but are not milestone-tracked. Tracked via `notes/untracked-subsystems.md`:
- Add notification preferences and digest delivery.
- Add inbound email replies through a verified webhook flow.
- Add mention and conflict notification triggers.
- Test watcher-to-email delivery against live Postmark and Cloudflare accounts.
- **Permissions / RBAC sharing UI** — `permissions_handlers.go` (431 lines), `permissions.gohtml`, `permissions.js`. Per-resource read/write/admin grants.
- **Tagging** — `tags.js`, `tag.gohtml`. Document tags.
- **CodeMirror inline editor** — `editor.js` (+620), `codemirror.bundle.js`, link autocomplete.
- **macOS menu-bar app** — 14 Swift files, live search popover, drag-drop target. Beyond web-first plan scope.
- **API tokens** — `token_create.gohtml`, `auth.APIKey` types.
- **Password reset flow** — `password_reset.gohtml`, `auth.PasswordResetToken` repository methods.
## Known Gaps Requiring Human Review
## Notes for Human Review
- `collaboration.Service` defines its own `EmailSender` interface duplicating `email.Sender`. Consolidate when convenient.
- `Service.notifyWatchersOfComment` and `NotifyDocumentChanged` create in-app notifications only; never invoke `s.email`. `Notification.EmailedAt` field unused.
- `email.SMTPSender` uses unauthenticated SMTP (fine for local Mailpit, not Postmark).
- Go 1.24.2 is below the patched versions reported by `govulncheck`, including fixes for `html/template`, `crypto/tls`, `net/http`, and `crypto/x509` vulnerabilities.
- WebSocket broadcasts are authenticated but not filtered by per-document permissions.
- Content-hash downloads are not tied back to effective document permission.
- Browser caches are not cleared on logout.
- Raw HTML is enabled in authored Markdown while the security specification says it should be disabled.
- Folder/collection permission administration is only fully discoverable for documents.
- The implementation uses JSON for SimpleSync while protocol documentation still leaves JSON versus protobuf unresolved.
- Backup documentation does not match the named data volume in Compose.
---
*This report updated 2026-06-24 by hand. Next refresh when M4 email work lands.*
*This report is maintained from the implementation and verification results, not milestone checkbox state alone.*

View File

@@ -7,7 +7,14 @@ Accepted
The platform needs to send email notifications for: file changes (to watchers), new comments, conflict alerts, and digest summaries. It must also receive email replies and convert them to comments. Requirement: use Postmark email gateway with plain-text only.
## Decision
**Postmark HTTP API for outgoing email, Postmark inbound webhook for incoming email.**
**Postmark or Cloudflare Email Service HTTP API for outgoing email, with a
provider-specific verified inbound webhook for incoming email.**
Amended 2026-07-22: Cloudflare Email Service is supported as an alternative
transactional outbound provider. The same durable queue and plain-text
templates are shared by Postmark, Cloudflare, and local SMTP. Cloudflare-hosted
Workers would normally use an Email binding, but Cairnquire is a standalone Go
application and therefore uses the authenticated REST API.
### Implementation

View File

@@ -97,12 +97,14 @@ Cairnquire is a self-hosted documentation platform. A single Go binary serves HT
- Client syncs index to IndexedDB for offline search
- Index rebuilds incrementally on file changes
### 7. Email (Postmark)
- Go adapter using Postmark HTTP API
### 7. Email (Postmark or Cloudflare Email Service)
- Go adapters use the Postmark or Cloudflare Email Sending REST API
- Local development uses SMTP with Mailpit
- A durable SQLite queue handles asynchronous delivery and bounded retries
- Plain-text emails only (no HTML)
- Threading via Message-ID / In-Reply-To headers
- Webhook endpoint receives email replies, converts to comments
- Templates: notification, digest, conflict alert
- Planned: threading via Message-ID / In-Reply-To headers
- Planned: verified inbound webhook converts email replies to comments
- Implemented templates: file change, comment, mention, and conflict; digest remains planned
### 8. File Store (Content-Addressed)
- Files stored by SHA-256 hash: `store/ab/cd/abcdef1234...`

View File

@@ -9,9 +9,9 @@
- [x] Comment system (backend + UI wired)
- [x] Comment creation endpoint (web)
- [x] Comment threading (parent/child) - schema supports it
- [x] Comment threading (parent/child) with nested browser replies
- [x] Line/section anchoring (hash-based)
- [x] Comment resolution (mark as resolved)
- [x] Whole-thread resolution with resolved-history view
- [x] Comment display in the Go-served browser UI (`c39c50b` dropdown menu + comment side panel, `comments.js` +330 lines)
- [x] Notification system (backend complete)
@@ -21,10 +21,10 @@
- [ ] Global notification settings
- [ ] Digest mode (hourly/daily batches)
- [x] Web notification UI (backend complete, JS partial)
- [x] Notification bell with unread count - API exists
- [x] Notification list dropdown - API exists
- [x] Mark as read functionality - API exists
- [x] Web notification UI
- [x] Notification bell with unread count
- [x] Notification list dropdown
- [x] Mark one/all as read functionality
- [ ] Notification preferences page
### Week 8: Email Integration & Conflict Resolution
@@ -34,6 +34,7 @@
- [x] Postmark adapter (`email/postmark.go`)
- [x] Plain-text email templates (`email/templates.go`)
- [x] Outgoing email queue with retry (`email/queue.go` + migration 000016)
- [x] Cloudflare Email Service REST adapter (`email/cloudflare.go`)
- [ ] Webhook endpoint for inbound email
- [x] Email notification types
@@ -60,20 +61,20 @@
### Functional
- [x] Users can add comments to specific lines in a document (API exists)
- [x] Comments are linked to document version (hash)
- [x] Comment threading works (reply to reply) (schema supports it)
- [x] Users can watch files or folders for changes (API exists)
- [x] Email notifications sent within 1 minute of event (Postmark adapter + queue with retry)
- [x] Comment threading works (reply to reply) in the browser and API
- [x] Users can watch files or folders for changes (document UI + APIs)
- [x] Email notifications queued within 1 minute of event (Postmark, Cloudflare, or SMTP sender)
- [ ] Replying to notification email creates a comment
- [ ] Digest emails batch notifications by time window
- [x] Conflicts display side-by-side diff (M2 sync)
- [x] Conflicts display a unified diff (M2 sync)
- [x] Users can resolve conflicts via web UI (M2 sync)
- [ ] Resolved comments are hidden but accessible in history
- [x] Resolved comments are hidden but accessible in history
### Non-Functional
- [x] Emails are plain-text only (no HTML) — enforced by templates, verified by test
- [ ] Email threading works in Gmail, Outlook, Apple Mail
- [ ] Webhook signature verified for inbound email
- [x] Failed emails retried 3 times with exponential backoff — queue.go, verified by test
- [x] Temporary email failures retried 3 times with exponential backoff; permanent provider rejections fail immediately
- [x] Email queue doesn't block web requests (async processing) — worker runs in goroutine
### Email Template Examples
@@ -166,7 +167,7 @@ CREATE TABLE watchers (
- [ ] All acceptance criteria pass
- [ ] Email flows tested with real Postmark account
- [ ] Comment threading tested with 3+ levels
- [x] Comment threading tested with 3+ levels
- [ ] Conflict resolution tested with 2+ concurrent editors
- [ ] Notification preferences persist across sessions
- [ ] Digest mode tested with 24-hour window

View File

@@ -150,19 +150,19 @@ A minimal-dependency, ultra-fast documentation platform that publishes markdown
**Goal:** Comments, notifications, and conflict resolution
- [x] Comment system linked to file versions (by hash) - backend complete
- [ ] Email notifications via Postmark adapter - SMTP stub exists
- [x] Queued email notifications via Postmark, Cloudflare Email Service, or local SMTP
- [x] Web-based conflict resolution UI (Milestone 2)
- [ ] Notification preferences (per-file, per-folder, global, digest)
- [x] Comment threading schema - UI needs wiring
- [x] Comment threading schema and browser reply UI
- [ ] Email replies create comments
**Acceptance Criteria:**
- [x] Users can comment on specific lines/sections of a document (API)
- [x] Comments persist and are linked to document versions
- [ ] Email notifications sent for watched file changes (NoOp sender)
- [x] Email notifications queued for watched file changes with retry
- [ ] Replying to notification email creates a comment
- [x] Conflicts display side-by-side diff with resolution actions (M2)
- [x] Conflicts display a unified diff with resolution actions (M2)
- [ ] Digest mode batches notifications by time window
---
@@ -265,7 +265,7 @@ See [architecture-overview.md](architecture-overview.md) for detailed component
| Markdown | `github.com/yuin/goldmark` + extensions | Fast, extensible, CommonMark compliant |
| WebSocket | `github.com/gorilla/websocket` | Most widely used Go WS library |
| File Watch | `github.com/fsnotify/fsnotify` | Cross-platform, stdlib-adjacent |
| Email | `github.com/keighl/postmark` + `net/smtp` | Postmark adapter + SMTP fallback |
| Email | `net/http` + `net/smtp` | Postmark/Cloudflare REST adapters + local SMTP |
| Crypto | `crypto/ed25519`, `crypto/sha256` | Standard library, no external deps |
| Templates | `html/template` | Stdlib, auto-escaping, SSR-safe |
@@ -371,7 +371,7 @@ cairnquire/
| WebAuthn/passkey adoption issues | Medium | Medium | Password fallback always available; extensive browser testing |
| SQLite performance at scale | Low | High | libsql replication path; FTS5 for search; benchmark early |
| Markdown extension parser bugs | Medium | Medium | Fuzz testing; strict CommonMark base; gradual feature rollout |
| Email deliverability | Medium | Medium | Postmark reputation monitoring; SPF/DKIM setup; SMTP fallback |
| Email deliverability | Medium | Medium | Provider reputation monitoring; SPF/DKIM/DMARC setup; provider fallback |
| Frontend bundle bloat | Medium | Medium | Bundle analysis in CI; tree-shaking enforced; lazy loading |
| File sync conflicts in teams | High | Medium | Clear UX for conflict resolution; email notification; automatic merge when possible |
@@ -383,11 +383,9 @@ cairnquire/
2. Complete Milestone 2: Sync Protocol
3. Complete Milestone 3: Authentication
4. 🔄 Finish Milestone 4: Collaboration
- Wire up comment UI in browser (JS exists, needs integration)
- Wire up notification bell UI
- Implement actual email sending (Postmark or SMTP)
- Add notification preferences page
- Test watcher-driven notifications end-to-end
- Complete inbound email replies, mention notifications, and conflict notifications
- Add global/per-resource notification preferences and digest scheduling
- Test watcher-driven notifications against live Postmark and Cloudflare accounts
5. 🔄 Finish Milestone 5: Search & UI Polish
- Tag filtering in search
- Design system /design route
@@ -395,11 +393,11 @@ cairnquire/
- Mobile-responsive layout polish
- Accessibility improvements
6. Milestone 6: Production Hardening
- Increase test coverage from 39% to 80%+
- Increase test coverage from 52.4% to 80%+
- Add integration tests for auth, sync, collaboration
- Security audit checklist
- Backup/restore scripts
- Health check and monitoring endpoints
- Complete dependency health checks and add monitoring endpoints
---

View File

@@ -53,8 +53,12 @@ deployment, only `CAIRNQUIRE_PUBLIC_ORIGIN` must be supplied in `.env`.
| Variable | Required | Compose default | Description |
|----------|----------|-----------------|-------------|
| `CAIRNQUIRE_PUBLIC_ORIGIN` | Yes | None | Exact external HTTPS origin. Passkeys and device-flow URLs depend on this value. |
| `CAIRNQUIRE_EMAIL_PROVIDER` | No | `smtp` | Outbound provider: `smtp`, `postmark`, `cloudflare`, or `noop`. |
| `CAIRNQUIRE_EMAIL_SMTP_HOST` | No | `mailpit` | SMTP server used for notifications. |
| `CAIRNQUIRE_EMAIL_SMTP_PORT` | No | `1025` | SMTP server port. |
| `CAIRNQUIRE_EMAIL_POSTMARK_TOKEN` | For Postmark | Empty | Postmark server token. |
| `CAIRNQUIRE_EMAIL_CLOUDFLARE_ACCOUNT_ID` | For Cloudflare | Empty | Account with Cloudflare Email Sending enabled. |
| `CAIRNQUIRE_EMAIL_CLOUDFLARE_API_TOKEN` | For Cloudflare | Empty | API token with Email Sending permission. Store it as a deployment secret. |
| `CAIRNQUIRE_EMAIL_FROM` | No | `notifications@cairnquire.local` | Notification sender address. |
| `CAIRNQUIRE_LOG_LEVEL` | No | `INFO` | Application log level. |
@@ -72,6 +76,23 @@ checked-in Compose deployment:
| `CAIRNQUIRE_CONFIG` | Empty | Optional JSON configuration file. Environment variables override it. |
| `CAIRNQUIRE_DEV_MODE` | `false` | Local-only auth shortcut. Never enable this on a deployed server. |
### Cloudflare Email Service
Cairnquire is an external Go application, so it uses Cloudflare Email
Service's REST API rather than a Workers binding. Before enabling the provider:
1. Enable Email Sending for the sender domain in Cloudflare, or run `npx wrangler email sending enable example.com`.
2. Create a Cloudflare API token with Email Sending permission for the account.
3. Set `CAIRNQUIRE_EMAIL_PROVIDER=cloudflare`, the account ID, API token, and a `CAIRNQUIRE_EMAIL_FROM` address on the onboarded domain.
Cloudflare Email Service is used only for transactional notifications. API
tokens must be supplied through deployment secrets and must not be committed.
The durable queue retries Cloudflare `429` and server errors; validation and
permanent-bounce responses are retained immediately as failed deliveries.
Cloudflare Email Routing for inbound comment replies is a separate Milestone 4
feature and is not enabled by these outbound settings.
## Persistent Data
Compose mounts: