Case study · Web Application
TaskFlow
TaskFlow is a collaborative workspace for projects, tasks, and teammates. The public demo is an authenticated React app. A second Angular client was rebuilt against the same APIs to learn the framework.
Project snapshot
At a glance
- Role
- Full-Stack Developer
- Project type
- Collaborative workspace
- Frontend
- Next.js 16 · React 19 · Angular 22 (learning client)
- Backend
- Next.js Route Handlers · Postgres · Supabase RLS
- Testing
- React and Angular unit tests · Playwright QA
- Architecture
- Versioned writes, realtime as cache invalidation, and an IndexedDB outbox for safe mutations only.
- Status
- Live demo
Problem
Project tools have to show status, ownership, and deadlines without becoming noise—and the moment two people edit the same task, a fixture prototype stops being honest. I needed hierarchy in the UI and a backend that could survive refresh, roles, and conflicting writes.
Approach
I kept a shared app shell for navigation and command search, then moved entities onto Supabase with Google OAuth. TanStack Query owns server data; Zustand only keeps UI chrome. Mutations go through Next.js route handlers into services and repositories, with RLS enforcing workspace isolation on the user-scoped client.
How it works
Edits send an expectedVersion. A Postgres RPC updates the row only when the version still matches; otherwise the API returns 409 with the latest record and the UI opens a conflict dialog. Realtime postgres_changes invalidate Query keys for the workspace. Safe offline mutations queue in IndexedDB and replay on reconnect.
- React + TanStack Query
- Next.js API route
- Zod + session/RBAC
- Service
- Supabase client (RLS)
- Postgres / RPCs
- Realtime → cache invalidate
Engineering decisions
TanStack Query for entities, Zustand for chrome
Server state and UI chrome have different lifetimes. Query caches tasks and projects; Zustand keeps sidebar, density, presence, and conflict dialogs. Realtime invalidates Query keys instead of inventing a parallel store.
Row Level Security on the user client
Normal CRUD uses the cookie-scoped Supabase client so policies actually run. A secret admin client exists for seed/ops, not for everyday writes. That keeps authorization honest even if a route forgets a check.
Optimistic concurrency with versioned RPCs
I send expectedVersion on updates and let Postgres reject stale writes. The UI can offer the latest row instead of last-write-wins.
Offline outbox for safe mutations only
Status edits and comments can queue while offline; membership and deletes cannot. That keeps collaboration usable on flaky networks without letting dangerous ops replay blindly.
Verification
Built, not mocked.
What was deployed, executed, or observed.
Tested
React and Angular suites cover authentication, realtime behavior, and offline policy; Playwright covers the public demo surface.
Secure
Authentication uses Google OAuth with httpOnly cookies, while RLS and server authorization enforce workspace access.
Realtime
Task updates and workspace presence were verified across concurrent sessions.
Offline-capable
Safe mutations queue in IndexedDB and replay after reconnect; unsafe mutations remain online-only by design.
Accessible
Dialog focus handling, named controls, keyboard behavior, and axe checks are covered across the tested surfaces.
Known limits
- The public demo is the React client. Angular TaskFlow is a local learning rebuild, not production-deployed.
- Invitation email delivery is still deferred.
Current state
Signed-in visitors can run a seeded workspace: projects, tasks, comments, and teammates with roles. Conflicting edits surface a dialog instead of silent overwrites. Safe changes can queue offline and replay.
Implemented
- ✓ Supabase Auth with Google OAuth and proxy session refresh
- ✓ Workspaces, projects, tasks, assignees, comments
- ✓ RBAC roles with RLS policies
- ✓ Versioned updates and conflict UI
- ✓ Realtime invalidation + presence
- ✓ Offline outbox for safe mutations
- ✓ Invitations, notifications, attachments, audit views
Demo / seeded
- △ Seeded Portfolio Demo Workspace (projects, tasks, comments, attachments)
What I learned
I learned to treat realtime as a cache signal, not a second source of truth. Version checks on write are what keep two editors from silently overwriting each other.
Next
- 01Send production invitation emails for the existing token accept flow
- 02Support ownership transfer between workspace members
- 03Publish every table the client already subscribes to on supabase_realtime
- 04Improve kanban accessibility beyond native HTML drag and drop