Projects, clients & tasks
Tick's catalog is deliberately open-ended: a task may belong to a project, a project may belong to a client, and any of the three can stand alone. A time entry can point at any one of them — or at nothing. Three rules make that model predictable.
Rule 1 — store only the deepest reference
An entry stores exactly one reference: a client, a project, a task, or nothing. Project and client are never copied onto the entry — they are derived at read time by walking task → task.project → project.client.
Consequences you will notice:
- Attaching a task to the timer shows the whole chain (
Task · Project · Client) as one chip, but only the task id is saved. - Reassigning a task to another project re-files its entire history automatically — every entry pointing at that task now reports under the new project and client. That is intended.
- Deleting something an entry pointed at never destroys the time: the entry's reference is cleared instead (see Rule 3).
The database enforces this with a single ref_type / ref_id pair on time_entries (ref_type is one of client | project | task or null).
Rule 2 — rate inheritance
The billable rate for an entry is resolved server-side, first non-null wins:
entry.rate_override → task.rate → project.rate → client.rate → org_member.rate → user.default_rate
- A task's own rate applies whether it belongs to a project or stands alone — it's the rung right below an entry-level override, and above the project it lives in. Clearing it falls back to the project → client → member → user chain, same as clearing a project's or client's rate.
billabledefaults from the project's Billable by default flag when a project is resolvable, elsetrue.- Amount = hours × resolved rate, computed only when the entry is billable.
- Clients never compute inheritance — every API response carries the already-resolved rate (
resolvedRate) and, for projects and tasks, where it came from (rateSource).
You can see the resolution at work on the Projects page: each project card's subline states its effective rate and its origin — $95/h (own rate), $110/h from client, or $85/h default (the user's default). A task with its own rate shows it next to the task's name; the timer bar and manual-entry dialog show the resolved rate on their billable toggles, and a per-member rate override slots into the chain for teams.
The Projects page
The header counts projects and open/done tasks, with New task and New project buttons. Each project is a collapsible card showing:
- Client color dot, name, and a
billable/internaltag. - Subline: client,
x/y tasks open, and the rate with its source (Rule 2). - A progress bar of tracked vs estimated time ("6h 30m tracked / 40h estimated") — it switches to a brighter accent when over the estimate — plus the billable amount.
Expanding a card lists its tasks: a done toggle (completed tasks render struck-through and are hidden from the picker), entry count, tracked time, and a ▶ start button that starts the timer on that task. A task with its own rate shows $X/h right after its name. An + Add task link creates one inline. Standalone tasks (no project) get their own card below with the same row layout.
Project form: name, client picker, billable-by-default toggle, rate, estimate (free text — 40h, 2h 30m, same grammar as durations), visibility (private/public), archived. Task form: name, project picker, rate, estimate, completed — the rate field's placeholder shows what an empty field would inherit (the selected project's resolved rate and its source, or your default rate for a standalone task).
The Clients page
A table of clients: a round avatar with initials in the client's color (assigned deterministically from a short palette and reused for dots everywhere), the rate ($110/h, or the muted $85/h (default) when inheriting), project and task counts, tracked time with its amount, and edit / delete actions. The client form is just a name and an optional rate.
Rule 3 — cascade delete
Deleting a client (or project) opens a cascade dialog instead of a confirm button. It shows three checkboxes that cascade downward — Projects ⇒ Tasks ⇒ Time entries — each with a live count:
- Checking Projects auto-checks Tasks and Entries; unchecking Entries unchecks the two above (you can't delete a container while keeping its contents checked-for-deletion).
- An outcome panel spells out what happens to everything unchecked:
- Projects kept → their
clientlink is cleared and their rate falls back per Rule 2; the panel states the resulting $/h. - Tasks kept → if their project was deleted they become standalone, falling back to the project chain's rate unless the task has its own.
- Entries kept → entries pointing directly at the deleted object have their reference cleared (
ref_type = null). Time is never lost silently. Entries pointing at a surviving task or project are untouched.
- Projects kept → their
Everything that was checked soft-deletes to the trash, and an undo toast restores the full snapshot in one click.
Rule 4 — deleting entries and tags
- A single entry deletes immediately with an undo toast (configurable countdown, 8s by default).
- Bulk delete confirms the count first, then trashes with undo.
- Deleting a tag strips the label from entries and never touches time. Tag deletion is the one hard-ish case: it removes the tag but leaves every entry's duration and amount alone.
The Tags page
Tags are created inline in the page header (Enter works; names are lowercased and a leading # is stripped). Creating a name that already exists is a no-op — you get a "already exists" toast instead of a duplicate, and a name sitting in the trash is revived rather than recreated. The table shows each #tag chip, how many distinct projects/clients it is used on, entry count, hours, and last-used date. The filter action jumps to the Time page with #tag pre-filled; delete strips the label with an undo toast.
#tag, fix the tags field on each entry with ✎ Edit, then delete the tag you no longer want.Trash
Everything deleted anywhere in Tick — clients, projects, tasks, tags, entries — is a soft delete (deleted_at timestamp) and lands in Settings → Trash, grouped by entity with a per-row "days left" countdown.
- Items are restorable for 30 days; anything older is purged automatically the next time the trash is loaded.
- Delete forever hard-deletes selected items (or the whole trash) immediately.
- Undo toasts throughout the app restore exactly the rows a delete removed — including full cascade snapshots (the references a cascade cleared are put back too).