Skip to main content

Tasks - Smart Task Management

Tasks is Tuturuuu’s intelligent task management system featuring a 6-level hierarchy, bucket dump for rapid capture, and cross-board project coordination.

Tasks and Calendar planning

Both satellite apps expose local planning views without changing workspace or requiring a cross-app login handoff: The shared sidebar retains locale and personal/internal workspace aliases, including on mobile. Calendar also serves /tasks/boards, /tasks/boards/{boardId}, and /tasks/{taskId} within the workspace. Task views hide the calendar-only mini-month sidebar to leave more room for task work. Tasks’ embedded calendar uses the shared scheduling, connection, event, and task controls and enforces manage_calendar. Each host resolves its own app-session actor. Keep the task route context and dialog manager prefix aligned: Tasks uses an empty prefix, while Calendar and Web use /tasks. Tasks already owns a dialog provider, so its embedded calendar disables the additional dialog wrapper. When validating this integration, check both locales, personal and team workspace links, board/task detail return paths, and mobile navigation with the sidebar closed. Mutations continue through the existing protected product APIs.

Recipient identity

Task and board shares match either an explicit recipient user id or the recipient’s current confirmed Auth email. Shared-task reads and edits use this rule, as do shared board navigation and access. Editable contact/profile email fields and app-session email claims do not select email shares. Public view links and workspace membership retain their existing access rules. Coverage lives in packages/tasks-api/src/server/board-access-identity.test.ts and the Tasks shared-task route tests.

Direct list and assignee access

Task-list reads require a MEMBER workspace membership or an explicit board share. Creating, renaming, moving, archiving or deleting a list requires a MEMBER with manage_projects, or a board share with edit permission. A move must pass this check for both the source and destination boards. A workspace GUEST has no implicit list access; an explicit board share still applies. Direct assignee INSERT and DELETE use the same member permission or an explicit board/task edit share. Public view links and view shares permit no assignment changes. Email shares use the current confirmed Auth email, matching the share APIs; editable profile fields and token email claims cannot supply that identity. Existing view-authorized assignment reads and service-role API access remain. These database rules cover Web, Tasks, Calendar and AI tools wherever a user client accesses these tables; mobile continues through the product APIs. Task-list change subscriptions use Cloudflare, so task_lists is excluded from supabase_realtime. Regression coverage is apps/database/supabase/tests/task-list-assignee-authorization.sql (internal security reviews), alongside board access and shared-task route tests.

Overview

Tasks provides:
  • Hierarchical organization - 6 levels from workspaces to individual tasks
  • Bucket dump - Capture notes and convert to tasks/projects
  • Cross-board projects - Coordinate tasks across multiple boards
  • Task cycles - Sprint/iteration management
  • Smart estimation - Fibonacci, exponential, linear, and t-shirt sizing
  • Custom statuses - Per-board status workflows
  • Labels & priorities - Flexible categorization

Task description media retention

Task descriptions in the Tasks app accept pasted or selected images and videos. The Tasks app session can read embeds from the task-images directory when the user has manage_drive_tasks_directory, the same permission required to upload them. General Drive access is not required for task media, and a Tasks session cannot read unrelated Drive or Finance files through the share route. Uploads made while creating a task remain available when the task is created or saved as a draft. Discarding the new-task dialog clears its local draft and requests immediate deletion of its uploads. A daily Tasks cleanup job removes task media older than 30 days when no current task or saved task draft references it. This includes media removed from an existing description and uploads left behind when a browser closes before the discard request completes. References in task version history do not retain media after removal from the current description. The Tasks Vercel project schedules /api/cron/tasks/cleanup-media at 03:17 UTC; the route requires CRON_SECRET or VERCEL_CRON_SECRET.

Task read RPC identity

get_user_accessible_tasks and get_user_tasks_with_relations accept an authenticated caller’s own user ID or the service role. Anonymous and mismatched callers receive SQLSTATE 42501. Signatures, filtering and successful self results remain unchanged across Tasks, Calendar, retained Web planning views and AI task tools. The database regression packet is apps/database/supabase/tests/time-task-rpc-scoping.sql.

Personal placement and template backgrounds

Personal placement in Tasks requires current source-task access, including a valid board share where supported. An assignment or personal override alone does not authorize placement after source access is lost. Moving a task to a terminal status also requires source edit access. The personal-placement route scenario tests cover shared-board access and stale assignments or overrides. Template background keys must belong to the template’s workspace under its template-backgrounds prefix. Tasks, shared template UI and retained Web routes apply the same rule before saving or signing a background. The helper regression cases live in packages/utils/src/__tests__/template-background-path.test.ts.

Board And List Name Uniqueness

  • Active and archived task board names are unique per workspace using lower(btrim(name)); only trashed boards with deleted_at set release the name.
  • Non-deleted task list names are unique per board using lower(btrim(name)), regardless of status/category.
  • Duplicate board writes return 409 with code: "TASK_BOARD_NAME_EXISTS". Duplicate list writes return 409 with code: "TASK_LIST_NAME_EXISTS".
  • Existing duplicate rows are preserved by the forward migration that renames older duplicates with a deterministic (duplicate <id>) suffix before the unique indexes are created. The migration does not move or delete tasks.

