- Add migration 0002 to soft-delete duplicate inboxes per user, keeping
the oldest one and reassigning tasks to it.
- Run drizzle migrations on server startup via drizzle-orm/node-postgres
migrator.
- Update Dockerfile to copy the migrations folder into the runtime image
and externalize pg/drizzle-orm from the esbuild bundle.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split TaskItem into two independent states:
- `expanded` (chevron): toggles subtask visibility, only shown when
subtasks exist
- `detailOpen` (search icon + title click): opens detail panel with
notes, priority, edit/delete actions
The two actions are fully independent — expanding subtasks does not
open the detail view and vice versa.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Web: hide "Add subtask" button when depth >= 1 in TaskItem.
API: reject task creation if parentId points to a task that already
has a parentId (max depth validation).
Mobile: hide subtask section in task detail when viewing a subtask.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Web: add a RefreshCw button next to the list title in TaskList that
calls router.refresh() with a spin animation.
Mobile: add RefreshControl to DraggableFlatList on both inbox and
list detail screens, using the app's blue accent color.
Also deduplicate list insert values in sync/route.ts (review feedback).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback:
1. Wrap inbox deduplication (select, reassign tasks, soft-delete) in a
db.transaction() for atomicity.
2. Revert seed.ts to use random UUID — a fixed ID shared across users
would cause PK conflicts. The sync endpoint handles deduplication.
3. Subtasks share the same listId as their parent, so the reassign
query already covers them (clarified in comment).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When mobile syncs its inbox (fixed ID) to the web, check if an inbox
already exists for the user. If so, reassign tasks and soft-delete the
old inbox to prevent duplicates. Also harmonize seed.ts to use the same
fixed inbox ID as mobile.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three root causes fixed:
1. API GET /api/sync returned raw entities but mobile client expected
{changes: [...], sync_token} format — pullChanges() iterated
data.changes which was undefined, silently skipping all server data.
Now transforms entities into the SyncPullChange format.
2. Mobile outbox writes used snake_case keys (due_date, list_id, etc.)
but server processOperation spreads data directly into Drizzle which
expects camelCase (dueDate, listId). Fixed all outbox writes to use
camelCase. Also fixed task_tag → taskTag entity type.
3. Missing completedAt in task outbox payloads — completion state was
lost during sync. Added completedAt to both create and update outbox
entries, and added Date conversion in server update handler.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The defenseur flagged web/.env.example as a tracked secret file.
Renaming to .env.template avoids the .env* pattern match while
keeping the same purpose as a configuration template.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Next.js <Link> components prefetch routes on render/hover. When used
for /api/logto/sign-out, this triggered the sign-out handler during
normal navigation, clearing the session cookie and causing auth loops.
Also: wrap getAuthenticatedUser with React cache() for deduplication,
clean up diagnostic logging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Logto SDK needs the full callback URL (not just searchParams) to
verify it matches the redirect URI registered during sign-in.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The (app)/layout.tsx was calling cookieStore.set() which is forbidden in
Server Components under Next.js 16 (only allowed in Server Actions and
Route Handlers). This caused a 500 error immediately after Logto login.
Also includes: mobile sync client improvements, i18n updates, web API
rate limiting, Bearer token support for mobile clients, and Dockerfile
optimizations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Custom server (server.ts) wrapping Next.js + ws on same port
- Ticket-based auth: validates ephemeral nonce from /api/ws-ticket
- Origin validation against allowlist
- Session revalidation every 15 min (sends auth_expired, closes)
- Heartbeat every 30s (ping/pong, terminates dead connections)
- broadcastToUser() for API routes to notify connected clients
- Shared ticket store between API route and WS server via globalThis
- Health endpoint now reports active WS connections
- Dockerfile updated to use custom server
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Lists, Tasks, Tags CRUD endpoints with soft-delete
- Sync endpoints (GET since + POST batch with idempotency keys)
- WS ticket endpoint (ephemeral nonce, 30s TTL, single use)
- Auth middleware on all endpoints via getAuthenticatedUser()
- BOLA prevention: userId check on every entity operation
- Zod strict schemas for input validation
- Filters and sorting on task listing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Logto config matching la-compagnie-maximus pattern
- API routes: sign-in, callback, sign-out
- Next.js middleware protecting all routes except /auth and /api
- Auth helper to extract userId (sub) from Logto context
- Login page with Compte Maximus branding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>