WorkestraDocs
PlatformTime Tracking

Timer

The composer-first timer page, three views over the same week, the global header pill, idle detection, Pomodoro mode, and browser tab title — Workestra's Toggl-grade time-entry surface.

The timer surface has three layers. The header pill lives in the top bar of every page once a timer is running. The composer page at /time/timer is where you start, edit, and review entries in three views (Calendar / List / Timesheet). And embedded <TimeOnRecord> widgets put a Start-timer button on every CRM contact, deal, support ticket, project task, and recruiting candidate.

Composer + three views

Screenshot needed — /time/timer with the composer bar, the segmented control (Calendar / List / Timesheet), and the calendar view filled with blocks.

The composer

The composer bar pinned to the top of /time/timer is the single most important element on the page. Designed to feel like Toggl's "What are you working on?" prompt:

ElementWhat it does
Description inputBorderless, large. Type what you're working on. Inline keyboard tokens: @deal:Acme, #bug, /project:Q1 resolve via the entity registry on blur.
Entity chipClick to open a popover-tabs picker: Task / Ticket / Deal / Candidate / Internal. The chip shows the entity icon + truncated title once set.
Project chipAuto-derived from the entity (a task's project, a deal's primary deal, etc.). Color dot + name.
Tag chipsInline tag editor — type to add, Enter / comma to commit, Backspace to remove the last.
Billable toggleA single emerald $ icon button. Filled when on.
Goal ringWhen an active goal applies to the current entity/project, a small progress ring renders next to the clock (Pro: see Goals).
Pomodoro toggleSmall Timer icon button — orange-accented when on. Toggles Pomodoro mode (see below).
Elapsed clockHH:MM:SS, tabular-nums, text-4xl on desktop. Color shifts: muted when idle, primary when running.
Play / StopBig circular button. Stop shows a subtle pulsing ring as a "this is live" signal.

Inline keyboard tokens

While typing the description, you can reference entities inline:

working on @deal:Acme renewal #bug /project:Q1

On blur, @deal:Acme → resolves to the matching CRM deal and pre-fills the entity chip. #bug becomes a tag. /project:Q1 sets the project chip. The text in the description is left clean (tokens stripped) so the entry reads naturally.

The three views

A segmented control between the composer and the content switches views. View + week are mirrored to the URL (?view=calendar|list|timesheet&week=YYYY-MM-DD) so refreshes and shared links round-trip cleanly.

Calendar view

Week grid, hour rows running 00:00–23:00, day columns. Time entries with started_at / ended_at render as colored blocks anchored at the right time. Drag an empty area to create a new entry. Click a block to open a side drawer for edit / duplicate / delete.

Calendar events from your connected Google / M365 calendars (see Calendar Import) appear as ghost blocks with a + convert CTA on hover.

List view

Day-grouped chronological list. Identical entries (same description + entity + billable + project) get grouped under a count badge with aggregate hours; clicking expands. Multi-select rows with checkboxes for bulk-edit (project, billable, tags) or bulk-delete.

Timesheet view

Pivot grid — rows are projects/entities, columns are the 7 days of the week, cells are decimal hours. Today's column gets a faint primary tint. Each cell with hours shows a small green dot if any underlying entry is billable. Click a cell to see / edit the entries it represents.

Sticky Daily total row at the bottom + Total column on the right. Grand total in the bottom-right corner.

Favorites — pinned quick-start entries

The right rail of /time/timer (visible at xl: breakpoint and up) is a Favorites panel — up to 10 pinned project + description + tag combinations you start with one click.

ElementWhat
Favorite rowDescription + project chip + billable indicator. Click to start a timer with all those fields pre-filled.
Keyboard shortcutsPress 19 or 0 while the timer page is focused to start the corresponding favorite.
Add favoriteInline form at the top of the panel. Save the current composer state as a favorite, or compose a new one from scratch.
Hover deleteA small × button appears on hover. Confirmation dialog before deletion.

Favorites are per-user and private to your workspace. The panel is hidden on mobile / narrow viewports — the keyboard shortcuts only fire when the panel is mounted.

Favorites panel

Screenshot needed — right rail of /time/timer showing 5 favorites with their numeric shortcuts (1-5), the inline 'Add favorite' form at the top.

Splitting an entry

Realised mid-day that what you logged as a single 4-hour Acme block was actually 2.5 h of design work + 1.5 h on a different ticket? Split it.

  1. Hover any entry row in List view.
  2. Click the Split icon (between Edit and Delete).
  3. The dialog pre-fills at the midpoint. Drag the slider, type a number of hours, or pick a time. The two preview rows show what each side becomes.
  4. Confirm — the original entry is replaced by two new entries, each with the same description / project / tags / billable. You can re-edit each independently.

The split runs as a server action with a compensating rollback: if creating the second entry fails after the first, the first is rolled back atomically. Locked entries (date-locked or approval-locked) cannot be split.

Hover any entry → a link icon appears. Click it → the URL https://your-workspace/time/timer?desc=…&project=…&tags=…&billable=1 lands on your clipboard. Paste it anywhere — Slack, an issue tracker, a meeting note. Whoever opens it lands on the timer page with the composer pre-filled and ready to .