Satellite Auth And API Ownership

apps/tasks is registered internally as the tasks app. App-session checks, cross-app token verification, and Web API allowlists should use targetApp: 'tasks' unless a route explicitly accepts another first-party audience such as the CLI’s Web-minted targetApp: 'platform' task tokens. apps/tasks is also the canonical owner for authenticated task product pages and task-owned APIs. Do not add new task UI or task API behavior to apps/web. Old authenticated Web task URLs should stay as redirects to the Tasks origin, while /products/tasks remains Web-owned marketing content. Task-owned API families include tasks, task boards, board aliases, labels, templates, projects, initiatives, cycles, drafts, task progress, plans, shared task links, admin task embeddings, task webhooks, task cron routes, Mira task endpoints, habits, and habit trackers. First-party task clients should resolve the Tasks origin through TASKS_APP_URL, NEXT_PUBLIC_TASKS_APP_URL, or TUTURUUU_TASKS_BASE_URL. Legacy TUDO_APP_URL and NEXT_PUBLIC_TUDO_APP_URL are accepted only as compatibility aliases. Local development falls back to the Tasks app origin (http://localhost:7809 or the matching portless Tasks domain). SDK, CLI, mobile, and packages/internal-api task-domain helpers should use that Tasks origin outside apps/tasks; inside apps/tasks, relative API calls are still preferred. Tasks may serve credentialed browser API traffic from first-party Web/platform origins, but CORS must stay origin-specific. Do not use wildcard credentialed CORS for task APIs.

Task Embedding Webhook Operations

The Supabase task embedding webhook must send its configured shared secret in the x-webhook-secret header. Set SUPABASE_WEBHOOK_SECRET in the Tasks deployment and use the same value in the Supabase Database Webhook header configuration. Use a secret placeholder in local examples and operational documentation; never record the deployed value. The route fails closed before parsing the payload or creating an admin Supabase client. Missing or empty server configuration returns a server configuration error, while an absent or mismatched request header returns 401 Unauthorized. CLI task commands call the Tasks origin with bearer app-session tokens minted by apps/web. The Tasks proxy must pass ttr_app_... bearer tokens to route-level withSessionAuth so the route can verify CLI scope, expiry, and target-app allowlists. Production apps/web and apps/tasks must share TUTURUUU_APP_COORDINATION_SECRET, or the same accepted fallback signing secret, or freshly minted CLI tokens will be rejected as Unauthorized. The local /api/auth/verify-app-token route must delegate token validation to the central Web verifier with verificationBaseUrl: TTR_URL. That handoff sets both the host-only Tasks app-session cookie and the Web-issued app-session cookie used by central API requests. Do not reintroduce Tasks-local Supabase session validation for cross-app login. Tasks must also keep a local /login page. It is a redirect-only route: valid Tasks app-session cookies continue to /personal/tasks or the safe next path, while missing or stale cookies redirect to apps/web login with a /verify-token return URL. Without this route, a successful Web handoff can set cookies and still leave the browser on a Next.js 404. The Tasks app should not authenticate protected product APIs with local Supabase Auth cookies. Fallback /api/* traffic is proxied through the local catch-all API route, which strips sb-*-auth-token cookies before forwarding to Web. Keep the corresponding Web routes app-session aware for targetApp: 'tasks'; otherwise the browser can have valid Tasks cookies while the data layer still loops through 401 and proxy-side 429 responses. Task board/list count endpoints must compute counts in the database, preferably through private service-role RPCs, and return one aggregate row per board/list. Do not hydrate every matching tasks row in an API route just to count them.

Board Capacity Rules

Board capacity rules provide reusable work-in-progress controls for task queues. Rules can select lists, labels, and projects; selector dimensions combine with AND, while labels and projects can use Any or All matching. A matching task is counted once even when several relations match. Rules can measure task count or estimation points and can count active tasks (the default) or all non-deleted tasks. Authenticated clients use these Tasks-owned routes:
  • GET/POST /api/v1/workspaces/{wsId}/task-boards/{boardId}/capacity-rules
  • PATCH/DELETE /api/v1/workspaces/{wsId}/task-boards/{boardId}/capacity-rules/{ruleId}
Joined workspace members and direct board guests may read capacity status. Only joined members with manage_projects may configure rules. Hard limits are enforced in PostgreSQL under a board-scoped transaction lock so browser, CLI, guest, bulk, and API writes share the same admission decision. Rejected writes return 409 with code: "TASK_CAPACITY_EXCEEDED" and structured rule usage. Successful task writes may include capacityWarnings for exceeded soft rules. Active tasks are non-deleted, not completed or closed, and outside done and closed lists. Missing estimation points contribute zero. A rule whose selected list, label, or project is deleted is disabled automatically, preventing an empty selector from accidentally broadening its scope.

Rich Text Editor Notes

The task editor includes isolated React roots inside some Tiptap node views, especially task mention chips.
  • Do not mount next-themes ThemeProvider inside those isolated createRoot(...) islands. next-themes injects an inline script, and Next.js 16 will warn or fail when that script is rendered from a client subtree instead of the app root.
  • Resolve theme for those islands from document.documentElement and prefers-color-scheme, then pass that resolved value through local component state instead of re-creating app-wide providers.
  • Add referrerPolicy="strict-origin-when-cross-origin" to every YouTube iframe path used by the task editor or its previews. Missing referrer policy can surface as YouTube Error 153 even when the video URL itself is valid.
  • Canonical deep links for board tasks should use the board route with the task query parameter: /{wsId}/tasks/boards/{boardId}?task={taskId}.
  • Tuturuuu Mobile normalizes production web links from https://tuturuuu.com into native routes for task boards, board task details, task estimates, task portfolio/project routes, and common workspace modules. Mobile accepts both ?task={taskId} from web and ?taskId={taskId} from native routes.
  • Links can opt out of native app opening with native=0 or openInBrowser=1. Use this for flows that must stay in the browser, such as debugging, web-only admin surfaces, or temporary compatibility issues.
  • Avoid generating or persisting modal-only URLs such as /{wsId}/tasks/{taskId} for share/copy flows unless the dedicated task detail page is the explicit target. The board-backed URL is the reliable reloadable path used by the dashboard task dialog.
  • When reloading or recovering a task deep link, resolve the task through the workspace-scoped task API (/api/v1/workspaces/{wsId}/tasks/{taskId}) rather than the current-user task dialog API. The current-user endpoint can 404 for valid board tasks because it is optimized for dialog hydration, not route recovery.
  • Android opens supported links by default only after App Links verification succeeds. Keep apps/web/public/.well-known/assetlinks.json in sync with the production Android signing certificate fingerprint; if Play App Signing uses a different app-signing certificate than the repo release keystore, add the Play Console SHA-256 fingerprint before release.
  • Keep GoRouter.overridePlatformDefaultLocation enabled in mobile. iOS can provide a universal link URL as Flutter’s platform default route on cold start, and GoRouter will show a no-route page unless app_links is allowed to normalize that URL first.

Mobile task estimation recovery

Mobile Planning > Estimations accepts boards without estimation settings and uses the API defaults: no method, standard range, zero estimates allowed, and unestimated issues excluded. Invalid board fields or unsupported methods produce the existing error and Retry state instead of an uncaught cast. Invalid stored snapshots are discarded before seeding the route so entry can reload fresh data. Responses arriving after the estimation screen is disposed do not update it. A rejected save still reports failure after a workspace change. A successful PATCH with an unreadable response body is not replayed: the screen refetches boards from the server, and load failures use the translated error and Retry state. The estimation editor scrolls its controls and actions together on small screens with enlarged text. These rules apply to mobile estimation settings; web settings and sibling planning tabs are unchanged. Regression coverage lives in task_estimate_board_test.dart, task_estimates_cubit_test.dart, and task_estimate_dialog_test.dart.

Mobile Board List Mode Notes

  • Mobile board list mode hides documents, done, and closed task lists by default when no explicit list or status filter is selected.
  • The filter sheet must disclose that default exclusion and indicate when a selected list/status filter overrides it, because those filters intentionally reveal otherwise hidden list categories.
  • Moving a task between lists, including marking it done or closed, should refresh the affected source and destination lists instead of relying on a full board reload.

Task Board Realtime And Cache Notes

  • Successful task mutations must update visible TanStack Query caches in place before broadcasting. Patch all visible task shapes that can render the same card: ['tasks', boardId], every ['tasks-full', boardId, ...] query, ['task', taskId], matching ['workspaceTask', wsId, taskId], and personal task buckets when present.
  • Relation-only updates for labels, projects, and assignees should broadcast task:relations-changed and call the active board refresh with { invalidateTasks: false }. Do not invalidate ['tasks', boardId] or ['tasks-full', boardId] after successful relation-only changes; refetching those visible arrays causes card flicker while the optimistic cache already has the relation data.
  • Scalar task changes such as priority, dates, estimate, name, and list should patch visible task caches and broadcast task:upsert with the changed fields. Rollbacks should restore snapshots of the touched task ids instead of invalidating the whole board task cache.
  • Realtime receivers may reconcile task detail, history, counts, and personal task queries in the background. The board page should revalidate loaded progressive-list data without invalidating visible task arrays so shared boards stay current without remounting cards. During the Cloudflare migration, the server publishes the same authorized task events to both transports so supported older mobile and cached web clients retain updates; new clients use Cloudflare only. See the bounded compatibility bridge and removal criterion and its regression coverage.
  • Task edit dialogs should keep dropdowns and popovers controlled from a single active overlay id within each dialog section. Opening another menu must close the previous one, and outside-click/Escape close behavior should flow through the same onOpenChange path.

AI Journal Capture Notes

  • The quick-journal endpoint at /api/v1/workspaces/{wsId}/tasks/journal is a two-step flow: preview requests may invoke AI, but save requests that already include reviewed tasks plus a listId must persist directly.
  • Do not re-resolve the AI model, re-check credits, or regenerate tasks during that save step. A successful preview must be able to save even if model allocation changes afterward.
  • When the destination picker allows switching workspaces, clear the currently selected board/list immediately in the same UI event before loading the new workspace boards. Effect-only cleanup is too late for save flows and can send a stale listId that belongs to a different workspace.
  • If the final create request fails, reopen the destination-selection step with the reviewed tasks still in memory. Do not clear the journal draft optimistically before the insert succeeds, or users can lose the prompt they just refined.
  • Any workspace alias accepted by the board/list bootstrap endpoints, such as personal, must also be normalized by the journal save route before membership and list checks. Otherwise the picker can show valid destinations while the final save still rejects the same workspace context.
  • The journal save route should verify workspace membership first with the request-scoped client, then validate the chosen list against that normalized workspace. Admin-backed list checks are fine for consistency with selector data, but they must never bypass the caller’s workspace access gate.
  • Any workspace-scoped project validation in that same save route should follow the same pattern: verify membership with the request-scoped client first, then resolve valid task_projects IDs through the admin client scoped to the normalized workspace. Do not read protected workspace project tables directly from the caller-scoped client after access has already been established.
  • Any top-level labelIds or reviewed task labels[].id in that save route must be checked against workspace_task_labels.ws_id for the normalized workspace before task rows are inserted. Foreign workspace label IDs must fail the request instead of being carried into task_labels; labels created by name can still be upserted through the admin-backed workspace path after membership passes.
  • After membership passes, persist the journal-created tasks rows and their relation-table writes (task_labels, task_project_tasks, task_assignees, label upserts) through the same admin-backed workspace path, and stamp actor-owned columns such as creator_id explicitly. Do not mix admin-backed validation with caller-scoped writes or the save step can still fail with table-level permission errors.

Task Hierarchy

1. Workspaces

Container for all workspace resources.

2. Task Initiatives

Strategic initiatives grouping related projects.
Usage:

3. Task Projects

Cross-functional projects coordinating tasks across boards.
Usage:

4. Workspace Boards

Kanban-style task boards. Personal workspaces should always have a default active task board named Tasks. Web and mobile task-board listing APIs ensure that board exists before returning board data, so users should not see a prompt just to bootstrap their personal task workflow. Mobile opens that default board directly from both the Tasks and Boards surfaces unless the user disables default task-board navigation in mobile Settings. The web settings dialog exposes the same preference for the Boards page, keeping the board picker available for users who prefer the previous flow. The Tasks board header also exposes a global create action. It lists only workspaces where the caller has manage_projects, including workspaces that do not yet contain a board, and opens the newly created board immediately. In the Tasks settings dialog, each workspace can store a per-user TASK_DEFAULT_BOARD_ID. The Tasks root/proxy entrypoint validates that saved ID against active accessible boards before redirecting and falls back to the first active board when the preference is empty or stale.

Task list status semantics

Task list status is a workflow category, not the list display name. The review status sits between active and done: moving a task into a review list resolves the task for deadline/reminder filtering, but it must not set completed, completed_at, or closed_at. Review tasks should keep the same unchecked checkbox and non-completed visual treatment used by active tasks. Review lists are for walkthrough or acceptance queues. Task cards in review, done, or closed lists should not show due-date or overdue treatment because those tasks are already resolved for deadline purposes. Mobile list mode still shows review lists by default; only documents, done, and closed are hidden when no explicit list or status filter is selected.

Task description realtime

Task descriptions use a Yjs CRDT document stored in tasks.description_yjs_state. Once that state exists, treat it as the authoritative collaborative source. The JSON tasks.description field is the read/search/cache projection and must not overwrite a valid Yjs state just because it differs; board caches and recently opened dialogs can hold stale description JSON while another collaborator has newer Yjs edits. Initialization or repair writes that seed the local Yjs document from persisted state must use the provider sync origin so those changes are not broadcast as fresh user edits. Persistence should serialize the description projection from the Yjs update being saved, keeping description and description_yjs_state from diverging under multi-user edit churn. Focused and fullscreen task dialogs in the shared Tasks UI show a title, details and description skeleton while the task and durable description hydrate. The editor stays mounted once its final task binding exists; settling hides its contents without recreating the Yjs binding or discarding local edits. Short descriptions use a bounded minimum editing area rather than a viewport-sized blank region. Compact creation and readonly shared views retain their existing presentation. Tracked description recovery reconciles equivalent content through the active editor schema, including editor-filled defaults and JSON key ordering. It also recognizes a tracked version that matches both the saved current description and the initial hydrated editor baseline, or a successful save from the same dialog opening, so unsaved edits do not create a recovery prompt for that same version. This changes recovery metadata only: it never writes history into the editor or replaces a valid collaborative document. Genuine differences in text, formatting, media, mentions or unsupported attributes remain available for explicit comparison and restore. Pre-clear history remains recoverable. Regression coverage lives in description-reconciliation.test.ts and task-dialog-loading-shell.test.tsx. The ttr CLI uses the same description endpoint and TipTap/Yjs codec as the web editor. Prefer first-class description commands over raw task update payloads:
For local conversion or debugging, use the login-free codec commands:

Task templates

Task templates are reusable single-task starters and are intentionally separate from board templates. Store workspace templates in task_templates with creator ownership, private or workspace visibility, archived timestamps, default board/list ids, priority, dates, estimate, labels, assignees, projects, and optional TipTap/Yjs description state. Template API routes live under /api/v1/workspaces/{wsId}/task-templates. They must accept authenticated workspace members and tasks app-session JWTs from ttr, reject direct task-board guests, and keep private templates readable only by their creator. Workspace-visible template mutation should follow the task/project management permission used by task-board management flows; private template mutation stays scoped to the member owner. The web templates hub at /{locale}/{wsId}/tasks/templates should default to the Task Templates tab while preserving Board Templates as the secondary tab and keeping existing board-template detail links stable. The CLI supports both workspace-stored and local markdown templates:
Local files under .tuturuuu/task-templates/*.md use YAML frontmatter for template fields and the markdown body as the task description. Explicit ttr tasks create flags must override template defaults so users can reuse a starter without losing the current task context.

Mobile board caching

The mobile task-board flow restores workspace boards, board metadata, and per-list task pages from CacheStore before network revalidation. Board detail pages prefetch the first three lists on open and the focused Kanban list plus the next two lists on horizontal page changes, so column swipes should not wait for one on-demand request at a time. Cached task-page payloads must preserve the display metadata used by cards, including assignees, labels, projects, and relationship summaries. The mobile board List tab hides tasks in done and closed lists by default. Explicit status filters or list filters are treated as user intent and can show those terminal lists again. Mobile task details open description in a read-first fullscreen surface. Keep that surface borderless and height-bounded under the task title, with editing entered only from the edit FAB and save actions kept inside the editor chrome. Task create/update API routes silently drop assignee ids that no longer belong to the workspace. This lets stale task assignments be cleaned up during the next save instead of blocking unrelated edits such as description updates.

Timeline board view

The task board Timeline is task-first: TimelineBoard keeps its public props stable for board and project views, while the internal model renders one scheduled task per readable row grouped by task list. Task titles and source metadata belong in the sticky left column; timeline bars should communicate the date range only, so narrow zoom levels never hide task names behind clipped bar labels. Keep drag, resize, schedule-from-unscheduled, edit, delete, and context-menu actions routed through the same board mutation path used by the other views. The row model must preserve personal and external task metadata and keep unknown-list tasks grouped under the virtual unknown-list section instead of dropping them from the schedule.

Personal external tasks

Personal task boards can reference tasks from other workspaces without cloning or moving the source task. Assigned tasks from accessible source workspaces appear by default in a virtual External tasks lane on each personal board. Explicit personal-board placements can also reference tasks from another board inside the same personal workspace; those cards are external to the destination board even though the source workspace is personal. Native tasks from the same personal board must still move through the normal workspace task route instead of the personal-placement route. The tasks API may use the server-side admin client to hydrate candidate source tasks, but it must filter those rows through the current user’s source workspace_members rows with type = 'MEMBER' before returning personal default or placed external cards. A guest row in the source workspace is not enough to reveal task data through the personal board. The virtual lane is presentation-only: it has no task_lists row, no list actions, no create-task form, no list reordering, and can be collapsed per board when the user wants to focus on planned lists. Personal boards default the lane to collapsed when the board has no assigned external task references; once assigned external tasks exist, the lane opens by default unless the user has saved an explicit collapsed or expanded preference for that board. By default the external lane shows only open source tasks from normal task lists. Source tasks in documents, review, done, or closed lists are filtered out unless the lane-level compact filters explicitly include resolved tasks. Lane sorting is also local to the external lane, so changing it does not reorder the user’s real personal lists. External task cards should show the source workspace and the source board’s ticket identifier. Opening an external task must resolve the task by id through the current-user task route so the dialog receives the source workspace, source board, and source lists instead of the personal board context. Per-user placement lives on task_user_overrides with personal_board_id, personal_list_id, personal_sort_key, personal_added_at, and personal_placed_at. Placement writes must go through upsert_personal_task_placement. Drag clients should pass the previous and next visible task ids when available, and the RPC uses calculate_personal_task_placement_sort_key to compute a sort key against the combined personal list of native personal tasks and placed external task references. Per-list task loading for personal boards must include the personal boardId alongside listId; the API needs both values to load placed external references for real personal lists and to keep already-placed external tasks out of the virtual external lane. Optimistic personal-placement moves must keep a fresh _localMutationAt marker after the placement API response is merged. Loaded-list revalidation may still return a pre-move page briefly, and the marker prevents that stale response from evicting the placed external task until the server view catches up. Optimistic placement writes should upsert the task into both the progressive board task cache and any full-board cache that is already mounted, because stale list reloads can briefly remove the card before the destination list refetches. The card renderer should hide only the active pre-drop drag source; once a placement mutation is optimistic, the real destination card must stay visible even if the sortable layer still reports that task id as dragging. Native task reorders and external personal-placement moves should both stamp a fresh _localMutationAt marker on optimistic writes and on the server-confirmed cache merge. Progressive list loading treats that marker as authority for movement fields such as list_id, sort_key, personal_list_id, and personal_sort_key, which prevents stale list pages from flashing a card back to its previous position while the mutation response is still settling. Personal-board list badges must use exact includeCount responses, not a page-size estimate from progressive loading. External reference counts should be resolved through get_personal_task_board_external_counts so placed external tasks, default external-lane tasks, source-workspace access, and external-lane filters stay aligned with the records the board can actually show. Board source filters use TaskFilters.sourceScope to decide which task source set is visible. all_visible preserves the existing board behavior, current_board returns only tasks from the active board, and the two external scopes return assigned-to-me tasks whose source board is not the active board. external_current_workspace stays inside the current workspace, while external_specific requires selected source workspace ids or source board ids; when nothing is selected, the client should show an empty prompt instead of running an unbounded external query. Source-filtered list and timeline reads must go through the tasks API route, which normalizes the route workspace, verifies request-scoped membership, then uses the server-side Supabase admin client to call the private.list_task_source_filter_ids RPC. The RPC rechecks target workspace membership and source workspace access before returning paginated task ids plus total_count for API-side hydration. Client UI must not call the private RPC directly or trust client-provided source ids as authorization.
  • personal_board_id points to the destination personal board after an external task is planned there.
  • personal_list_id points to the real personal list that owns the planning position.
  • Dragging from the virtual lane into a real personal list creates or updates the personal placement.
  • Dragging a placed external task back to the virtual lane removes the personal placement so the task returns to the default external view.
  • Personal-only label and project overlays live in task_user_override_labels and task_user_override_projects. Those rows reference labels/projects owned by the user’s personal workspace and are loaded only for that owner.
Source task fields, including tasks.list_id, task_labels, and task_project_tasks, must not be changed when a task appears in the external lane, is planned into a personal list, is moved within the personal board, or returns to the external lane unless the personal move changes the task’s workflow status. Routes that read personal board tasks should keep filtering inaccessible source tasks out instead of surfacing stale placements to users who no longer have source-workspace access. When a placed external task moves to a personal list with a different workflow status, the source task must also move to the first matching list on its original board. done and closed targets require the matching terminal source list. Non-terminal targets first try the same source status, then fall back to active and not_started lists so tasks moved out of source done or closed queues do not keep completed or closed card state.
Usage:

5. Task Lists

Columns within boards (e.g., “To Do”, “In Progress”, “Done”). Task boards may contain multiple closed lists when teams need distinct terminal buckets such as Abandoned, Not Planned, or Duplicate. Treat closed as a status group for sorting/reporting, not a singleton board column. Access rule: direct task-table access is server-owned; request-scoped clients have no task read or write grants. Authorized routes verify the actor, workspace membership or share before using the admin client. Task-list RLS requires a workspace_members row in the board’s workspace. Task RLS adds a defence-in-depth fence: list-bound tasks require list visibility, and list-less tasks require the actor to match creator_id. The calendar scheduling route POST /api/{wsId}/task/create creates list-less tasks. Board shares, task shares and public share links do not grant direct list or task access. Regression coverage: apps/database/supabase/tests/security-definer-function-grants.sql.
Usage:

6. Workspace Tasks

Individual work items. Task titles support up to 1024 characters. Keep task-only validation on the dedicated task-title limit rather than the generic name limit so task routes, drafts, and database checks stay aligned.

Bucket Dump Feature

Rapidly capture thoughts as notes, then convert to tasks or projects.

Notes Table

Convert Note to Task

Convert Note to Project

Task Cycles (Sprints)

Cycles are time-boxed sets of tasks in one workspace. The Tasks app sidebar lists Cycles (/{wsId}/cycles) and Cycle planning (/{wsId}/cycles/planning) under Tasks; both are enabled for members with manage_projects, the permission both pages enforce.

Cycle and backlog rules

Exceptions: rows that existed before the workspace guard are not rewritten. Audit them with apps/database/verification/task-cycle-cross-workspace-links.sql; cleanup needs release-authority approval. Tasks linked to several workspace cycles before one-cycle-per-task are not cleaned up either: reads use the current-cycle rule above, and the task’s next assign replaces all of its workspace-cycle links (board-sprint rows stay). Regression coverage: apps/database/supabase/tests/task-cycle-workspace-guard.sql, apps/database/supabase/tests/task-cycle-one-per-task.sql, and the route tests under apps/tasks/src/app/api/v1/workspaces/[wsId]/task-cycles/. API and client helpers (@tuturuuu/internal-api/task-cycles). Both reads return { tasks: TaskCyclePlanningTask[] }, matching CyclePlanningTask in packages/tasks-ui/src/tu-do/cycles/planning/types.ts: id, name, display_number, ticket_prefix, priority, status (list status), board_id, cycle_id, assignees, projects and labels. Malformed wsId or cycleId values return 404.

Cycle planning view

/{wsId}/cycles/planning lists one scope at a time: Current cycle, Next, or Backlog. It needs manage_projects, like the Cycles page.
  • Scopes. Current is the active cycle (the latest started, if several), else a planned cycle whose dates include today. Next is the earliest planned cycle starting after today. Backlog is every open task with no cycle. The view opens on Current, or on Backlog when no cycle is scheduled. “Today” is the viewer’s local calendar day.
  • Grouping. Tasks are grouped by list status in the order In progress, In review, Not started, Done, Closed. Closed starts collapsed, empty groups are hidden, and tasks in documents lists are not shown.
  • Filters. Assignee (Me, Unassigned, people) comes first, then Project and search. Priority and labels are under More. Filter choices come from the loaded tasks.
  • Moving tasks. Use a row’s menu, or drag the row onto a scope. The move shows immediately and is undone, with an error toast, if the write fails. Done and Closed tasks cannot move to the backlog, which lists open tasks only.
  • Scope changes. Filter values with no option in the new scope are dropped. A scope at the 500-task cap shows a one-line note.
  • Empty states. Each is a single line with one action.
Code: packages/tasks-ui/src/tu-do/cycles/planning/. Regression coverage: __tests__/cycle-planning-model.test.ts and __tests__/cycle-planning-view.test.tsx.

Task Estimation

Estimation Types:
  • Fibonacci: 1, 2, 3, 5, 8, 13, 21
  • Exponential: 1, 2, 4, 8, 16, 32
  • Linear: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
  • T-Shirt: XS (1), S (2), M (3), L (5), XL (8), XXL (13)

Task Labels

Usage:

Custom Task Statuses

Usage:

Task Dependencies

Track task relationships and blockers.
Usage:

User Interface Components

Board View

Best Practices

✅ DO

  1. Use hierarchy appropriately
  2. Leverage bucket dump
  3. Set task estimates
  4. Use labels for categorization
  5. Track dependencies

❌ DON’T

  1. Don’t create deep hierarchies
  2. Don’t skip workspace isolation

Returning to a Tasks tab

Board views in Tasks and the platform keep cached cards visible while refreshing the loaded pages and board/list metadata on browser focus, visibility restoration, back/forward restoration, or reconnect. Paired wake events are coalesced for one second; a later tab visit refreshes again instead of waiting five minutes. This is scoped to the mounted board and preserves accumulated pagination and local edits. My Tasks uses its separate 30-second TanStack Query freshness policy; unrelated satellite queries retain their defaults. Regression coverage lives in packages/tasks-ui/src/tu-do/shared/__tests__/board-client.test.tsx and use-progressive-board-loader-resume.test.tsx.

Durable description hydration and realtime availability

All Tasks description editors load the authorized saved Yjs document independently of the peer channel connection. A realtime outage must not hide an otherwise readable saved description. The description stays read-only until both durable hydration and the channel connection succeed; the reconnecting indicator remains visible. Rejected durable reads do not count as an empty document and do not permit initialization writes. Initialization of missing legacy Yjs state remains connection-gated and ignores late results after the dialog or resource changes. Regression coverage: packages/ui/src/hooks/__tests__/supabase-provider.test.ts and packages/tasks-ui/src/tu-do/shared/task-edit-dialog/hooks/__tests__/use-task-yjs-sync.test.ts. When a production channel request returns an HTML 404, verify the deployed Web source SHA and first-class /api/v1/realtime/channels route before changing channel permissions. A green branch or a production sync receipt alone does not prove that the live API deployment contains that route.

Authorized board hydration

Tasks and the shared web board reuse the board metadata already authorized by the server for the current request. The client validates both board and workspace IDs before using that snapshot, so controls and list structure can appear without a second metadata round trip. The snapshot is fresh for 30 seconds; explicit refresh, realtime updates, and returning to a visible tab still revalidate it. Existing loaded cards remain visible while those requests finish. Cached data does not replace server membership or board-access checks. Regression evidence: board-client.test.tsx covers immediate authorized metadata, rejection of mismatched snapshots, and focus revalidation. Production timing must be measured after deployment; focused tests do not prove network performance.

Request-scoped workspace reads

Tasks and Calendar dashboard authorization use getRequestWorkspace to coalesce identical workspace reads within one server render. The key includes workspace, actor ID, actor email, and admin mode. This is React request memoization, not a persistent authorization cache: separate requests resolve workspace access again. Calendar’s layout and page share this reader; shared Meet context retains its existing memoization. The wrapper’s actor/mode isolation regression tests live in packages/utils/src/__tests__/request-workspace.test.ts. This reduces repeated server reads; it does not replace app build checks or production load measurements.

Create tasks with a private creative reference

/{wsId}/tasks/new provides an explicit composer with a task name and board/list selection. Opening this route performs no task or board creation. It requires a Tasks app session and joined workspace; task creation retains the canonical API’s destination authorization. A permission failure preserves the entered form. Mutation retries are disabled so this workflow does not automatically repeat a creation request. A Lettin studio handoff includes only source IDs and remains in the same workspace. An unchecked source-reference control lets the creator deliberately attach a canonical private notebook link to the task description. The form states the board visibility and source access boundaries before submission. It never reads source content or changes source permissions/publication. Invalid source IDs return not found, and no caller-provided URL is persisted. Creators can also use the composer without any source reference. See Lettin for the product decision and regression evidence. Focused composer validation can run without workspace builds:
The Tasks Vitest configuration lowers JSX with the automatic runtime and resolves its root internal API import to source. A preserved JSX transform otherwise fails Vite import analysis before mounted tests run. Other local suites may still need source aliases for unbuilt dependency subpaths; record those failures separately and use exact-head CI for application builds. Task planning keeps each creation form single-use after submission. If the response cannot confirm creation, the form stays preserved and locked, with a link to the selected board. Check that board before starting another task: the server may have inserted it even when the response failed. This guard does not provide server-side idempotency across new forms or page reloads.

Explicit task planning priority

The Tasks /{wsId}/tasks/new composer, including the Lettin handoff, offers an optional priority choice: critical, high, normal, or low. It starts with no priority and omits the API field until the creator chooses one. Clearing a choice omits it again. No notebook content or metadata determines priority. Failed creation preserves the choice in the locked single-use form; mutation retries stay disabled. Check the selected board before starting another form. Source-reference sharing remains unchecked and independently consented, and the destination API retains workspace and task authorization. Regression coverage in apps/tasks/src/components/task-plan-composer.test.tsx checks each API priority, clearing, preserved choices after failed creation, and unchanged source consent. The composer fixtures render choices from both English and Vietnamese message bundles. Exact-head Tasks build and repository CI remain separate delivery gates.

Explicit destination project association

Tasks planning, including the Lettin handoff, can associate the new task with one existing destination workspace project. This control starts unchecked. Project choices are loaded through the canonical authenticated internal API only after opt-in, and the creator must select a currently available project. Opening the composer never creates a project, task, or source reference. No notebook content or metadata determines the project; source-link consent remains independent. Pending or failed project reads and a selection removed by refreshed choices block submission with association enabled. The creator can retry the read or uncheck association to continue without a project. Failed task creation preserves the project choice in the locked single-use form. Check the selected board before starting another form; neither read nor creation failures trigger automatic retries. The canonical task API revalidates workspace project ownership and destination task authorization at submission. Existing project and board access rules apply; notebook grants and publication never change. apps/tasks/src/components/task-plan-composer.test.tsx covers opt-in-only reads, required current selection, ID-only creation payloads, opt-out, unavailable and removed projects, denial preservation, and both language bundles. These fixtures double the internal API and translation hook; exact-head app builds and API CI remain separate delivery evidence.

Authored task planning notes

The Tasks /{wsId}/tasks/new composer, including the Lettin handoff, starts with empty optional planning notes. Creators may enter up to 2,000 characters for the chosen destination task. Notes are visible under the destination task’s existing permissions. The composer saves only this authored text as literal rich-document paragraphs; line breaks are retained and markup is treated as text. It does not read or prefill notebook text, artwork or profile data. Notebook-link sharing remains independently off by default, and project association requires its own explicit selection. An opted-in source link is appended as a separate paragraph; unchecking it removes only the link. Empty or whitespace-only notes add no description unless a source link is selected. Changing the destination board preserves the authored plan while requiring a new list choice. Creation still requires explicit submission and current destination permissions. An unconfirmed response retains the form and notes in the existing locked recovery state, preventing an automatic retry or duplicate create. Closing the composer discards these local notes; they are not a durable draft. Regression coverage: task-plan-description.test.ts exercises literal text, line breaks, independent link composition and serialized size bounds; task-plan-composer.test.tsx covers empty defaults, both shipped languages, optional notes without source/project sharing, source removal, oversized input rejection and uncertain creation recovery. Hosted task creation and the exact-head Tasks build remain separate delivery gates.

Provider meeting commitments in task previews

Task scheduling previews in Tasks, Calendar and Web preserve future Google and Microsoft calendar commitments even when the local event copy is unlocked. The rule recognizes current provider/source IDs and legacy Google event IDs. Native future task slots remain replaceable; locked, in-progress and retained habit slots remain blocked. Preview-only copies are excluded. Provider meetings must not become free time merely because they are not native locked tasks. The shared decision lives in calendar-preview-blocked.ts; its unit tests and unified-scheduler/provider-commitments.test.ts in all three apps cover the preview integration. This does not add Google Tasks or Microsoft To Do sync.

Offline fallback on the native Worker

The offline worker precaches the exact /offline.html (with offline.css and offline.js) and shows it when navigation fails without a network. The native Worker serves static assets with html_handling: 'none', so /offline.html answers 200 at that exact URL in one asset read. The default handling answers it with an internal redirect to /offline that only the bridge’s redirect-following hides. Tasks emits no other HTML and relies on no HTML auto-index or trailing-slash alias. The Next build is unchanged. Covered by apps/tasks/cloudflare/offline-fallback.runtime.mjs on the sealed native build, and by the artifact contract in scripts/ci/tasks-native-artifact.test.py, which rejects a missing or redirecting html_handling. The native Tasks app hides a resolved page as soon as the URL pathname differs from the pathname that loaded its model. It shows a loading surface until the matching model arrives, including board and workspace switches and back/forward navigation. The URL/model boundary compares pathnames only; query-only changes do not invalidate the board identity. Board state remounts for each actor, workspace and board; workspace providers remount for each actor and workspace. A retained board snapshot cannot supply another board’s body. Regression coverage: apps/tasks/src/runtime/page-transition.test.tsx and page-transition-router.test.tsx cover retained models, late responses and workspace navigation through the native router.