* feat(project): add project completion with celebration + trophy view
Completing a project marks it done and archives it, with a celebration
dialog (confetti + live stats) and a prompt to resolve unfinished tasks
(move to Inbox / mark done). Completed projects show a trophy badge and
a Reopen action on the archived-projects page.
isDone stays distinct from isArchived; selectArchivedProjects is left
intact so completed projects' tasks stay filtered out of Today/Overdue.
Append/merge deferred to #8032. Plan: docs/plans/2026-06-05-project-completion.md
* refactor(project): drop Archive menu item; Complete is the retire path
Archive and Complete produced near-identical end states; collapse to one
user-facing action. Removes the 'Archive project' menu item and handler so
Complete is the single way to retire a project. The archiveProject action,
reducer and ProjectService.archive() stay (needed for op-log replay of
historical archive ops and the legacy unarchive/restore path). Wiki updated;
menu specs cover the Complete flow.
* fix(project): keep completion dialog open for the active project
MatDialog closeOnNavigation (default true) dismissed the celebration the
moment completing the currently-active project navigated to '/'. Navigate
first, then open the dialog.
* test(project): e2e for completion flow (complete, celebrate, reopen)
Covers the resolve-unfinished-tasks prompt, the celebration dialog with
stats, the trophy badge on the archived page, and reopening. Adds an
openProjectContextMenu page-object helper.
* feat(project): confirm project completion
* feat(project): show completion celebration fullscreen
* fix(project): harden completion celebration flow
* style(project): use spacing tokens in completion dialog
* fix(project): align completion screen with project context
* style(project): soften completion screen coloring
* style(project): reuse completion screen surfaces
* fix: refine project completion dialog actions
* fix: complete projects with atomic task resolution
* fix: restore archive path in project completion UI
* refactor(project): remove dead completion code
The project-level completeProject action (OpType.Update) was never
dispatched — completion goes exclusively through the atomic
TaskSharedActions.completeProject (OpType.Batch) meta-reducer. Drop the
dead action, its reducer case, the PROJECT_COMPLETE enum member and the
immutable 'PCO' op-log code (which would otherwise be permanently
reserved for an op that can never be produced); enum count 147->146.
Also remove the unused selectCompletedProjects / selectPlainArchivedProjects
selectors (no consumers) and the misspelled, unused 'angel' confetti
field, keeping the regression tests that guard selectArchivedProjects.
* fix(project): make project completion non-reversible
Completion resolves a project's open tasks (move-to-inbox / mark-done),
which reopen cannot truly restore, so the "Reopen"/"Undo" affordances on
the completion path were misleading. Drop the celebration dialog's Reopen
button and the post-complete undo snack; the fullscreen celebration is
the feedback and deliberate reactivation still lives on the
archived-projects page (Project.reopen kept for it).
Also restore the project title param on the archive confirm dialog (it
was rendering a raw {{title}} placeholder), remove the now-unused
moveTasksToInbox / markTasksDone resolution helpers (the meta-reducer
resolves tasks atomically), and drop the orphaned UNDO / S.COMPLETED
i18n keys. Updates the completion e2e to the close flow and asserts the
resolution props are forwarded to the atomic action.
* fix(tasks): cancel native reminders for project-completed tasks
Completing a project marks its unfinished tasks done inside the
meta-reducer (no per-task updateTask), so unscheduleDoneTask$'s
native-reminder cancellation is bypassed and an OS-scheduled Android
notification could still fire for a now-done task. Add a local-only
effect that cancels native reminders for the force-completed task ids.
Local-only by design: it dispatches no actions (the persistent
dismissReminderOnly/clearDeadlineReminder would each be an extra synced
op), and done tasks are already filtered from reminders$ on all
platforms — only the native Android notification needs explicit removal.
* test(project): drop TaskService spies orphaned by helper removal
getByIdWithSubTaskData$/moveToProject/setDone/setUnDone were only used
by the removed moveTasksToInbox/markTasksDone helpers and their deleted
tests.
* feat(sync): affectedEntities multi-entity conflict detection for atomic completion
WIP checkpoint of the atomic completeProject approach. The Batch op declares
every touched entity (PROJECT, INBOX, TASKs, TODAY_TAG) via a new
affectedEntities field threaded through op-log capture, conflict detection,
the sync server (+Prisma migration) and shared-schema. Per-effect
completeProject handlers (issue two-way-sync, time-block, repeat-cfg)
re-derive the task changes the atomic op bypasses.
* revert(sync): remove affectedEntities multi-entity conflict detection
Reverts 0893a86162. The affectedEntities feature existed solely to make the
atomic completeProject Batch op sync-correct (its only producer). Decoupling
project completion into normal per-task ops (next commit) makes the existing
per-entity conflict detection and effects fire naturally, so this entire
layer — sync-core, super-sync-server, shared-schema, op-log plumbing, the
Prisma migration, and the per-effect completeProject listeners — is no longer
needed. Preserved in history via the checkpoint commit.
* refactor(project): decouple completion from task resolution (Option C)
Completion was an atomic multi-entity Batch op (completeProject) that marked
tasks done / moved them to Inbox inside the project-shared meta-reducer.
Because it bypassed the normal per-task actions, every downstream consumer had
to be taught about it separately — conflict detection (the affectedEntities
feature, reverted in the previous commit), native-reminder cancellation, issue
two-way-sync, time-block and repeat-cfg effects.
Decouple instead: completion is now a plain single-entity PROJECT flag flip
(completeProject = OpType.Update, mirroring archiveProject). Unfinished-task
resolution runs first as the normal per-task actions (moveToOtherProject /
updateTask isDone) from the completion flow, so the existing effects and
per-entity conflict detection fire naturally — no special-casing anywhere.
- project.actions/reducer: restore plain completeProject action + on() handler
- project.service: complete() is a flag dispatch; restore moveTasksToInbox /
markTasksDone (normal per-task dispatch + Rule #6 flush)
- work-context-menu: resolve unfinished work before the flag flip
- drop the completeProject meta-reducer block, the Batch action, the
TASK_SHARED_COMPLETE_PROJECT op code, and the reminder-cancel effect
(unscheduleDoneTask$ already cancels native reminders on the normal path);
current-task clearing is covered by the existing task-internal effect
Net: ~190 LOC removed here on top of ~1565 (affectedEntities + a Prisma
migration) in the revert. Completion's task resolution is not undone either
way, so the atomic bundle never bought a clean reversal.
* docs(project): record decoupled-completion decision (ADR #5)
Document why project completion uses decoupled per-task resolution + a plain
single-entity flag flip instead of an atomic multi-entity op: the atomic op
forced a cross-stack affectedEntities conflict-detection feature and per-effect
listeners, for an undo guarantee it never delivered. Adds ARCHITECTURE-DECISIONS
#5 and a revision note + corrected undo/bulk-mechanic notes in the plan doc.
* test(project): pin completion ordering + resolution edge cases
Address multi-agent review of the decoupled-completion refactor:
- assert resolution (moveTasksToInbox / markTasksDone) runs BEFORE the
completeProject flag flip (toHaveBeenCalledBefore) — the core invariant of
the decoupled design that was previously not pinned
- cover the not-done branch of moveTasksToInbox (no setUnDone) and assert
markTasksDone dispatches exactly the passed set
- add explicit PCO encode/decode round-trip assertions
- document the inbox-path current-task carry-forward nuance in ADR #5
Composition is covered end-to-end by e2e/tests/project/project-completion.spec.ts.
* refactor(project): tighten completion flow per review
- collapse 3x getCompletionInfo() to <=2: gate the resolve prompt once,
recompute only after a resolution; each call now wrapped with an error
snack so a failed archive load no longer aborts silently
- drop the dead post-confirm re-prompt branch (unreachable between two
sequential single-user modals)
- reuse getDiffInDays + dateStrToUtcDate in completion-stats util instead
of hand-rolled local-midnight/duration helpers
- use a Set for the top-level-task membership check (was O(n*m))
- drop redundant inline dialog sizing; the panelClass owns fullscreen
- remove dead --project-complete-accent test assertion
* refactor(project): finish review follow-ups for completion flow
- W3: reset the celebration confetti instance on dialog destroy so its
rAF loop + window resize listener are torn down when the dialog closes
before the animation ends (ConfettiService now returns the handle and
fires without awaiting completion)
- S2: extract resolveBgImageToDataUrl() shared by app.component and the
celebration dialog (was duplicated file://->data-url resolution)
- S3: split completeProject() into _getCompletionInfoOrNotify (dedupes the
error handling), _promptResolveUnfinishedTasks and _confirmCompletion
- W2: keep prefers-reduced-motion gating app-wide (a11y) + document intent
* fix(project): close confetti teardown race on early dialog close
If the celebration dialog is dismissed while canvas-confetti is still
loading, the instance was assigned after ngOnDestroy ran, so reset() never
fired and the rAF loop + resize listener leaked. Guard with an _isDestroyed
flag and reset the instance immediately if it arrives post-destroy.
Also drop the now-dead CanvasConfetti type alias (superseded by
ConfettiInstance, zero references).
* docs(project): drop non-existent undo-snack from completion wiki
* refactor(project): extract completion task-tree and dialog helpers
* refactor(project): hide Archive menu item; Complete is the retire path
* refactor(project): drop dead archive(), reuse resolve-choice type
Multi-review follow-ups on the completion feature:
- Remove orphaned ProjectService.archive() (+ unused import, spec) — the
menu collapsed Archive into Complete, leaving no caller. The
archiveProject action/reducer stay for op-log decode of historical ops.
- Reuse the exported ResolveUnfinishedTasksChoice type instead of
re-spelling the union three times in work-context-menu.
- Fix misleading moveTasksToInbox comment (setUnDone re-opens, not move).
- Note the as-shipped deviations (no extra selectors, no celebration
effect) in the design plan so they aren't hunted for later.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
5.5 KiB
Project View
A project is the primary way to organize tasks in Super Productivity. Every task belongs to exactly one project, making projects the main organizational structure for your work.
What Projects Are
Projects are self-contained workspaces. Each project has:
- Task lists — Active tasks and an optional backlog
- Notes — Project-specific notes
- Theme settings — Custom colors and appearance
- Integration settings — Connections to external issue trackers (like Jira or GitHub)
When you switch to a project view, you see only the tasks that belong to that project, along with the project's notes and settings.
Projects Vs Tags
Projects and tags serve different organizational purposes:
| Aspect | Projects | Tags |
|---|---|---|
| Relationship to tasks | Each task belongs to exactly one project (required) | Each task can have zero or more tags (optional) |
| Purpose | Primary organizational structure — every task has a home project | Secondary labels for cross-cutting categorization |
| Features | Backlog, notes, theme, integrations | Simple labels for filtering and grouping |
A task must always belong to a project. Tags are optional labels you can add to tasks across different projects to create cross-cutting categories.
Project Features
Backlog: Projects can have a separate backlog list for tasks you're not actively working on. You can move tasks between the active list and the backlog.
Notes: Each project can have its own notes, separate from task notes.
Theme: Projects can have custom colors and appearance settings to help you visually distinguish them.
Integrations: Projects can connect to external issue trackers (like Jira, GitHub, GitLab) to sync tasks and track work.
Organizing Projects
Projects themselves are flat — there are no parent projects or subprojects. A project cannot contain other projects.
However, you can organize projects into folders in the navigation menu. Folders can be nested (folders within folders) to create a hierarchy for navigation, but this is purely for organization — it doesn't change how projects work or how tasks are stored.
You can move projects between folders without affecting the tasks or any other project data.
Hiding Projects
When a project is active but you do not want it in the sidebar project list, enable Hide project from sidebar in the project settings or use the Projects visibility menu in the sidebar.
Hiding a project changes navigation only. The project stays active, and its tasks can still appear in cross-project task views such as Today, Planner, Schedule, Boards, and Search. Project navigation helpers, including project short syntax, do not match hidden projects.
How Tasks Belong to Projects
Every task has a project assignment. When you create a task, it's assigned to the current project (or the Inbox if you're in a tag context without a default project). You can change a task's project by:
- Dragging and dropping the task to another project in the navigation
- Editing the task and selecting a different project
When you view a project, you see all tasks that belong to it. When you view a tag, you see tasks from multiple projects that have that tag, and each task shows its project as a badge.
Completing Projects
When you finish with a project, you complete it to retire it from your active workflow and celebrate the result. Right-click the project in the sidebar (or click the ⋮ button) and choose Complete project.
If the project still has unfinished tasks, you're asked what to do with them first: Move to Inbox (the default — they stay actionable), Mark as done, or Cancel. Before the project is completed, a final confirmation asks you to confirm the action.
On completion you get a full-screen celebration with a short summary — tasks done, time tracked, days worked, and how many days the project ran from its first worked day to completion (time-based stats are hidden when you don't track time).
A completed project becomes fully dormant — all its data is preserved, but it stops generating noise across the app. Specifically, it:
- Is removed from the sidebar project list and the main visibility checklist
- Has its tasks hidden from Today, Tags, Boards, Overdue, Deadline, and Scheduled views
- Has its repeating tasks suspended — no new instances are generated
- Has its reminders suppressed — no notifications fire for its tasks
Finding and reopening completed projects: click the eye icon in the Projects section header to open the visibility menu, then navigate to Archived projects, where completed projects appear with a trophy badge and their completion date. Use the Reopen action there to clear the completed state and bring the project back to the sidebar with all tasks, repeating configs, and reminders active again.
Related
- 4.02-Inbox-View — The default project for uncategorized tasks
- 4.07-Tag-View — How tags provide cross-cutting categorization
- 4.05-Board-View — Visual column-based organization
- 4.01-The-Today-View — Today's task list
- 4.03-Planner-View — Day-level planning across projects
- 4.04-Schedule-View — Time-based scheduling