Plan work as a dependency graph: TaskWeave blocks circular dependencies, keeps task statuses in sync with their prerequisites, and draws the whole plan as an interactive graph.
▶ Live demo: the full UI running on an in-browser port of the API, preloaded with a sample project. Your changes stay in your browser.
| Task list | Cycle rejected with the full path |
|---|---|
![]() |
![]() |
- Create, edit and delete tasks from a list view, a status board or the graph.
- Add and remove dependencies. A dependency that would create a cycle is rejected, and the UI
shows the exact loop, for example
Model staging layer in dbt -> Build customer 360 dashboard -> Add data quality checks -> Model staging layer in dbt. - Automatic status updates: a task is
blockedwhile any dependency is unfinished and returns topendingwhen the last one completes. Completed tasks are never downgraded automatically. - Interactive dependency graph (React Flow with dagre auto-layout), nodes colored by status and animated edges where a prerequisite is still open.
- REST API with clear 400/404/409 errors, a demo data seeder and the Django admin.
- A live demo that needs no server: the GitHub Pages build runs a TypeScript port of the service layer and API validation in the browser and keeps its data in localStorage.
| Layer | Tools |
|---|---|
| Backend | Python 3.10+, Django 5.2 LTS, Django REST Framework, SQLite or MySQL |
| Frontend | React 19, TypeScript, Vite, Tailwind CSS 4, TanStack Query, React Flow, dagre |
| Quality | Django test runner, Vitest and Testing Library, Ruff, Oxlint, GitHub Actions |
| Demo | GitHub Pages, in-browser TypeScript port of the API, localStorage |
flowchart LR
subgraph Browser
UI["React app<br/>list / board / graph"] --> RQ["TanStack Query cache"]
RQ -. "live demo build" .-> DEMO["In-browser API<br/>TypeScript port + localStorage"]
end
RQ -- "JSON over HTTP" --> API["DRF TaskViewSet"]
API --> SVC["services.py<br/>cycle check, status cascade"]
SVC --> ORM["Django ORM"]
ORM --> DB[("SQLite or MySQL")]
Views only validate input and shape responses. All rules live in backend/tasks/services.py,
and every write that can affect readiness runs inside a transaction.
Adding "A depends on B" is only safe if B cannot already reach A. The service loads all edges in
one query, runs a breadth-first search from B and records each node's parent. If the search
reaches A, the parent chain gives the existing path from B to A; adding A in front closes the loop
A -> B -> ... -> A. The API returns that path with task titles, and the database enforces
unique, non-self-referencing edges as a second line of defense.
GitHub Pages only serves static files, so the demo build (npm run build:demo, which sets
VITE_DEMO=true) swaps the HTTP client for an in-browser implementation of the same API interface.
frontend/src/demo/ ports services.py (the BFS cycle check, derived statuses and the downstream
cascade) and the viewset's validation, so the UI gets the same responses and the same 400, 404 and
409 errors. Each write is applied to a copy and saved to localStorage only if it succeeds, and
Reset demo data reloads the seed_demo project. The backend test cases are ported to Vitest
and run against this port to keep the two in step.
More detail, including the status rules and trade-offs, is in docs/design-decisions.md.
.
├── .github/workflows/ CI, plus the GitHub Pages deployment of the demo
├── backend/
│ ├── backend/ Django project settings and root URLs
│ └── tasks/
│ ├── models.py Task and TaskDependency (with DB constraints)
│ ├── services.py Cycle detection and status propagation
│ ├── serializers.py
│ ├── views.py TaskViewSet, graph and health endpoints
│ ├── management/commands/seed_demo.py
│ └── tests/ Service and API tests
├── frontend/
│ └── src/
│ ├── api/ Fetch client and TanStack Query hooks
│ ├── components/ List, board, graph, task panel, dependency editor
│ ├── demo/ In-browser port of the API for the live demo
│ └── lib/ Status metadata and graph layout
├── docs/ Design decisions and screenshots
├── requirements.txt Runtime dependencies (pinned)
├── requirements-dev.txt Adds Ruff
└── requirements-mysql.txt Adds the MySQL driver
Prerequisites: Python 3.10 or newer and Node.js 20.19 or newer (CI uses Node 24).
The API listens on port 8101.
macOS / Linux:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
cp backend/.env.example backend/.env
cd backend
python manage.py migrate
python manage.py seed_demo # optional sample project
python manage.py runserver 8101Windows (PowerShell):
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements-dev.txt
Copy-Item backend\.env.example backend\.env
cd backend
python manage.py migrate
python manage.py seed_demo
python manage.py runserver 8101To use MySQL instead of SQLite, install requirements-mysql.txt, create the database and set
DB_ENGINE=mysql plus the DB_* values in backend/.env.
Run this in a second terminal. The dev server uses port 5101 and calls the API on port 8101; open the address Vite prints.
cd frontend
npm install
cp .env.example .env # Windows: Copy-Item .env.example .env
npm run devTo try the frontend on its own, run it on the in-browser API used by the live demo:
cd frontend
npm install
npm run dev:demoBackend variables are read from the environment or backend/.env. The defaults suit local
development; backend/.env.example and frontend/.env.example list the exact values.
| Variable | Default | Description |
|---|---|---|
DEBUG |
false |
Enables debug mode and the browsable API |
SECRET_KEY |
none | Required when DEBUG is off |
ALLOWED_HOSTS |
the local machine | Comma-separated host names |
CORS_ALLOWED_ORIGINS |
the dev server on port 5101 | Origins allowed to call the API |
DB_ENGINE |
sqlite |
sqlite or mysql |
DB_NAME |
backend/db.sqlite3 / taskweave |
SQLite file path or MySQL database name |
DB_USER, DB_PASSWORD |
root, empty |
MySQL credentials |
DB_HOST, DB_PORT |
the local machine, 3306 |
MySQL server |
VITE_API_URL (frontend) |
the API on port 8101 | API base URL used by the React app, including /api |
VITE_DEMO (frontend) |
not set | true runs the app on the in-browser API; npm run build:demo sets it |
All endpoints are under /api/ and exchange JSON.
| Method | Path | Description |
|---|---|---|
GET |
/health/ |
Liveness check |
GET |
/tasks/ |
List tasks with dependencies and dependents ids |
POST |
/tasks/ |
Create a task (title, description, status) |
GET |
/tasks/{id}/ |
Retrieve a task |
PUT / PATCH |
/tasks/{id}/ |
Update a task; status changes cascade to dependents |
DELETE |
/tasks/{id}/ |
Delete a task and re-evaluate its dependents |
POST |
/tasks/{id}/dependencies/ |
Add a dependency: {"depends_on_id": 3} |
DELETE |
/tasks/{id}/dependencies/{depends_on_id}/ |
Remove a dependency |
GET |
/graph/ |
Nodes (id, title, status) and edges (task, depends_on) |
A rejected cycle returns 400:
{
"detail": "Adding this dependency would create a cycle: Extract → Load → Transform → Extract",
"cycle": [
{"id": 1, "title": "Extract"},
{"id": 3, "title": "Load"},
{"id": 2, "title": "Transform"},
{"id": 1, "title": "Extract"}
]
}Other errors: 404 for unknown or malformed ids, 409 for a duplicate dependency, 400 with
field messages for invalid input (for example completing a task whose dependencies are unfinished).
cd backend && python manage.py test # 52 tests: cycles, status rules, API, seeding
ruff check backend # from the repository root
cd frontend
npm run lint
npm test # includes the backend cases ported to the demo
npm run build
npm run build:demo # the GitHub Pages buildGitHub Actions runs the backend suite on Python 3.10 and 3.12 and lints, tests and builds the
frontend on every push and pull request. Every push to main also tests and publishes the demo
build to GitHub Pages.



