Vibecode Timeular
track this build4 phases, 12 steps, beginner friendly0%The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Timeular, track time with projects and reminders using software-only controls. The hard boundary is physical tracker option, mobile apps, calendar integration, team features, and sync, plus ubiquitous capture, coaching, and team workflow.
You are building a lean indie version of Timeular. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Timeular · indie build A manual time tracker built on the observation that manual tracking fails for exactly two reasons: switching takes too long and people forget to start. So the entire build is about making the switch instant and making the gaps recoverable. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Switching | A global hotkey to a switcher that closes on selection | the cost of switching is what determines whether tracking happens at all | | Gaps | Untracked time detected and offered for filling | forgetting is normal and unrecoverable gaps make the data useless | | Runtime | TypeScript with a local application and SQLite | a personal time log does not need a service | | Reminders | Idle and long-running detection, gentle rather than nagging | a nagging tracker gets closed | | Export | Complete, with rounding applied at export only | the stored entry keeps the actual duration | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A global hotkey and idle detection mechanism** · free - Why: Both are platform-specific, and the idle detection in particular differs in what it can see and what permission it needs. - Get it: Whatever your desktop framework provides, checking the permission requirement for idle time before designing onboarding. - Verify: A hotkey fires from another application and idle seconds can be read - [ ] **Two weeks of your own real tracking** · free, and it takes two weeks - Why: Switch friction, gap frequency and reminder tolerance are all behavioural, and two weeks is roughly how long it takes for a tracker to be abandoned if it is annoying. - Get it: Track your real work, including the days you forget entirely, since those produce the gaps the build exists to handle. - Verify: Two weeks exist including at least three days with substantial gaps - [ ] **Your working hours and rounding rule** · free - Why: Gaps are detected against working hours and exports are rounded by an agreed rule, and both are enforced in code rather than being preferences. - Get it: Write down your working hours per day and the rounding rule you have agreed with whoever receives the exports. - Verify: Both are written down and configured before the first export ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Automatic activity detection from the screen. - Team tracking and manager visibility. - Invoicing and payment collection. - Hardware device integration. - physical tracker option, mobile apps, calendar integration, team features, and sync - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform If one of those is essential to you, that is the reason to keep paying for Timeular, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Timeular The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me deliberate time tracking where switching activity costs one keystroke to replace Timeular. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Switching: A global hotkey to a switcher that closes on selection · the cost of switching is what determines whether tracking happens at all - Gaps: Untracked time detected and offered for filling · forgetting is normal and unrecoverable gaps make the data useless - Runtime: TypeScript with a local application and SQLite · a personal time log does not need a service - Reminders: Idle and long-running detection, gentle rather than nagging · a nagging tracker gets closed - Export: Complete, with rounding applied at export only · the stored entry keeps the actual duration ### Data model (create this before Phase 1) - activities(id, name, colour, project_id, hotkey, archived_at) - entries(id, activity_id, started_at, ended_at, note, source) · source is switched, filled or edited - gaps(id, from_at, to_at, resolved_by_entry_id, dismissed_at) · untracked time, surfaced rather than ignored - idle_events(at, duration_seconds, resolution) · resolution is discarded, assigned or kept - projects(id, name, client, billable, rate_minor, rate_effective_from) - exports(id, period, rounding_rule, generated_at, path) - An entry records whether it was created by switching, by filling a gap or by editing, because data entered three days later is not the same evidence as data entered at the time. ### Phase 1 · Switching in one keystroke Build: A global hotkey opening a switcher, with per-activity hotkeys for the common ones. The switcher closes on selection with no confirmation. Measure the time from keystroke to switched and treat it as the primary requirement. Trap: A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. Done when: Switching activity is measured at under a second from keystroke including the first switch after a cold start. No confirmation or required field blocks a switch. Direct hotkeys exist for frequent activities. A note can be added to a running entry without interrupting tracking. Do not build yet: No gaps, reminders or reporting. ### Phase 2 · The time you forgot to track Build: Detect untracked periods during working hours and surface them as gaps. Offer to fill a gap from adjacent context. Let a gap be dismissed as genuinely not work, and keep the dismissal. Trap: Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. Done when: Untracked periods during working hours are surfaced as gaps. Filling a gap takes one action and records the entry as filled rather than switched. A dismissed gap stays dismissed. The day's total distinguishes tracked, filled and dismissed time. Do not build yet: No reminders or reporting yet. ### Phase 3 · Reminders that do not become noise Build: Detect idle time and ask what to do with it on return. Detect an entry running far longer than that activity usually does. Limit how often reminders can appear, and stop for the day after repeated dismissal. Trap: An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. Done when: Idle time is detected and offered for assignment or discarding on return. An entry running far beyond its activity's usual duration prompts once, not repeatedly. Reminders stop for the day after repeated dismissal. The idle threshold is configurable and its default is derived from observed patterns rather than assumed. Do not build yet: No projects, rates or export yet. ### Phase 4 · Projects, rates and what changed when Build: Projects with billable rates carrying effective dates. Compute value from the rate in force when the work happened, not the current one. Handle a rate change mid-period. Trap: Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. Done when: Historic entries are valued at the rate in force at the time, verified by changing a rate and re-running a past report. A rate change mid-period splits correctly at the boundary. Reports state which rates were applied. Non-billable activities are excluded from value while remaining in time totals. Do not build yet: No export or rounding yet. ### Phase 5 · Export, rounding and the honest total Build: Apply rounding and minimum increments at export only, showing actual and rounded together. Export complete data including entry sources. Never modify a stored entry by rounding. Trap: Rounding the stored entry destroys the actual figure, so a client asking what the real time was cannot be answered, and repeated rounding on edits compounds the error. Round at export, show both, and keep the entry exact. Done when: Rounding applies at export with actual and rounded shown together. Stored entries are unchanged by exporting twice, verified by comparison. Export includes entry sources so filled time is distinguishable from switched. A period with a rate change exports with both rates stated. Do not build yet: Nothing after this. ### Out of scope (and why) - Automatic activity detection from what is on screen, which is a different tool with a much larger privacy cost. - Team tracking and manager visibility, which changes what the tool is for and who it serves. - Invoicing and payment collection, which is an adjacent product with its own requirements. - Hardware device integration, which is the original product's distinguishing feature and is not reproducible in software. ### README must contain - That the switch time is measured and treated as the primary requirement, because switching cost decides whether tracking happens - That gaps are surfaced and fillable rather than silently lost - That entries record whether they were switched, filled or edited, since those are different evidence - That rates carry effective dates and historic work is valued at the rate of its time - That rounding happens at export and the stored entry keeps the actual duration ===== AGENTS.md ===== # Agent instructions · Timeular indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A global hotkey to a switcher that closes on selection, Untracked time detected and offered for filling, TypeScript with a local application and SQLite, Idle and long-running detection, gentle rather than nagging, Complete, with rounding applied at export only. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. - Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. - An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. - Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. ===== BUILD_PLAN.md ===== # Build plan · Timeular A manual time tracker built on the observation that manual tracking fails for exactly two reasons: switching takes too long and people forget to start. So the entire build is about making the switch instant and making the gaps recoverable. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Switching in one keystroke Changing what you are tracking takes under a second. ### Steps 1. Pre-warm the switcher The first switch of the day is the one that decides the habit. Files: `src/switcher.ts` 2. Never require a field to switch Notes are added to the entry afterwards. Files: `src/entries.ts` 3. Measure and log the switch time A number, not an impression. It is the primary requirement. Files: `src/metrics.ts` ### Done when - [ ] Switching is under a second including cold start - [ ] No confirmation or required field blocks it - [ ] Frequent activities have direct hotkeys - [ ] Notes can be added to a running entry ### Watch out - A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. ## Phase 2 · The time you forgot to track Gaps are visible and fillable rather than silently lost. ### Steps 1. Detect gaps against working hours Not against the clock. Evenings are not missing data. Files: `src/gaps.ts` 2. Make filling one action A slow fill flow is the same problem as a slow switch. Files: `src/gaps.ts` 3. Record entry source Filled three days later is weaker evidence than switched at the time. Files: `src/entries.ts` ### Done when - [ ] Gaps during working hours are surfaced - [ ] Filling a gap is one action - [ ] Dismissals persist - [ ] Totals distinguish tracked, filled and dismissed ### Watch out - Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. ## Phase 3 · Reminders that do not become noise You are nudged when it helps and left alone when it does not. ### Steps 1. Ask about idle time on return Not during. Interrupting a break is the wrong moment. Files: `src/idle.ts` 2. Prompt once on a long-running entry Once. A timer left running overnight is the case being caught. Files: `src/reminders.ts` 3. Back off after dismissals Several dismissals is an answer for the day. Files: `src/reminders.ts` ### Done when - [ ] Idle time is offered on return - [ ] A long-running entry prompts once - [ ] Reminders stop after repeated dismissal - [ ] The idle threshold has a defensible default ### Watch out - An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. ## Phase 4 · Projects, rates and what changed when Billable time is computed from the rate that applied at the time. ### Steps 1. Store rates with effective dates Applying the current rate backwards reprices invoiced work. Files: `src/rates.ts`, `src/rates.test.ts` 2. Split correctly at a rate change Mid-period changes are the case that gets it wrong. Files: `src/value.ts` 3. State applied rates on reports So a disagreement is about the rate rather than the arithmetic. Files: `src/report.ts` ### Done when - [ ] Historic entries use the rate of their time - [ ] A mid-period rate change splits correctly - [ ] Reports state the rates applied - [ ] Non-billable time counts in totals but not value ### Watch out - Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. ## Not in this build - Automatic activity detection from the screen. - Team tracking and manager visibility. - Invoicing and payment collection. - Hardware device integration. ## After v1, if you want it - Estimates against actuals per project, which is where tracking starts improving planning - A weekly review of gaps and fills, since a rising fill rate means the switching is too slow - Calendar events offered as gap fills, which covers most meetings automatically ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Your working time record. DB_PATH=~/.local/share/timetrack/log.db # Required. The global hotkey. Choose one nothing else claims. SWITCH_HOTKEY=Ctrl+Shift+T # Required. The threshold that fails the build. Switch cost decides whether tracking happens. MAX_SWITCH_MS=800 # Required. When idle time is offered for assignment. Too short and the tracker gets closed. IDLE_THRESHOLD_SECONDS=300 # Required. Gaps are detected against these. Evenings are not missing data. WORKING_HOURS=09:00-18:00 # Required. Applied at export only. The stored entry keeps the actual duration. ROUNDING_RULE=none
You are building a lean indie version of Timeular. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Timeular · indie build A manual time tracker built on the observation that manual tracking fails for exactly two reasons: switching takes too long and people forget to start. So the entire build is about making the switch instant and making the gaps recoverable. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Switching | A global hotkey to a switcher that closes on selection | the cost of switching is what determines whether tracking happens at all | | Gaps | Untracked time detected and offered for filling | forgetting is normal and unrecoverable gaps make the data useless | | Runtime | TypeScript with a local application and SQLite | a personal time log does not need a service | | Reminders | Idle and long-running detection, gentle rather than nagging | a nagging tracker gets closed | | Export | Complete, with rounding applied at export only | the stored entry keeps the actual duration | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A global hotkey and idle detection mechanism** · free - Why: Both are platform-specific, and the idle detection in particular differs in what it can see and what permission it needs. - Get it: Whatever your desktop framework provides, checking the permission requirement for idle time before designing onboarding. - Verify: A hotkey fires from another application and idle seconds can be read - [ ] **Two weeks of your own real tracking** · free, and it takes two weeks - Why: Switch friction, gap frequency and reminder tolerance are all behavioural, and two weeks is roughly how long it takes for a tracker to be abandoned if it is annoying. - Get it: Track your real work, including the days you forget entirely, since those produce the gaps the build exists to handle. - Verify: Two weeks exist including at least three days with substantial gaps - [ ] **Your working hours and rounding rule** · free - Why: Gaps are detected against working hours and exports are rounded by an agreed rule, and both are enforced in code rather than being preferences. - Get it: Write down your working hours per day and the rounding rule you have agreed with whoever receives the exports. - Verify: Both are written down and configured before the first export ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Automatic activity detection from the screen. - Team tracking and manager visibility. - Invoicing and payment collection. - Hardware device integration. - physical tracker option, mobile apps, calendar integration, team features, and sync - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform If one of those is essential to you, that is the reason to keep paying for Timeular, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Timeular The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me deliberate time tracking where switching activity costs one keystroke to replace Timeular. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Switching: A global hotkey to a switcher that closes on selection · the cost of switching is what determines whether tracking happens at all - Gaps: Untracked time detected and offered for filling · forgetting is normal and unrecoverable gaps make the data useless - Runtime: TypeScript with a local application and SQLite · a personal time log does not need a service - Reminders: Idle and long-running detection, gentle rather than nagging · a nagging tracker gets closed - Export: Complete, with rounding applied at export only · the stored entry keeps the actual duration ### Data model (create this before Phase 1) - activities(id, name, colour, project_id, hotkey, archived_at) - entries(id, activity_id, started_at, ended_at, note, source) · source is switched, filled or edited - gaps(id, from_at, to_at, resolved_by_entry_id, dismissed_at) · untracked time, surfaced rather than ignored - idle_events(at, duration_seconds, resolution) · resolution is discarded, assigned or kept - projects(id, name, client, billable, rate_minor, rate_effective_from) - exports(id, period, rounding_rule, generated_at, path) - An entry records whether it was created by switching, by filling a gap or by editing, because data entered three days later is not the same evidence as data entered at the time. ### Phase 1 · Switching in one keystroke Build: A global hotkey opening a switcher, with per-activity hotkeys for the common ones. The switcher closes on selection with no confirmation. Measure the time from keystroke to switched and treat it as the primary requirement. Trap: A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. Done when: Switching activity is measured at under a second from keystroke including the first switch after a cold start. No confirmation or required field blocks a switch. Direct hotkeys exist for frequent activities. A note can be added to a running entry without interrupting tracking. Do not build yet: No gaps, reminders or reporting. ### Phase 2 · The time you forgot to track Build: Detect untracked periods during working hours and surface them as gaps. Offer to fill a gap from adjacent context. Let a gap be dismissed as genuinely not work, and keep the dismissal. Trap: Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. Done when: Untracked periods during working hours are surfaced as gaps. Filling a gap takes one action and records the entry as filled rather than switched. A dismissed gap stays dismissed. The day's total distinguishes tracked, filled and dismissed time. Do not build yet: No reminders or reporting yet. ### Phase 3 · Reminders that do not become noise Build: Detect idle time and ask what to do with it on return. Detect an entry running far longer than that activity usually does. Limit how often reminders can appear, and stop for the day after repeated dismissal. Trap: An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. Done when: Idle time is detected and offered for assignment or discarding on return. An entry running far beyond its activity's usual duration prompts once, not repeatedly. Reminders stop for the day after repeated dismissal. The idle threshold is configurable and its default is derived from observed patterns rather than assumed. Do not build yet: No projects, rates or export yet. ### Phase 4 · Projects, rates and what changed when Build: Projects with billable rates carrying effective dates. Compute value from the rate in force when the work happened, not the current one. Handle a rate change mid-period. Trap: Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. Done when: Historic entries are valued at the rate in force at the time, verified by changing a rate and re-running a past report. A rate change mid-period splits correctly at the boundary. Reports state which rates were applied. Non-billable activities are excluded from value while remaining in time totals. Do not build yet: No export or rounding yet. ### Phase 5 · Export, rounding and the honest total Build: Apply rounding and minimum increments at export only, showing actual and rounded together. Export complete data including entry sources. Never modify a stored entry by rounding. Trap: Rounding the stored entry destroys the actual figure, so a client asking what the real time was cannot be answered, and repeated rounding on edits compounds the error. Round at export, show both, and keep the entry exact. Done when: Rounding applies at export with actual and rounded shown together. Stored entries are unchanged by exporting twice, verified by comparison. Export includes entry sources so filled time is distinguishable from switched. A period with a rate change exports with both rates stated. Do not build yet: Nothing after this. ### Out of scope (and why) - Automatic activity detection from what is on screen, which is a different tool with a much larger privacy cost. - Team tracking and manager visibility, which changes what the tool is for and who it serves. - Invoicing and payment collection, which is an adjacent product with its own requirements. - Hardware device integration, which is the original product's distinguishing feature and is not reproducible in software. ### README must contain - That the switch time is measured and treated as the primary requirement, because switching cost decides whether tracking happens - That gaps are surfaced and fillable rather than silently lost - That entries record whether they were switched, filled or edited, since those are different evidence - That rates carry effective dates and historic work is valued at the rate of its time - That rounding happens at export and the stored entry keeps the actual duration ===== AGENTS.md ===== # Agent instructions · Timeular indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A global hotkey to a switcher that closes on selection, Untracked time detected and offered for filling, TypeScript with a local application and SQLite, Idle and long-running detection, gentle rather than nagging, Complete, with rounding applied at export only. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. - Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. - An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. - Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. ===== BUILD_PLAN.md ===== # Build plan · Timeular A manual time tracker built on the observation that manual tracking fails for exactly two reasons: switching takes too long and people forget to start. So the entire build is about making the switch instant and making the gaps recoverable. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Switching in one keystroke Changing what you are tracking takes under a second. ### Steps 1. Pre-warm the switcher The first switch of the day is the one that decides the habit. Files: `src/switcher.ts` 2. Never require a field to switch Notes are added to the entry afterwards. Files: `src/entries.ts` 3. Measure and log the switch time A number, not an impression. It is the primary requirement. Files: `src/metrics.ts` ### Done when - [ ] Switching is under a second including cold start - [ ] No confirmation or required field blocks it - [ ] Frequent activities have direct hotkeys - [ ] Notes can be added to a running entry ### Watch out - A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. ## Phase 2 · The time you forgot to track Gaps are visible and fillable rather than silently lost. ### Steps 1. Detect gaps against working hours Not against the clock. Evenings are not missing data. Files: `src/gaps.ts` 2. Make filling one action A slow fill flow is the same problem as a slow switch. Files: `src/gaps.ts` 3. Record entry source Filled three days later is weaker evidence than switched at the time. Files: `src/entries.ts` ### Done when - [ ] Gaps during working hours are surfaced - [ ] Filling a gap is one action - [ ] Dismissals persist - [ ] Totals distinguish tracked, filled and dismissed ### Watch out - Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. ## Phase 3 · Reminders that do not become noise You are nudged when it helps and left alone when it does not. ### Steps 1. Ask about idle time on return Not during. Interrupting a break is the wrong moment. Files: `src/idle.ts` 2. Prompt once on a long-running entry Once. A timer left running overnight is the case being caught. Files: `src/reminders.ts` 3. Back off after dismissals Several dismissals is an answer for the day. Files: `src/reminders.ts` ### Done when - [ ] Idle time is offered on return - [ ] A long-running entry prompts once - [ ] Reminders stop after repeated dismissal - [ ] The idle threshold has a defensible default ### Watch out - An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. ## Phase 4 · Projects, rates and what changed when Billable time is computed from the rate that applied at the time. ### Steps 1. Store rates with effective dates Applying the current rate backwards reprices invoiced work. Files: `src/rates.ts`, `src/rates.test.ts` 2. Split correctly at a rate change Mid-period changes are the case that gets it wrong. Files: `src/value.ts` 3. State applied rates on reports So a disagreement is about the rate rather than the arithmetic. Files: `src/report.ts` ### Done when - [ ] Historic entries use the rate of their time - [ ] A mid-period rate change splits correctly - [ ] Reports state the rates applied - [ ] Non-billable time counts in totals but not value ### Watch out - Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. ## Not in this build - Automatic activity detection from the screen. - Team tracking and manager visibility. - Invoicing and payment collection. - Hardware device integration. ## After v1, if you want it - Estimates against actuals per project, which is where tracking starts improving planning - A weekly review of gaps and fills, since a rising fill rate means the switching is too slow - Calendar events offered as gap fills, which covers most meetings automatically ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Your working time record. DB_PATH=~/.local/share/timetrack/log.db # Required. The global hotkey. Choose one nothing else claims. SWITCH_HOTKEY=Ctrl+Shift+T # Required. The threshold that fails the build. Switch cost decides whether tracking happens. MAX_SWITCH_MS=800 # Required. When idle time is offered for assignment. Too short and the tracker gets closed. IDLE_THRESHOLD_SECONDS=300 # Required. Gaps are detected against these. Evenings are not missing data. WORKING_HOURS=09:00-18:00 # Required. Applied at export only. The stored entry keeps the actual duration. ROUNDING_RULE=none
You are building a production product version of Timeular. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Timeular · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Timeular, track time with projects and reminders using software-only controls. The hard boundary is physical tracker option, mobile apps, calendar integration, team features, and sync, plus ubiquitous capture, coaching, and team workflow. ## Product outcome A tracker fast enough that switching is not a decision, honest enough to show the time you forgot, and exact enough that a client asking what the real figure was can be answered. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop OS - notification permission - optional accessibility permission for app-name tracking - local SQLite ## Explicit non-goals for v1 - Automatic activity detection from the screen. - Team tracking and manager visibility. - Invoicing and payment collection. - Hardware device integration. - physical tracker option, mobile apps, calendar integration, team features, and sync - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform ## Success criteria - Switching is confirmed under the threshold including a cold start - Gaps during working hours are confirmed surfaced - Reminders are confirmed to stop after repeated dismissal - Historic entries are confirmed to use the rate of their time - Stored entries are confirmed unchanged by exporting ===== BRIEF.md ===== # Build brief · Timeular The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me deliberate time tracking where switching activity costs one keystroke to replace Timeular. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Switching: A global hotkey to a switcher that closes on selection · the cost of switching is what determines whether tracking happens at all - Gaps: Untracked time detected and offered for filling · forgetting is normal and unrecoverable gaps make the data useless - Runtime: TypeScript with a local application and SQLite · a personal time log does not need a service - Reminders: Idle and long-running detection, gentle rather than nagging · a nagging tracker gets closed - Export: Complete, with rounding applied at export only · the stored entry keeps the actual duration ### Data model (create this before Phase 1) - activities(id, name, colour, project_id, hotkey, archived_at) - entries(id, activity_id, started_at, ended_at, note, source) · source is switched, filled or edited - gaps(id, from_at, to_at, resolved_by_entry_id, dismissed_at) · untracked time, surfaced rather than ignored - idle_events(at, duration_seconds, resolution) · resolution is discarded, assigned or kept - projects(id, name, client, billable, rate_minor, rate_effective_from) - exports(id, period, rounding_rule, generated_at, path) - An entry records whether it was created by switching, by filling a gap or by editing, because data entered three days later is not the same evidence as data entered at the time. ### Phase 1 · Switching in one keystroke Build: A global hotkey opening a switcher, with per-activity hotkeys for the common ones. The switcher closes on selection with no confirmation. Measure the time from keystroke to switched and treat it as the primary requirement. Trap: A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. Done when: Switching activity is measured at under a second from keystroke including the first switch after a cold start. No confirmation or required field blocks a switch. Direct hotkeys exist for frequent activities. A note can be added to a running entry without interrupting tracking. Do not build yet: No gaps, reminders or reporting. ### Phase 2 · The time you forgot to track Build: Detect untracked periods during working hours and surface them as gaps. Offer to fill a gap from adjacent context. Let a gap be dismissed as genuinely not work, and keep the dismissal. Trap: Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. Done when: Untracked periods during working hours are surfaced as gaps. Filling a gap takes one action and records the entry as filled rather than switched. A dismissed gap stays dismissed. The day's total distinguishes tracked, filled and dismissed time. Do not build yet: No reminders or reporting yet. ### Phase 3 · Reminders that do not become noise Build: Detect idle time and ask what to do with it on return. Detect an entry running far longer than that activity usually does. Limit how often reminders can appear, and stop for the day after repeated dismissal. Trap: An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. Done when: Idle time is detected and offered for assignment or discarding on return. An entry running far beyond its activity's usual duration prompts once, not repeatedly. Reminders stop for the day after repeated dismissal. The idle threshold is configurable and its default is derived from observed patterns rather than assumed. Do not build yet: No projects, rates or export yet. ### Phase 4 · Projects, rates and what changed when Build: Projects with billable rates carrying effective dates. Compute value from the rate in force when the work happened, not the current one. Handle a rate change mid-period. Trap: Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. Done when: Historic entries are valued at the rate in force at the time, verified by changing a rate and re-running a past report. A rate change mid-period splits correctly at the boundary. Reports state which rates were applied. Non-billable activities are excluded from value while remaining in time totals. Do not build yet: No export or rounding yet. ### Phase 5 · Export, rounding and the honest total Build: Apply rounding and minimum increments at export only, showing actual and rounded together. Export complete data including entry sources. Never modify a stored entry by rounding. Trap: Rounding the stored entry destroys the actual figure, so a client asking what the real time was cannot be answered, and repeated rounding on edits compounds the error. Round at export, show both, and keep the entry exact. Done when: Rounding applies at export with actual and rounded shown together. Stored entries are unchanged by exporting twice, verified by comparison. Export includes entry sources so filled time is distinguishable from switched. A period with a rate change exports with both rates stated. Do not build yet: Nothing after this. ### Out of scope (and why) - Automatic activity detection from what is on screen, which is a different tool with a much larger privacy cost. - Team tracking and manager visibility, which changes what the tool is for and who it serves. - Invoicing and payment collection, which is an adjacent product with its own requirements. - Hardware device integration, which is the original product's distinguishing feature and is not reproducible in software. ### README must contain - That the switch time is measured and treated as the primary requirement, because switching cost decides whether tracking happens - That gaps are surfaced and fillable rather than silently lost - That entries record whether they were switched, filled or edited, since those are different evidence - That rates carry effective dates and historic work is valued at the rate of its time - That rounding happens at export and the stored entry keeps the actual duration ===== ARCHITECTURE.md ===== # Architecture · Timeular ## Stack | Part | Choice | Why | | --- | --- | --- | | Switching | A global hotkey to a switcher that closes on selection | the cost of switching is what determines whether tracking happens at all | | Gaps | Untracked time detected and offered for filling | forgetting is normal and unrecoverable gaps make the data useless | | Runtime | TypeScript with a local application and SQLite | a personal time log does not need a service | | Reminders | Idle and long-running detection, gentle rather than nagging | a nagging tracker gets closed | | Export | Complete, with rounding applied at export only | the stored entry keeps the actual duration | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Switching | Instant activity changes. | Any interface, provided nothing is required before a switch completes. | | Gaps | Surfacing and filling untracked time. | Any detection, provided gaps are visible rather than ignored. | | Reminders | Idle handling with backoff. | Any implementation, provided it stops after repeated dismissal. | | Rates | Effective-dated valuation. | Any store, provided historic work uses the rate of its time. | | Export | Rounding applied outward only. | Any rules, provided stored entries stay exact. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Your working time record. - `SWITCH_HOTKEY` · required · The global hotkey. Choose one nothing else claims. - `MAX_SWITCH_MS` · required · The threshold that fails the build. Switch cost decides whether tracking happens. - `IDLE_THRESHOLD_SECONDS` · required · When idle time is offered for assignment. Too short and the tracker gets closed. - `WORKING_HOURS` · required · Gaps are detected against these. Evenings are not missing data. - `ROUNDING_RULE` · required · Applied at export only. The stored entry keeps the actual duration. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Timeular product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A global hotkey to a switcher that closes on selection, Untracked time detected and offered for filling, TypeScript with a local application and SQLite, Idle and long-running detection, gentle rather than nagging, Complete, with rounding applied at export only. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. - Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. - An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. - Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. - Rounding the stored entry destroys the actual figure, so a client asking what the real time was cannot be answered, and repeated rounding on edits compounds the error. Round at export, show both, and keep the entry exact. ===== MILESTONES.md ===== # Delivery milestones · Timeular Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Switching in one keystroke Changing what you are tracking takes under a second. ### Steps 1. Pre-warm the switcher The first switch of the day is the one that decides the habit. Files: `src/switcher.ts` 2. Never require a field to switch Notes are added to the entry afterwards. Files: `src/entries.ts` 3. Measure and log the switch time A number, not an impression. It is the primary requirement. Files: `src/metrics.ts` ### Done when - [ ] Switching is under a second including cold start - [ ] No confirmation or required field blocks it - [ ] Frequent activities have direct hotkeys - [ ] Notes can be added to a running entry ### Watch out - A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. ## M2 · The time you forgot to track Gaps are visible and fillable rather than silently lost. ### Steps 1. Detect gaps against working hours Not against the clock. Evenings are not missing data. Files: `src/gaps.ts` 2. Make filling one action A slow fill flow is the same problem as a slow switch. Files: `src/gaps.ts` 3. Record entry source Filled three days later is weaker evidence than switched at the time. Files: `src/entries.ts` ### Done when - [ ] Gaps during working hours are surfaced - [ ] Filling a gap is one action - [ ] Dismissals persist - [ ] Totals distinguish tracked, filled and dismissed ### Watch out - Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. ## M3 · Reminders that do not become noise You are nudged when it helps and left alone when it does not. ### Steps 1. Ask about idle time on return Not during. Interrupting a break is the wrong moment. Files: `src/idle.ts` 2. Prompt once on a long-running entry Once. A timer left running overnight is the case being caught. Files: `src/reminders.ts` 3. Back off after dismissals Several dismissals is an answer for the day. Files: `src/reminders.ts` ### Done when - [ ] Idle time is offered on return - [ ] A long-running entry prompts once - [ ] Reminders stop after repeated dismissal - [ ] The idle threshold has a defensible default ### Watch out - An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. ## M4 · Projects, rates and what changed when Billable time is computed from the rate that applied at the time. ### Steps 1. Store rates with effective dates Applying the current rate backwards reprices invoiced work. Files: `src/rates.ts`, `src/rates.test.ts` 2. Split correctly at a rate change Mid-period changes are the case that gets it wrong. Files: `src/value.ts` 3. State applied rates on reports So a disagreement is about the rate rather than the arithmetic. Files: `src/report.ts` ### Done when - [ ] Historic entries use the rate of their time - [ ] A mid-period rate change splits correctly - [ ] Reports state the rates applied - [ ] Non-billable time counts in totals but not value ### Watch out - Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. ## M5 · Export, rounding and the honest total (production only) Exported time matches the agreed rule and the stored data stays exact. ### Steps 1. Round at export only The stored entry keeps the truth. Files: `src/rounding.ts` 2. Show actual and rounded together Hiding the difference is how billing disputes start. Files: `src/export.ts` 3. Export entry sources Filled and switched are different evidence and a client may ask. Files: `src/export.ts` ### Done when - [ ] Rounding applies at export and shows both figures - [ ] Stored entries are unchanged by exporting - [ ] Export distinguishes filled from switched time - [ ] Rate changes appear in the export ### Watch out - Rounding the stored entry destroys the actual figure, so a client asking what the real time was cannot be answered, and repeated rounding on edits compounds the error. Round at export, show both, and keep the entry exact. ===== OPERATIONS.md ===== # Operations · Timeular ## Backup A time log is billing evidence and gets more valuable with age. Back it up, and keep the exports too, since an export is what someone else received and may ask about. ## Restore Restore and confirm entry sources survived, since losing the distinction between switched and filled time removes the only signal about how reliable a given entry is. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Switch time is the metric, logged on every switch, since it degrades as the database grows and its degradation is exactly what ends the habit. ## Incident checklist A week that looks like eleven hours means gaps are not being surfaced rather than that nothing happened. That is a detection problem, and the working hours configuration is usually where it starts. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Switching is confirmed under the threshold including a cold start - [ ] Gaps during working hours are confirmed surfaced - [ ] Reminders are confirmed to stop after repeated dismissal - [ ] Historic entries are confirmed to use the rate of their time - [ ] Stored entries are confirmed unchanged by exporting ## Launch constraint Do not market omitted Timeular capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Your working time record. DB_PATH=~/.local/share/timetrack/log.db # Required. The global hotkey. Choose one nothing else claims. SWITCH_HOTKEY=Ctrl+Shift+T # Required. The threshold that fails the build. Switch cost decides whether tracking happens. MAX_SWITCH_MS=800 # Required. When idle time is offered for assignment. Too short and the tracker gets closed. IDLE_THRESHOLD_SECONDS=300 # Required. Gaps are detected against these. Evenings are not missing data. WORKING_HOURS=09:00-18:00 # Required. Applied at export only. The stored entry keeps the actual duration. ROUNDING_RULE=none
# Timeular · indie build A manual time tracker built on the observation that manual tracking fails for exactly two reasons: switching takes too long and people forget to start. So the entire build is about making the switch instant and making the gaps recoverable. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Switching | A global hotkey to a switcher that closes on selection | the cost of switching is what determines whether tracking happens at all | | Gaps | Untracked time detected and offered for filling | forgetting is normal and unrecoverable gaps make the data useless | | Runtime | TypeScript with a local application and SQLite | a personal time log does not need a service | | Reminders | Idle and long-running detection, gentle rather than nagging | a nagging tracker gets closed | | Export | Complete, with rounding applied at export only | the stored entry keeps the actual duration | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A global hotkey and idle detection mechanism** · free - Why: Both are platform-specific, and the idle detection in particular differs in what it can see and what permission it needs. - Get it: Whatever your desktop framework provides, checking the permission requirement for idle time before designing onboarding. - Verify: A hotkey fires from another application and idle seconds can be read - [ ] **Two weeks of your own real tracking** · free, and it takes two weeks - Why: Switch friction, gap frequency and reminder tolerance are all behavioural, and two weeks is roughly how long it takes for a tracker to be abandoned if it is annoying. - Get it: Track your real work, including the days you forget entirely, since those produce the gaps the build exists to handle. - Verify: Two weeks exist including at least three days with substantial gaps - [ ] **Your working hours and rounding rule** · free - Why: Gaps are detected against working hours and exports are rounded by an agreed rule, and both are enforced in code rather than being preferences. - Get it: Write down your working hours per day and the rounding rule you have agreed with whoever receives the exports. - Verify: Both are written down and configured before the first export ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Automatic activity detection from the screen. - Team tracking and manager visibility. - Invoicing and payment collection. - Hardware device integration. - physical tracker option, mobile apps, calendar integration, team features, and sync - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform If one of those is essential to you, that is the reason to keep paying for Timeular, and the README should say so rather than pretend.
# Build brief · Timeular The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me deliberate time tracking where switching activity costs one keystroke to replace Timeular. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Switching: A global hotkey to a switcher that closes on selection · the cost of switching is what determines whether tracking happens at all - Gaps: Untracked time detected and offered for filling · forgetting is normal and unrecoverable gaps make the data useless - Runtime: TypeScript with a local application and SQLite · a personal time log does not need a service - Reminders: Idle and long-running detection, gentle rather than nagging · a nagging tracker gets closed - Export: Complete, with rounding applied at export only · the stored entry keeps the actual duration ### Data model (create this before Phase 1) - activities(id, name, colour, project_id, hotkey, archived_at) - entries(id, activity_id, started_at, ended_at, note, source) · source is switched, filled or edited - gaps(id, from_at, to_at, resolved_by_entry_id, dismissed_at) · untracked time, surfaced rather than ignored - idle_events(at, duration_seconds, resolution) · resolution is discarded, assigned or kept - projects(id, name, client, billable, rate_minor, rate_effective_from) - exports(id, period, rounding_rule, generated_at, path) - An entry records whether it was created by switching, by filling a gap or by editing, because data entered three days later is not the same evidence as data entered at the time. ### Phase 1 · Switching in one keystroke Build: A global hotkey opening a switcher, with per-activity hotkeys for the common ones. The switcher closes on selection with no confirmation. Measure the time from keystroke to switched and treat it as the primary requirement. Trap: A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. Done when: Switching activity is measured at under a second from keystroke including the first switch after a cold start. No confirmation or required field blocks a switch. Direct hotkeys exist for frequent activities. A note can be added to a running entry without interrupting tracking. Do not build yet: No gaps, reminders or reporting. ### Phase 2 · The time you forgot to track Build: Detect untracked periods during working hours and surface them as gaps. Offer to fill a gap from adjacent context. Let a gap be dismissed as genuinely not work, and keep the dismissal. Trap: Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. Done when: Untracked periods during working hours are surfaced as gaps. Filling a gap takes one action and records the entry as filled rather than switched. A dismissed gap stays dismissed. The day's total distinguishes tracked, filled and dismissed time. Do not build yet: No reminders or reporting yet. ### Phase 3 · Reminders that do not become noise Build: Detect idle time and ask what to do with it on return. Detect an entry running far longer than that activity usually does. Limit how often reminders can appear, and stop for the day after repeated dismissal. Trap: An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. Done when: Idle time is detected and offered for assignment or discarding on return. An entry running far beyond its activity's usual duration prompts once, not repeatedly. Reminders stop for the day after repeated dismissal. The idle threshold is configurable and its default is derived from observed patterns rather than assumed. Do not build yet: No projects, rates or export yet. ### Phase 4 · Projects, rates and what changed when Build: Projects with billable rates carrying effective dates. Compute value from the rate in force when the work happened, not the current one. Handle a rate change mid-period. Trap: Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. Done when: Historic entries are valued at the rate in force at the time, verified by changing a rate and re-running a past report. A rate change mid-period splits correctly at the boundary. Reports state which rates were applied. Non-billable activities are excluded from value while remaining in time totals. Do not build yet: No export or rounding yet. ### Phase 5 · Export, rounding and the honest total Build: Apply rounding and minimum increments at export only, showing actual and rounded together. Export complete data including entry sources. Never modify a stored entry by rounding. Trap: Rounding the stored entry destroys the actual figure, so a client asking what the real time was cannot be answered, and repeated rounding on edits compounds the error. Round at export, show both, and keep the entry exact. Done when: Rounding applies at export with actual and rounded shown together. Stored entries are unchanged by exporting twice, verified by comparison. Export includes entry sources so filled time is distinguishable from switched. A period with a rate change exports with both rates stated. Do not build yet: Nothing after this. ### Out of scope (and why) - Automatic activity detection from what is on screen, which is a different tool with a much larger privacy cost. - Team tracking and manager visibility, which changes what the tool is for and who it serves. - Invoicing and payment collection, which is an adjacent product with its own requirements. - Hardware device integration, which is the original product's distinguishing feature and is not reproducible in software. ### README must contain - That the switch time is measured and treated as the primary requirement, because switching cost decides whether tracking happens - That gaps are surfaced and fillable rather than silently lost - That entries record whether they were switched, filled or edited, since those are different evidence - That rates carry effective dates and historic work is valued at the rate of its time - That rounding happens at export and the stored entry keeps the actual duration
# Agent instructions · Timeular indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A global hotkey to a switcher that closes on selection, Untracked time detected and offered for filling, TypeScript with a local application and SQLite, Idle and long-running detection, gentle rather than nagging, Complete, with rounding applied at export only. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. - Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. - An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. - Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened.
# Build plan · Timeular A manual time tracker built on the observation that manual tracking fails for exactly two reasons: switching takes too long and people forget to start. So the entire build is about making the switch instant and making the gaps recoverable. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Switching in one keystroke Changing what you are tracking takes under a second. ### Steps 1. Pre-warm the switcher The first switch of the day is the one that decides the habit. Files: `src/switcher.ts` 2. Never require a field to switch Notes are added to the entry afterwards. Files: `src/entries.ts` 3. Measure and log the switch time A number, not an impression. It is the primary requirement. Files: `src/metrics.ts` ### Done when - [ ] Switching is under a second including cold start - [ ] No confirmation or required field blocks it - [ ] Frequent activities have direct hotkeys - [ ] Notes can be added to a running entry ### Watch out - A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. ## Phase 2 · The time you forgot to track Gaps are visible and fillable rather than silently lost. ### Steps 1. Detect gaps against working hours Not against the clock. Evenings are not missing data. Files: `src/gaps.ts` 2. Make filling one action A slow fill flow is the same problem as a slow switch. Files: `src/gaps.ts` 3. Record entry source Filled three days later is weaker evidence than switched at the time. Files: `src/entries.ts` ### Done when - [ ] Gaps during working hours are surfaced - [ ] Filling a gap is one action - [ ] Dismissals persist - [ ] Totals distinguish tracked, filled and dismissed ### Watch out - Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. ## Phase 3 · Reminders that do not become noise You are nudged when it helps and left alone when it does not. ### Steps 1. Ask about idle time on return Not during. Interrupting a break is the wrong moment. Files: `src/idle.ts` 2. Prompt once on a long-running entry Once. A timer left running overnight is the case being caught. Files: `src/reminders.ts` 3. Back off after dismissals Several dismissals is an answer for the day. Files: `src/reminders.ts` ### Done when - [ ] Idle time is offered on return - [ ] A long-running entry prompts once - [ ] Reminders stop after repeated dismissal - [ ] The idle threshold has a defensible default ### Watch out - An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. ## Phase 4 · Projects, rates and what changed when Billable time is computed from the rate that applied at the time. ### Steps 1. Store rates with effective dates Applying the current rate backwards reprices invoiced work. Files: `src/rates.ts`, `src/rates.test.ts` 2. Split correctly at a rate change Mid-period changes are the case that gets it wrong. Files: `src/value.ts` 3. State applied rates on reports So a disagreement is about the rate rather than the arithmetic. Files: `src/report.ts` ### Done when - [ ] Historic entries use the rate of their time - [ ] A mid-period rate change splits correctly - [ ] Reports state the rates applied - [ ] Non-billable time counts in totals but not value ### Watch out - Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. ## Not in this build - Automatic activity detection from the screen. - Team tracking and manager visibility. - Invoicing and payment collection. - Hardware device integration. ## After v1, if you want it - Estimates against actuals per project, which is where tracking starts improving planning - A weekly review of gaps and fills, since a rising fill rate means the switching is too slow - Calendar events offered as gap fills, which covers most meetings automatically
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Your working time record. DB_PATH=~/.local/share/timetrack/log.db # Required. The global hotkey. Choose one nothing else claims. SWITCH_HOTKEY=Ctrl+Shift+T # Required. The threshold that fails the build. Switch cost decides whether tracking happens. MAX_SWITCH_MS=800 # Required. When idle time is offered for assignment. Too short and the tracker gets closed. IDLE_THRESHOLD_SECONDS=300 # Required. Gaps are detected against these. Evenings are not missing data. WORKING_HOURS=09:00-18:00 # Required. Applied at export only. The stored entry keeps the actual duration. ROUNDING_RULE=none
# Timeular · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Timeular, track time with projects and reminders using software-only controls. The hard boundary is physical tracker option, mobile apps, calendar integration, team features, and sync, plus ubiquitous capture, coaching, and team workflow. ## Product outcome A tracker fast enough that switching is not a decision, honest enough to show the time you forgot, and exact enough that a client asking what the real figure was can be answered. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - desktop OS - notification permission - optional accessibility permission for app-name tracking - local SQLite ## Explicit non-goals for v1 - Automatic activity detection from the screen. - Team tracking and manager visibility. - Invoicing and payment collection. - Hardware device integration. - physical tracker option, mobile apps, calendar integration, team features, and sync - cross-device history - team timesheets and payroll - behavioral datasets - blocking across every platform ## Success criteria - Switching is confirmed under the threshold including a cold start - Gaps during working hours are confirmed surfaced - Reminders are confirmed to stop after repeated dismissal - Historic entries are confirmed to use the rate of their time - Stored entries are confirmed unchanged by exporting
# Build brief · Timeular The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me deliberate time tracking where switching activity costs one keystroke to replace Timeular. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Switching: A global hotkey to a switcher that closes on selection · the cost of switching is what determines whether tracking happens at all - Gaps: Untracked time detected and offered for filling · forgetting is normal and unrecoverable gaps make the data useless - Runtime: TypeScript with a local application and SQLite · a personal time log does not need a service - Reminders: Idle and long-running detection, gentle rather than nagging · a nagging tracker gets closed - Export: Complete, with rounding applied at export only · the stored entry keeps the actual duration ### Data model (create this before Phase 1) - activities(id, name, colour, project_id, hotkey, archived_at) - entries(id, activity_id, started_at, ended_at, note, source) · source is switched, filled or edited - gaps(id, from_at, to_at, resolved_by_entry_id, dismissed_at) · untracked time, surfaced rather than ignored - idle_events(at, duration_seconds, resolution) · resolution is discarded, assigned or kept - projects(id, name, client, billable, rate_minor, rate_effective_from) - exports(id, period, rounding_rule, generated_at, path) - An entry records whether it was created by switching, by filling a gap or by editing, because data entered three days later is not the same evidence as data entered at the time. ### Phase 1 · Switching in one keystroke Build: A global hotkey opening a switcher, with per-activity hotkeys for the common ones. The switcher closes on selection with no confirmation. Measure the time from keystroke to switched and treat it as the primary requirement. Trap: A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. Done when: Switching activity is measured at under a second from keystroke including the first switch after a cold start. No confirmation or required field blocks a switch. Direct hotkeys exist for frequent activities. A note can be added to a running entry without interrupting tracking. Do not build yet: No gaps, reminders or reporting. ### Phase 2 · The time you forgot to track Build: Detect untracked periods during working hours and surface them as gaps. Offer to fill a gap from adjacent context. Let a gap be dismissed as genuinely not work, and keep the dismissal. Trap: Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. Done when: Untracked periods during working hours are surfaced as gaps. Filling a gap takes one action and records the entry as filled rather than switched. A dismissed gap stays dismissed. The day's total distinguishes tracked, filled and dismissed time. Do not build yet: No reminders or reporting yet. ### Phase 3 · Reminders that do not become noise Build: Detect idle time and ask what to do with it on return. Detect an entry running far longer than that activity usually does. Limit how often reminders can appear, and stop for the day after repeated dismissal. Trap: An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. Done when: Idle time is detected and offered for assignment or discarding on return. An entry running far beyond its activity's usual duration prompts once, not repeatedly. Reminders stop for the day after repeated dismissal. The idle threshold is configurable and its default is derived from observed patterns rather than assumed. Do not build yet: No projects, rates or export yet. ### Phase 4 · Projects, rates and what changed when Build: Projects with billable rates carrying effective dates. Compute value from the rate in force when the work happened, not the current one. Handle a rate change mid-period. Trap: Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. Done when: Historic entries are valued at the rate in force at the time, verified by changing a rate and re-running a past report. A rate change mid-period splits correctly at the boundary. Reports state which rates were applied. Non-billable activities are excluded from value while remaining in time totals. Do not build yet: No export or rounding yet. ### Phase 5 · Export, rounding and the honest total Build: Apply rounding and minimum increments at export only, showing actual and rounded together. Export complete data including entry sources. Never modify a stored entry by rounding. Trap: Rounding the stored entry destroys the actual figure, so a client asking what the real time was cannot be answered, and repeated rounding on edits compounds the error. Round at export, show both, and keep the entry exact. Done when: Rounding applies at export with actual and rounded shown together. Stored entries are unchanged by exporting twice, verified by comparison. Export includes entry sources so filled time is distinguishable from switched. A period with a rate change exports with both rates stated. Do not build yet: Nothing after this. ### Out of scope (and why) - Automatic activity detection from what is on screen, which is a different tool with a much larger privacy cost. - Team tracking and manager visibility, which changes what the tool is for and who it serves. - Invoicing and payment collection, which is an adjacent product with its own requirements. - Hardware device integration, which is the original product's distinguishing feature and is not reproducible in software. ### README must contain - That the switch time is measured and treated as the primary requirement, because switching cost decides whether tracking happens - That gaps are surfaced and fillable rather than silently lost - That entries record whether they were switched, filled or edited, since those are different evidence - That rates carry effective dates and historic work is valued at the rate of its time - That rounding happens at export and the stored entry keeps the actual duration
# Architecture · Timeular ## Stack | Part | Choice | Why | | --- | --- | --- | | Switching | A global hotkey to a switcher that closes on selection | the cost of switching is what determines whether tracking happens at all | | Gaps | Untracked time detected and offered for filling | forgetting is normal and unrecoverable gaps make the data useless | | Runtime | TypeScript with a local application and SQLite | a personal time log does not need a service | | Reminders | Idle and long-running detection, gentle rather than nagging | a nagging tracker gets closed | | Export | Complete, with rounding applied at export only | the stored entry keeps the actual duration | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Switching | Instant activity changes. | Any interface, provided nothing is required before a switch completes. | | Gaps | Surfacing and filling untracked time. | Any detection, provided gaps are visible rather than ignored. | | Reminders | Idle handling with backoff. | Any implementation, provided it stops after repeated dismissal. | | Rates | Effective-dated valuation. | Any store, provided historic work uses the rate of its time. | | Export | Rounding applied outward only. | Any rules, provided stored entries stay exact. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Your working time record. - `SWITCH_HOTKEY` · required · The global hotkey. Choose one nothing else claims. - `MAX_SWITCH_MS` · required · The threshold that fails the build. Switch cost decides whether tracking happens. - `IDLE_THRESHOLD_SECONDS` · required · When idle time is offered for assignment. Too short and the tracker gets closed. - `WORKING_HOURS` · required · Gaps are detected against these. Evenings are not missing data. - `ROUNDING_RULE` · required · Applied at export only. The stored entry keeps the actual duration. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Timeular product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A global hotkey to a switcher that closes on selection, Untracked time detected and offered for filling, TypeScript with a local application and SQLite, Idle and long-running detection, gentle rather than nagging, Complete, with rounding applied at export only. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. - Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. - An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. - Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. - Rounding the stored entry destroys the actual figure, so a client asking what the real time was cannot be answered, and repeated rounding on edits compounds the error. Round at export, show both, and keep the entry exact.
# Delivery milestones · Timeular Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Switching in one keystroke Changing what you are tracking takes under a second. ### Steps 1. Pre-warm the switcher The first switch of the day is the one that decides the habit. Files: `src/switcher.ts` 2. Never require a field to switch Notes are added to the entry afterwards. Files: `src/entries.ts` 3. Measure and log the switch time A number, not an impression. It is the primary requirement. Files: `src/metrics.ts` ### Done when - [ ] Switching is under a second including cold start - [ ] No confirmation or required field blocks it - [ ] Frequent activities have direct hotkeys - [ ] Notes can be added to a running entry ### Watch out - A switcher requiring a mouse, a confirmation or a note before it will change activity costs several seconds each time, and at twenty switches a day people stop using it within a fortnight. Make the switch immediate and let the note be added afterwards to the entry that already exists. ## M2 · The time you forgot to track Gaps are visible and fillable rather than silently lost. ### Steps 1. Detect gaps against working hours Not against the clock. Evenings are not missing data. Files: `src/gaps.ts` 2. Make filling one action A slow fill flow is the same problem as a slow switch. Files: `src/gaps.ts` 3. Record entry source Filled three days later is weaker evidence than switched at the time. Files: `src/entries.ts` ### Done when - [ ] Gaps during working hours are surfaced - [ ] Filling a gap is one action - [ ] Dismissals persist - [ ] Totals distinguish tracked, filled and dismissed ### Watch out - Silently ignoring untracked time produces a week that looks like eleven hours of work, and the person concludes the tracker is broken rather than that they forgot. Surface the gaps, make filling one quick, and let dismissal be an answer so the total is honest either way. ## M3 · Reminders that do not become noise You are nudged when it helps and left alone when it does not. ### Steps 1. Ask about idle time on return Not during. Interrupting a break is the wrong moment. Files: `src/idle.ts` 2. Prompt once on a long-running entry Once. A timer left running overnight is the case being caught. Files: `src/reminders.ts` 3. Back off after dismissals Several dismissals is an answer for the day. Files: `src/reminders.ts` ### Done when - [ ] Idle time is offered on return - [ ] A long-running entry prompts once - [ ] Reminders stop after repeated dismissal - [ ] The idle threshold has a defensible default ### Watch out - An idle prompt appearing every time you read something for four minutes is a tracker people close by lunchtime. Set the idle threshold from observed behaviour rather than a guess, and stop asking for the day once someone has dismissed several in a row. ## M4 · Projects, rates and what changed when Billable time is computed from the rate that applied at the time. ### Steps 1. Store rates with effective dates Applying the current rate backwards reprices invoiced work. Files: `src/rates.ts`, `src/rates.test.ts` 2. Split correctly at a rate change Mid-period changes are the case that gets it wrong. Files: `src/value.ts` 3. State applied rates on reports So a disagreement is about the rate rather than the arithmetic. Files: `src/report.ts` ### Done when - [ ] Historic entries use the rate of their time - [ ] A mid-period rate change splits correctly - [ ] Reports state the rates applied - [ ] Non-billable time counts in totals but not value ### Watch out - Applying the current rate to historic entries silently reprices work you already invoiced, so a report run today disagrees with an invoice sent last month and nobody can reconstruct which was right. Store rates with effective dates and always use the one that applied when the work happened. ## M5 · Export, rounding and the honest total (production only) Exported time matches the agreed rule and the stored data stays exact. ### Steps 1. Round at export only The stored entry keeps the truth. Files: `src/rounding.ts` 2. Show actual and rounded together Hiding the difference is how billing disputes start. Files: `src/export.ts` 3. Export entry sources Filled and switched are different evidence and a client may ask. Files: `src/export.ts` ### Done when - [ ] Rounding applies at export and shows both figures - [ ] Stored entries are unchanged by exporting - [ ] Export distinguishes filled from switched time - [ ] Rate changes appear in the export ### Watch out - Rounding the stored entry destroys the actual figure, so a client asking what the real time was cannot be answered, and repeated rounding on edits compounds the error. Round at export, show both, and keep the entry exact.
# Operations · Timeular ## Backup A time log is billing evidence and gets more valuable with age. Back it up, and keep the exports too, since an export is what someone else received and may ask about. ## Restore Restore and confirm entry sources survived, since losing the distinction between switched and filled time removes the only signal about how reliable a given entry is. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Switch time is the metric, logged on every switch, since it degrades as the database grows and its degradation is exactly what ends the habit. ## Incident checklist A week that looks like eleven hours means gaps are not being surfaced rather than that nothing happened. That is a detection problem, and the working hours configuration is usually where it starts. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Switching is confirmed under the threshold including a cold start - [ ] Gaps during working hours are confirmed surfaced - [ ] Reminders are confirmed to stop after repeated dismissal - [ ] Historic entries are confirmed to use the rate of their time - [ ] Stored entries are confirmed unchanged by exporting ## Launch constraint Do not market omitted Timeular capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Your working time record. DB_PATH=~/.local/share/timetrack/log.db # Required. The global hotkey. Choose one nothing else claims. SWITCH_HOTKEY=Ctrl+Shift+T # Required. The threshold that fails the build. Switch cost decides whether tracking happens. MAX_SWITCH_MS=800 # Required. When idle time is offered for assignment. Too short and the tracker gets closed. IDLE_THRESHOLD_SECONDS=300 # Required. Gaps are detected against these. Evenings are not missing data. WORKING_HOURS=09:00-18:00 # Required. Applied at export only. The stored entry keeps the actual duration. ROUNDING_RULE=none
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Timeular because the timer is simple, but people pay for frictionless capture and a history they trust enough to invoice or change habits from. The recurring cost buys idle detection, permissions, app metadata, privacy, notifications, sync, reports, integrations, and backups, not just the visible interface.
xphysical tracker option, mobile apps, calendar integration, team features, and sync
xcross-device history
xteam timesheets and payroll
xbehavioral datasets
xblocking across every platform
Don't feel like building it? These folks already made it free.
all 8 free alternatives to Timeular →· no votes, no pay-to-list · just what's real
Timeular pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| personal | $9/user | $7.50/user | 1 project budget; time tracking and reports. |
| personal pro | $14/user | $11.67/user | Up to 50 project budgets; rounding options of 1, 5, 6, 10, 15, or 30 minutes. |
| team | $19/user | $15.83/user | Team features; dedicated success manager at 20+ seats. |
| enterprise | custom | — | Custom seats, security, and support. |
free tierno free tier
billingmonthly + annual; 30-day trial; Enterprise custom
hidden costsTaxes/VAT are extra and checkout localizes pricing; Team success-manager service starts only at 20+ seats.
verified 2026-08-14 · source ↗
Vibecode Timeular
Kinda. The core of Timeular is buildable in a weekend with the prompt on this page, but there are real gaps: physical tracker option, mobile apps, calendar integration, team features, and sync, cross-device history. Read the honest list above before committing.
How much does Timeular cost?
Timeular costs about $9/month (Personal, checked 2026-07-31), which is $108 per year.
What do I lose by replacing Timeular?
Honestly: physical tracker option, mobile apps, calendar integration, team features, and sync; cross-device history; team timesheets and payroll; behavioral datasets; blocking across every platform. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Timeular?
Yes: Super Productivity (A local project timer with reminders, reports and no plastic eight-sided desk ornament required.) solidtime (Modern multi-user timers, clients and billable rates in one Compose stack; the desktop apps are still beta.) TimeTagger (A lean timeline, tags and PDF or CSV reports in one container; it is built for individuals, not payroll departments.) All 8 curated free alternatives are at vibecodeit.com/timeular/alternatives. The prompt is for when you want it exactly your way.