Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Aking16/timify/llms.txt

Use this file to discover all available pages before exploring further.

Tags are reusable, colored labels you can attach to any time entry to add a layer of categorization beyond projects. You might use them to separate client-facing work from internal tasks, mark entries by technology, or flag items for review. Tags are displayed as small chips or badges directly on the entry card, so you can scan them at a glance without opening an entry.

Tags data shape

Each row in the tags table holds the following fields.
id
string
required
UUID primary key, auto-generated with crypto.randomUUID() on insert.
userId
string
required
Foreign key referencing the user table. Deleting a user cascades and removes all their tags.
name
string
required
The display name of the tag. Must be non-empty and at most 16 characters long.
color
string
Hex color code used to render the tag chip. Defaults to #9ca3af (Tailwind gray-400) when not supplied.
createdAt
timestamp
Creation timestamp, set automatically by SQLite via unixepoch().
updatedAt
timestamp
Last-updated timestamp, refreshed on every write via Drizzle’s $onUpdateFn.

Many-to-many relationship: time_entry_tags

Tags and time entries share a many-to-many relationship managed through the time_entry_tags junction table. A unique index on (timeEntryId, tagId) prevents duplicate associations.
id
string
required
UUID primary key, auto-generated on insert.
timeEntryId
string
required
Foreign key referencing time_entries.id. Cascades on delete — removing an entry removes all its tag associations.
tagId
string
required
Foreign key referencing tags.id. Cascades on delete — removing a tag removes all its entry associations.

Creating a tag

Navigate to /app/tags and click the New Tag button to open the create dialog.
1

Enter a tag name

Type a short, descriptive name. The createTag action validates that the name is non-empty and no longer than 16 characters.
2

Pick a color (optional)

Choose a hex color for the tag chip. If you leave this blank, Timify uses the default color #9ca3af.
3

Save the tag

createTag inserts the row into tags under your userId, revalidates the get-tags cache tag, and returns the newly created tag object.

Validation rules for createTag

FieldRule
nameRequired · non-empty · maximum 16 characters
colorOptional · hex color string · null accepted

Editing a tag

Open the menu on a tag card on the /app/tags page and select Edit. The editTag server action updates name and color for the given id.

Validation rules for editTag

FieldRule
idRequired · existing tag UUID
nameRequired · non-empty · maximum 16 characters
colorOptional · hex color string
After a successful update, editTag calls revalidateTag("get-tags", "max") so the tag list and all entry chips refresh immediately.

Deleting a tag

Click Delete in the tag card’s action menu. deleteTag(id) verifies your session, runs db.delete(tags).where(eq(tags.id, id)), and revalidates the cache.
Deleting a tag removes it from all time entries it was attached to. Because time_entry_tags.tagId has onDelete: "cascade", every row in the junction table for that tag is automatically deleted. This action cannot be undone.

Attaching a tag to a time entry

From any entry card, click the tag icon or open the entry’s detail panel and select a tag from the dropdown. This calls addTagToTimeEntry(timeEntryId, tagId).
// src/actions/time-entry-tags/add-tag-to-time-entry.ts
await db.insert(timeEntryTags).values({
  timeEntryId,
  tagId,
});
The unique index unique_time_entry_tag on (timeEntryId, tagId) means adding the same tag to the same entry twice will throw a database constraint error. The UI prevents duplicates by hiding already-attached tags from the selection list.

Removing a tag from a time entry

Click the × on a tag chip in the entry card or deselect it in the tag picker. This calls removeTagFromTimeEntry(timeEntryId, tagId).
// src/actions/time-entry-tags/remove-tag-from-time-entry.ts
await db
  .delete(timeEntryTags)
  .where(
    and(
      eq(timeEntryTags.tagId, tagId),
      eq(timeEntryTags.timeEntryId, timeEntryId)
    )
  );
Both addTagToTimeEntry and removeTagFromTimeEntry call revalidatePath("/project/") so the entry list re-renders with the updated tag chips.

Fetching all tags

The getTags() server action retrieves all tags from the tags table. It uses Next.js "use cache" with the get-tags cache tag so results are served from memory on repeat requests.
// src/actions/tags/get-tags.ts
export async function getTags(): Promise<InferSelectModel<typeof tags>[]> {
  "use cache";
  cacheTag("get-tags");
  return db.select().from(tags);
}
The getTimeEntries action performs a LEFT JOIN across time_entry_tags and tags to return each entry with its full tag list pre-populated, so the UI never needs a second round-trip just to render tag chips.

Build docs developers (and LLMs) love