Useful for shared work patterns: "here's the link for our weekly standup time entry, just click and start."

Logging time for someone else

Admins, owners, and managers can log time on behalf of another workspace member. Two surfaces:

  • Reports → Detailed tab → "Add entry for…" button (visible only when the caller's role grants it). Opens a quick form with a member picker.
  • Server Action / API: pass for_user_id on the createTimeEntry payload. The action verifies the caller's role server-side via the service-role client (RLS can't bypass the check) and writes the entry with the target user as the owner.

Every on-behalf-of write records on_behalf_of: <target_user> in the audit log so the trail stays transparent.

The same pattern extends to reassigning an existing entry: updateTimeEntry({ reassign_to_user_id }) moves the entry's owner. Only admins / owners / managers; audit log captures reassigned_to.

Required-fields gate

If your workspace admin sets required_fields (any combination of description, project, tags) under Settings → General, the timer + manual-entry forms enforce them server-side. Trying to save an entry missing a required field returns a validation error with the missing field name.

The gate is on the create + update server actions, so it applies regardless of which surface you used (timer, manual entry, calendar import convert, API).

Header pill — the always-visible running indicator

Once any timer starts, a compact pill appears in the top bar of every page:

  • Pulsing primary dot · entity icon · description (truncated) · live HH:MM:SS clock · circular destructive Stop button.
  • Click the pill body → navigates to /time/timer.
  • Click Stop → stops the timer in place, no navigation.

Hidden when no timer is running. Fully replaces the older floating bottom-right widget for the global case (the floating widget still appears in inline embeds on detail pages because those don't have the global header).

Header pill running

Screenshot needed — close-up of the top bar with the pulsing primary dot pill: '[icon] Working on Acme renewal · 0:18:42 · Stop'

Browser tab title

When a timer runs, the browser tab title becomes:

0:18:42 · Working on Acme renewal

Ticks once per second. Restored to the page's normal title when the timer stops. Lets you spot a forgotten timer from any tab.

Pomodoro mode

Click the Timer icon in the composer to toggle Pomodoro:

  • Focus → Short break → Focus → Short break → Focus → Short break → Focus → Long break — every 4th focus gets a long break.
  • Default durations: 25 / 5 / 15 minutes (configurable in Settings).
  • The composer's elapsed clock is replaced by the Pomodoro phase + countdown ("Focus 18:42").
  • On phase complete, plays a soft tone and (with permission) sends a browser notification.
  • Inline pause and skip buttons appear next to the countdown.
  • Independent of the time-tracking timer — you can use Pomodoro without any timer running.

Idle detection

Whenever a timer is running, the app watches for activity (mouse, keyboard, focus). If you've been idle longer than time_tracking_settings.idle_minutes (default 10), a dialog opens:

You've been away — no activity for 12:34. What would you like to do with that time?

  • Keep idle time — dismiss, the running entry continues
  • Discard idle time — stop the timer immediately so the recorded hours don't span the idle stretch
  • Split into two entries — stop the running entry at the moment idle started, save the active stretch, and start a fresh timer for whatever you're doing now. The idle window is excluded from both entries.

The threshold is configurable per workspace.

Cmd+K — "Log time…"

Press Cmd+K (or Ctrl+K on Windows/Linux) anywhere in the app. Search "Log time" or scroll to the command. Opens the time-entry quick-add sheet, pre-seeded with the focused entity (e.g. on a task page, the task is already selected).

The conversational AI also supports it: "log 45 min on Acme renewal yesterday afternoon" → AI parses → confirmation card → confirm → entry created.

Switching entities mid-day

You can only have one timer running at a time per user. This is enforced at the database level (a partial unique index), so even if you start one on your desktop and another on your phone, the older one stops automatically.

To switch:

  1. Open the composer page (/time/timer).
  2. Stop the current timer, change description / entity, hit Start again. Or
  3. Click the running pill in the header → routes to the composer → change context → stop & start.

What gets saved on stop

A row is created in the time_entries table with:

  • started_at and ended_at — for the audit / "actual session window"
  • hours — computed from start/end with rounding applied
  • entity_type + entity_id — what you were working on
  • project_id — denormalized from the entity (auto for tasks → project)
  • hourly_rate, cost_rate, currency — snapshotted from the rate resolver at write-time
  • billable — whether this is billable work
  • source'timer' (vs 'manual', 'imported', 'api')
  • status'draft' until you submit your timesheet

Auto-stop for runaway timers (Pro)

If you forget to stop a timer overnight, the runaway-timer cron catches it. Default threshold: 12 hours (runaway_timer_hours in settings). Two behaviors:

  • Notify only (free): you get an in-app notification that a timer has been running long. The timer keeps going.
  • Auto-stop (Pro — runaway_timer_hours_auto_stop = true): the cron stops the timer at the threshold and tags the entry for review.

Auto-stopped entries are saved as drafts marked auto_stopped. Always review them before submitting your timesheet — the recorded hours probably overstate actual work.


Next steps

  • Calendar Import — turn meetings into time entries with one click
  • Goals — set hour targets, see progress in the composer
  • Weekly Timesheet — review and submit a week's worth of entries
  • Settings — week start, rounding, idle minutes, Pomodoro durations