# Task Labels and Projects — Design **Status:** Backlog idea, refined via interview 2026-07-15. Not yet approved for implementation. ## Context doot-native tasks have had a `Labels []string` field on `models.Task` (`internal/models/types.go`) since early in the project — it's fully wired at the store layer (persisted as a JSON column, scanned back out by `scanNativeTasks`) but never surfaced in any UI, web or widget. There's no editing, no display, no filtering. There's also no real "project" concept for doot-native tasks today. `Task.ProjectID`/`ProjectName` exist on the struct and `ProjectName` is a real column in `native_tasks`, but a repo-wide search turns up **zero** places that ever set `ProjectName` to a non-empty value when constructing a native task — it's vestigial, always empty in practice. This is likely a leftover from the Todoist-sync era (Todoist tasks did have real project names; the field just never got repurposed after native tasks replaced Todoist in commit `945c345`). So this feature isn't "finish wiring up projects" — it's building projects from scratch, alongside finally wiring up labels. This is a foundational feature: [[doot-future-task-scheduling-ideas]] items 1 (budgets/availability) and 4 (task chains) both reference "projects" as a dependency. Building this first unblocks both. ## Scope doot-native tasks only, matching every other feature built this session. Trello/Google Tasks cards keep whatever labeling concept they already have server-side (Trello lists, etc.) — this feature does not touch them. ## Data Model **Labels** — reuse the existing field, no schema change needed: - `Task.Labels []string`, already a JSON column on `native_tasks`. - Free-form text tags, many-to-many with tasks (a task can have any number of labels). - User-assignable color per label. Since labels are currently just strings with no identity beyond their text, color needs a home: a new small `labels` table (`name TEXT PRIMARY KEY, color TEXT`) mapping label text → color hex. A label typed on a task that doesn't yet exist in this table gets a default color (or prompts for one) the first time it's colored. **Projects** — new, lightweight: ```sql CREATE TABLE projects ( id TEXT PRIMARY KEY, name TEXT NOT NULL, color TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, archived BOOLEAN DEFAULT 0 ); ``` - `native_tasks` gets a new `project_id TEXT DEFAULT ''` column (a real FK-shaped reference, not the vestigial `project_name` string column — that column stays as dead weight for now since dropping SQLite columns needs a table-rebuild migration, out of scope for this feature; it just stops being read). - Exactly one project per task (`project_id` is a single string, empty = unassigned), per the interview answer. Labels handle the cross-cutting many-to-many need instead. - A project is not itself an atom/task — it's pure metadata (name + color) that a task points to. ## Recurrence Interaction Both `project_id` and `Labels` are **series-level metadata**: `CreateNextIteration` (in `internal/store/native_tasks.go`) already copies `content`, `description`, `project_name`, `priority`, and `labels` onto every new occurrence — `project_id` joins that same copy-forward list, no new mechanism needed. This matches the interview's "inherit automatically" answer exactly. ## API New endpoints, doot-only, matching the existing `/api/widget/task/*` pattern: - `GET /api/widget/projects` — list all non-archived projects (id, name, color), for populating a picker. - `POST /api/widget/projects` — create a project (name, color). - `POST /api/widget/task/project` — `{id, project_id}`, sets or clears a task's project. - `POST /api/widget/task/labels` — `{id, labels: []string}`, replaces a task's label set (matches `UpdateNativeTask`'s replace-whole-value style rather than an add/remove-single-label API). - Labels don't need their own CRUD endpoint in the same way projects do — a label is just a string until someone assigns it a color, so `POST /api/widget/task/labels` implicitly "creates" any new label text. A separate `POST /api/widget/labels/color` — `{name, color}` — sets a label's color. ## UI **Widget (Android):** per the interview, minimal — a small color mark on the time-grid row (project color takes priority for the row's accent; if labels need visual representation too, a second smaller dot, but start with just project color to avoid clutter). No filtering UI on the widget itself. **Task detail popup (`TaskDetailActivity`):** full display and editing — a project picker (chip showing current project, tap to open a picker/create-new dialog) and a label editor (chip-entry, similar in spirit to the recurrence weekday chips but free-text with autocomplete against existing label names), both using the color-coding. **Web:** out of scope for this spec's UI section — the interview didn't ask about web display specifically; assume parity with the widget's popup-level editing is desirable but not designed here. ## Testing Standard TDD per this project's convention: store-layer tests for `CreateProject`, `GetProjects`, `SetTaskProject`, `SetTaskLabels`, `SetLabelColor`; handler tests for the new endpoints (mirroring `widget_test.go`'s style); a `CreateNextIteration` test confirming `project_id` and `Labels` both carry forward to the next occurrence. ## Out of Scope - Project hierarchies (sub-projects, nesting) — flat list only. - Multi-project tasks — exactly one project per task, per the interview. - Archived-project handling in the UI beyond hiding them from the picker (no dedicated archive-management screen designed here). - Web UI specifics (noted above). - Migrating/backfilling the vestigial `project_name` column — it's simply superseded, not cleaned up, in this spec.