Causality map
This is the single biggest reference page in the academy. It lists every meaningful user action in dooer, alongside everything that ripples when you do it: which models are touched, which notifications fire, which audit log entries get written, which gamification effects trigger, and where it shows up in the UI.
Read this top-to-bottom and you have the full causal model of the app. Every lesson in the academy points back at one or more rows here.
How to use this page
- As a learner — open this when you finish a journey. Find the row that matches the action you just took. Notice the whole cascade, not just the change you saw on screen. That awareness is what separates "I clicked a button" from "I understand the system."
- As a builder / debugger — when something behaves unexpectedly, find the row for the action that triggered it. The downstream columns tell you which signal handlers and side-effects to inspect.
Conventions
- Notifications fired — these are
Notification rows created in the database; each one fires post_save → _BoundedExecutor → SMTP (after transaction.on_commit()).
- AuditLog action — the
action field on the row written to AuditLog. The row also stores changes_json (before/after) and the actor_id.
- Gamification effect — only
WorkItem completion mutates dooer_points. See How it works.
Tasks (WorkItem)
| Action |
Models touched |
Notifications fired |
AuditLog action |
Gamification |
UI ripples |
| Create quick task |
WorkItem |
TASK_ASSIGNED (if assignee ≠ self) |
create |
— |
Task appears in board column matching status; dashboard tile counts update; assignee's bell badge increments |
| Create full-brief task |
WorkItem + BriefSequence |
BRIEF_ISSUED + TASK_ASSIGNED |
create |
+10 if status=Complete on create |
Brief ID printed on task (BR-{ORG}-{PROJECT}-{YEAR}-NNN); brief downloadable as .docx |
| Edit task field inline |
WorkItem |
— |
update |
— |
Field re-renders; if status changed, task may move between Kanban columns |
| Mark task Complete |
WorkItem (completed_at stamped), UserProfile (points) |
STATUS_CHANGED_TERMINAL → original_assigner |
update |
+5 (quick) or +10 (brief); may trigger level-up email |
Task moves to Done column; level-up modal pops if threshold crossed; profile shows new points total |
| Reassign task |
WorkItem.assignee_id |
TASK_ASSIGNED (new), TASK_REASSIGNED (original assigner), TASK_REASSIGNED_AWAY (old; suppressed if actor==old) |
reassign |
— |
All three recipients see entries in their bells |
| Add subtask |
WorkItem (parent_id set) |
TASK_ASSIGNED (if assignee ≠ self) |
create |
— |
Subtask nested under parent; parent's effort_hours auto-rolls-up |
| Add predecessor (blocker) |
Dependency |
— |
— |
— |
"Blocked" badge appears on the dependent task; cleared when blocker completes |
| Drag task in Eisenhower |
WorkItem.priority and/or status |
— |
update |
— |
Quadrant grid + 5-day strip both recalculate |
| Drag task to day column (5-day) |
WorkItem.target_date |
— |
update |
— |
Daily workload gauge recomputes; overbook warning fires if cap exceeded |
| Propose effort |
WorkItem.effort_hours_pending |
EFFORT_REVISION_REQUESTED → original_assigner |
effort_propose |
— |
Approver sees row in /approvals |
| Approve effort |
WorkItem.effort_hours locked; parent rolls up |
EFFORT_REVISION_APPROVED → assignee |
effort_approve |
— |
Effort badge moves from "Proposed" → locked value |
| Reject effort |
WorkItem.effort_hours_pending cleared |
EFFORT_REVISION_REJECTED → assignee |
effort_reject |
— |
Assignee re-proposes |
| Accept pending task |
WorkItem.acceptanceStatus=Accepted |
(none) |
update |
— |
Task leaves approvals queue; appears in assignee's task list |
| Reject pending task |
WorkItem.acceptanceStatus=Rejected |
(none) |
update |
— |
Task returns to pending queue if reassigned |
| Stop recurrence |
WorkItem.recurrence cleared |
— |
update |
— |
Future occurrences removed; existing task remains |
| Delete task |
WorkItem deleted |
(cascade cleanup via pre_delete signal) |
delete |
— |
Task removed from board; confirmation modal shown first |
| Upload attachment |
Attachment |
RESOURCE_UPLOADED → stakeholders |
attachment_add |
— |
Attachment list on TaskDetail; file size + uploader stamped |
Feedback
| Action |
Models touched |
Notifications fired |
AuditLog action |
UI ripples |
| Add feedback to task |
Feedback (unique pending-per-author constraint) |
(effort-type feedback triggers approval chain) |
feedback_create |
Appears on TaskDetail Feedback tab; rolls up to project's Feedback Register tab |
| Edit feedback |
Feedback |
— |
feedback_update |
Updated row in registers |
| Convert feedback to task |
New WorkItem (origin=feedback); Feedback.status=Approved |
TASK_ASSIGNED if assigned |
create + feedback_approved |
Feedback marked closed; new task appears in board |
| Approve feedback |
Feedback.status=Approved |
(depends on type) |
feedback_approved |
Status badge changes |
| Reject feedback |
Feedback.status=Rejected |
— |
feedback_rejected |
Status badge changes |
Projects
| Action |
Models touched |
Notifications fired |
AuditLog action |
UI ripples |
| Create project |
Project (slug unique per org) |
(none) |
create |
Appears in "Planning" column of /projects |
| Change project status |
Project.status |
(none) |
update |
Repositioned in project Kanban; status visible on linked tasks list |
| Edit project name/description |
Project |
— |
update |
Header updates everywhere project is referenced |
| Upload project asset |
Resource (project-level) |
PROJECT_ATTACHMENT_ADDED → project watchers |
attachment_add |
Project → Assets tab |
| Delete project |
Project (cascade) |
— |
delete |
Project removed from /projects; linked tasks lose project_id |
Meetings
| Action |
Models touched |
Notifications fired |
AuditLog action |
UI ripples |
| Create meeting |
Meeting + MeetingAttendee × N |
MEETING_SYNCED_FROM_OUTLOOK (if Outlook-synced) |
create |
Meetings list; on each attendee's calendar view |
| Add meeting decision |
(decision sub-record) |
— |
— |
Decisions log on MeetingDetail |
| Add meeting action item |
WorkItem (linked via meeting) |
TASK_ASSIGNED |
create |
New task in board, linked back to meeting |
| Add meeting note |
MeetingNote + WorkItemBriefNote (if linked to a task) |
— |
— |
Notes panel on MeetingDetail; back-link on linked task |
| Set RACI role on attendee |
MeetingAttendee.raci_role |
— |
— |
Role tag visible on attendee chip |
| Delete meeting |
Meeting (cascade) |
— |
delete |
Confirmation; removed from list |
| Action |
Models touched |
Notifications fired |
AuditLog action |
UI ripples |
| Add comment on task |
Comment + N × Notification (one per watcher) |
COMMENT_ADDED to each watcher (excluding self) |
implicit |
Comment appears on task; watcher bells + emails |
Comment cost
A comment on a 10-watcher task = 10 emails. Make comments worth that price. (See T-1.6.)
Notifications
| Action |
Models touched |
Notifications fired |
AuditLog action |
UI ripples |
| Mark single notification read |
Notification.is_read=true |
— |
— |
Bell badge decrements |
| Mark all read |
Notification × N |
— |
— |
Bell badge resets to 0 |
| Delete notification |
Notification (soft-delete) |
— |
— |
Disappears from bell |
Notes
| Action |
Models touched |
Notifications fired |
AuditLog action |
UI ripples |
| Create note |
MeetingNote (free-standing) |
— |
create |
Appears in Notes list |
| Edit note |
MeetingNote |
— |
update |
Live preview updates; linked task panels refresh |
| Link note to task |
WorkItemBriefNote (M2M junction) |
— |
— |
Note appears in task's Related Notes panel; back-link on note |
| Delete note |
MeetingNote (cascade junctions) |
— |
delete |
Note removed from list; links cleared |
Sticky Notes
| Action |
Models touched |
UI ripples |
| Create sticky |
StickyNote (per-user, max 10) |
Note appears on dashboard sticky board |
| Edit sticky |
StickyNote (debounced save on blur) |
Persisted after ~2s of no typing |
| Delete sticky |
StickyNote |
Note removed |
Account / Settings
| Action |
Models touched |
Notifications fired |
AuditLog action |
UI ripples |
| Edit profile |
UserProfile |
— |
— |
Avatar/bio updated everywhere |
| Change password |
User.password + JWT blacklist |
— |
— |
All outstanding refresh tokens for that user invalidated |
| Edit notification preferences |
UserProfile.email_notifications_enabled |
— |
— |
Future notifications respect new prefs |
| Invite team member |
Invitation |
(welcome email after acceptance) |
invite |
Pending invite in Settings → Team |
| Remove team member |
Organization.members |
— |
member_remove |
Member loses access |
| Change member role |
RBAC group |
— |
role_change |
Member's view changes accordingly |
Read-only actions (no side effects)
These show data but don't mutate anything. Useful to know which ones are "safe to explore":
- View dashboard, board, calendar, reports.
- Filter views (filters are client-side or query-string).
- Search globally (Cmd+K).
- Export to XLSX (read + format; doesn't change data).
- Drill into Manager Reports.
- Open Audit Log.
The big picture
Three patterns repeat across the table:
- Every meaningful change creates an audit log row. That's the configuration baseline (see Configuration baseline).
- Most actions fire one or more notifications. Notifications are dooer's "information radiator" — they push state to people who need it (see Capture, organize, reflect).
- The accountability lineage is preserved.
original_assigner_id never changes. It outlives every reassignment.
If you understand those three patterns, you understand 80% of the system.
← Academy