Vibecode Sunsama
track this build5 phases, 15 steps, beginner friendly0%A daily planning dashboard is not hard, but Sunsama's value is many integrations, careful UX, calendar sync, and habit-forming workflow.
You are building a lean indie version of Sunsama. 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 ===== # Sunsama · indie build A day view where you pull tasks from your lists and calendar into today, estimate each one, watch the planned total against the hours actually free, work through them, and close the day by deciding explicitly what moves and what was abandoned, with estimate accuracy tracked so the numbers get better. Estimated effort: **multi-day**. 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 | | --- | --- | --- | | Runtime | TypeScript with a small server and SQLite | one person's planning data, where the interesting part is the ritual rather than the scale | | Calendar | Read-only calendar access for real availability | planned hours against available hours is the whole point and needs the real calendar | | Sources | Task import from where your work already lives | retyping work into a planner is why planners get abandoned | | Time | A library with full zone data | a day boundary is local, and a plan that rolls over at the wrong hour is confusing daily | | Storage | Local first with export | your planning history is a record of how you work and belongs to you | ## 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 application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Read-only access to your real calendar** · free with an existing account - Why: Available time is the number the whole ritual rests on, and a test calendar gives you a number that means nothing. - Get it: Connect with read-only scopes, which is all this needs and all it should request. - Verify: Busy time appears and matches your actual day - [ ] **Access to where your tasks already live** · free for most personal task tools - Why: Retyping work into a planner is why planners get abandoned, usually in week two. - Get it: An API token or export from your existing task tool. If your tasks live in your head, write them somewhere first, because this build cannot fix that. - Verify: Tasks import with stable identifiers - [ ] **A commitment to two weeks of honest use** · free, but it takes two weeks - Why: Estimate accuracy needs a sample, and the shutdown ritual only shows its value once you have moved the same task three times and had to admit it. - Get it: Decide to run it for two weeks with real work before judging it, and record actual time even on the days you forget the timer. - Verify: Two weeks of plans exist with actual times recorded - [ ] **A neutral wording pass on the reports** · free - Why: A planning tool that reads as criticism gets abandoned, and you cannot audit your own wording because you know what you meant. - Get it: Ask someone to read the report strings and say whether any of it reads as judgement. - Verify: A second reader's notes are recorded ## 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: - The integration breadth of the hosted product. - Writing time blocks back to your calendar. - The accumulated ritual design that makes the paid product work. - Mobile applications. - integration breadth - calendar two-way sync - daily ritual design - mobile apps - analytics - reliability If one of those is essential to you, that is the reason to keep paying for Sunsama, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Sunsama 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 a daily planning ritual: pull work in, estimate it, and shut down honestly at the end to replace Sunsama. 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. ### Rule zero This is deliberately manual. The value is not that software arranges your day, it is that you choose what goes into it and confront how much time you actually have. An automatic scheduler produces a full calendar and no reckoning; a planning ritual produces a shorter list and a decision. So the application never places work for you. It shows the capacity, it shows what you estimated against what happened, and it makes the end of the day a moment where unfinished work is moved deliberately rather than rolled over silently. If you find yourself building a solver, you are building something else. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a small server and SQLite · one person's planning data, where the interesting part is the ritual rather than the scale - Calendar: Read-only calendar access for real availability · planned hours against available hours is the whole point and needs the real calendar - Sources: Task import from where your work already lives · retyping work into a planner is why planners get abandoned - Time: A library with full zone data · a day boundary is local, and a plan that rolls over at the wrong hour is confusing daily - Storage: Local first with export · your planning history is a record of how you work and belongs to you ### Data model (create this before Phase 1) - tasks(id, source, external_id, title, notes, created_at, archived_at, project) - plans(id, on_date, planned_minutes, available_minutes, closed_at, reflection) - plan_items(id, plan_id, task_id, ordinal, estimate_minutes, actual_minutes, state, moved_to_plan_id) · state is planned, done, moved or abandoned - sessions(id, plan_item_id, started_at, ended_at) · time actually spent, which is what makes estimates improve - busy(id, source, starts_at_utc, ends_at_utc) · from your calendar, so availability is real - A task that is not finished is either moved with a decision or abandoned with one. Nothing rolls over silently. ### Phase 1 · Real availability, honestly counted Build: Read busy time from your real calendar and compute available minutes for the day within your working hours. Subtract a buffer you configure for the things that always happen and are never planned. Trap: Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. Done when: Available minutes for today match a hand calculation from your calendar and working hours. A day with back-to-back meetings shows very little available time rather than a full working day. The buffer is configurable and visible. A calendar change updates availability on refresh. Do not build yet: No tasks, planning or review. ### Phase 2 · Pulling work in from where it lives Build: Import tasks from one source you actually use, keeping the external identifier so a task is not duplicated on re-import. Show a backlog to pull from. Marking done locally reflects back to the source where the source supports it. Trap: A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. Done when: Tasks import from a real source and re-importing creates no duplicates. Completing a task locally updates the source, or the interface states clearly that it cannot. A task deleted at the source is marked rather than vanishing from a past plan. The backlog is searchable. Do not build yet: No planning or review yet. ### Phase 3 · Planning the day, with the total in front of you Build: Drag tasks into today and estimate each. Show planned total against available minutes, prominently, updating as you add. Over-commitment is shown as a figure rather than prevented, because sometimes you will do it deliberately. Trap: Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. Done when: Planned total against available minutes is visible and updates as tasks are added. Exceeding available time shows the overage clearly without blocking. Reordering tasks does not change the total. An unestimated task is flagged rather than counted as zero. Do not build yet: No tracking or shutdown yet. ### Phase 4 · Working, and recording what it took Build: Start and stop a task with one action, recording sessions. Allow entering time afterwards, because you will forget to start the timer. Compare estimate against actual per task without commentary. Trap: A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. Done when: Starting and stopping a task records a session. Entering time afterwards is as few actions as using the timer. Estimate against actual is shown per task with no judgement language. A task worked across two sessions sums correctly. Do not build yet: No shutdown or accuracy tracking yet. ### Phase 5 · The shutdown, which is the point Build: A closing flow listing everything unfinished and requiring a decision for each: move to a specific day, or abandon with a reason. Nothing rolls over automatically. Capture a short reflection and close the day. Trap: Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. Done when: Closing the day requires a decision on every unfinished item. Abandoning is one action and records a reason. A closed day cannot be silently edited afterwards. A task moved three times is flagged, because that pattern is information. Do not build yet: Accuracy tracking and export come last. ### Phase 6 · Estimate accuracy and export Build: Report estimate against actual over time, by project and by task size. Show the systematic bias rather than individual misses. Export everything. Keep the language neutral, since consistently underestimating is normal rather than a failing. Trap: Reporting individual misses invites self-criticism and teaches nothing, because any single estimate can be wrong for good reasons. The useful figure is the systematic bias across many tasks, which is stable, actionable and impersonal. Done when: A report shows the ratio of actual to estimated across many tasks, broken down by project and size. The report uses neutral language with no shaming terms, checked against a banned word list. Export produces plans, tasks and sessions as JSON and CSV. At least thirty completed tasks are required before a bias figure is shown. Do not build yet: Nothing after this. ### Out of scope (and why) - The integration breadth of the hosted product, which is the reason it fits into existing workflows without friction. - Two-way calendar sync writing blocks back, which is deliberately not done here because placing work is the thing this build refuses. - The daily ritual design itself, which is the accumulated product thinking and is most of what the subscription buys. - Mobile applications, which matter for a tool consulted through the day. ### README must contain - That the application never places work for you, and why that is the point - That available time subtracts a buffer, and how to tune it from your own history - That nothing rolls over automatically and every unfinished task needs a decision - That estimate accuracy is reported as systematic bias rather than individual misses - That the calendar is read only and no blocks are ever written ===== AGENTS.md ===== # Agent instructions · Sunsama indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a small server and SQLite, Read-only calendar access for real availability, Task import from where your work already lives, A library with full zone data, Local first with export. 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 - Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. - A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. - Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. - A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. - Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. ===== BUILD_PLAN.md ===== # Build plan · Sunsama A day view where you pull tasks from your lists and calendar into today, estimate each one, watch the planned total against the hours actually free, work through them, and close the day by deciding explicitly what moves and what was abandoned, with estimate accuracy tracked so the numbers get better. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Real availability, honestly counted The application knows how many hours you actually have today. ### Steps 1. Read the calendar read-only Busy time only. This application never writes to your calendar, which is a deliberate difference. Files: `src/calendar.ts` 2. Compute available minutes Working hours minus busy time minus buffer, in your zone. Files: `src/availability.ts` 3. Make the buffer visible Shown, not hidden in settings. It is the number that makes the capacity honest. Files: `src/ui/Capacity.tsx` ### Done when - [ ] Available minutes match a hand calculation - [ ] A meeting-heavy day shows little available time - [ ] The buffer is configurable and visible - [ ] A calendar change updates availability ### Watch out - Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. ## Phase 2 · Pulling work in from where it lives You plan from your real tasks rather than retyping them. ### Steps 1. Import with stable identifiers Source plus external identifier, unique. Re-import is then a no-op. Files: `src/import.ts` 2. Reflect completion where possible And say clearly where it is not possible, rather than diverging quietly. Files: `src/sync.ts` 3. Preserve history A task deleted at the source stays on the plans it was on. History is not editable by a later deletion. Files: `src/tasks.ts` ### Done when - [ ] Re-importing creates no duplicates - [ ] Completion reflects back, or the limitation is stated - [ ] A deleted source task stays on past plans - [ ] The backlog is searchable ### Watch out - A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. ## Phase 3 · Planning the day, with the total in front of you You cannot plan eleven hours of work into a six hour day without noticing. ### Steps 1. Build the day view Pull from backlog, order, estimate. Keyboard reachable, because this happens every morning. Files: `src/day.ts` 2. Show the total continuously Planned against available, updating live. Prominent rather than tucked away. Files: `src/ui/DayTotal.tsx` 3. Flag unestimated tasks Counting them as zero makes the total a lie. Flag and ask. Files: `src/day.ts` ### Done when - [ ] The planned against available total updates live - [ ] Over-commitment shows the overage without blocking - [ ] Reordering does not change the total - [ ] An unestimated task is flagged rather than zeroed ### Watch out - Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. ## Phase 4 · Working, and recording what it took Actual time is captured with enough ease that it actually gets captured. ### Steps 1. One action to start and stop From the day view, keyboard reachable. Files: `src/timer.ts` 2. Make retrospective entry equal Same number of actions. This is the path most days will take. Files: `src/timer.ts` 3. Show estimate against actual plainly A pair of numbers. No commentary, because the number is the information. Files: `src/ui/TaskRow.tsx` ### Done when - [ ] Start and stop records a session - [ ] Retrospective entry is as easy as the timer - [ ] Estimate and actual are shown without judgement - [ ] Two sessions on one task sum correctly ### Watch out - A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. ## Phase 5 · The shutdown, which is the point The day ends with a decision about every unfinished thing. ### Steps 1. Require a decision per item Move to a named day, or abandon with a reason. No default and no rollover. Files: `src/shutdown.ts` 2. Make abandoning easy Same effort as moving. It is frequently the correct choice and should not feel like failure. Files: `src/shutdown.ts` 3. Flag repeated moves A task moved several times is telling you something. Surface it without a verdict. Files: `src/shutdown.ts` ### Done when - [ ] Closing requires a decision on every unfinished item - [ ] Abandoning is one action with a recorded reason - [ ] A closed day is not silently editable - [ ] A repeatedly moved task is flagged ### Watch out - Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. ## Not in this build - The integration breadth of the hosted product. - Writing time blocks back to your calendar. - The accumulated ritual design that makes the paid product work. - Mobile applications. ## After v1, if you want it - A weekly review reading back the fortnight's decisions rather than its completions - Per-project time totals, which most freelancers need for invoicing anyway - An estimate suggestion from your own history for a task that resembles a previous one ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Plans, tasks and sessions. A record of how you work, so treat it accordingly. DB_PATH=./data/plans.db # Required. Local wall clock, in your zone. WORKING_HOURS=09:00-17:30 # Required. The zone working hours and day boundaries are expressed in. TIMEZONE=Europe/London # Required. Subtracted from available time for the things that always happen. Tune it from your own history. DAILY_BUFFER_MINUTES=90 # Required. Completed tasks required before a bias figure is shown. ACCURACY_MIN_SAMPLE=30 # Required. Times a task can be moved before it is flagged, without a verdict attached. MOVE_FLAG_THRESHOLD=3
You are building a lean indie version of Sunsama. 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 ===== # Sunsama · indie build A day view where you pull tasks from your lists and calendar into today, estimate each one, watch the planned total against the hours actually free, work through them, and close the day by deciding explicitly what moves and what was abandoned, with estimate accuracy tracked so the numbers get better. Estimated effort: **multi-day**. 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 | | --- | --- | --- | | Runtime | TypeScript with a small server and SQLite | one person's planning data, where the interesting part is the ritual rather than the scale | | Calendar | Read-only calendar access for real availability | planned hours against available hours is the whole point and needs the real calendar | | Sources | Task import from where your work already lives | retyping work into a planner is why planners get abandoned | | Time | A library with full zone data | a day boundary is local, and a plan that rolls over at the wrong hour is confusing daily | | Storage | Local first with export | your planning history is a record of how you work and belongs to you | ## 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 application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Read-only access to your real calendar** · free with an existing account - Why: Available time is the number the whole ritual rests on, and a test calendar gives you a number that means nothing. - Get it: Connect with read-only scopes, which is all this needs and all it should request. - Verify: Busy time appears and matches your actual day - [ ] **Access to where your tasks already live** · free for most personal task tools - Why: Retyping work into a planner is why planners get abandoned, usually in week two. - Get it: An API token or export from your existing task tool. If your tasks live in your head, write them somewhere first, because this build cannot fix that. - Verify: Tasks import with stable identifiers - [ ] **A commitment to two weeks of honest use** · free, but it takes two weeks - Why: Estimate accuracy needs a sample, and the shutdown ritual only shows its value once you have moved the same task three times and had to admit it. - Get it: Decide to run it for two weeks with real work before judging it, and record actual time even on the days you forget the timer. - Verify: Two weeks of plans exist with actual times recorded - [ ] **A neutral wording pass on the reports** · free - Why: A planning tool that reads as criticism gets abandoned, and you cannot audit your own wording because you know what you meant. - Get it: Ask someone to read the report strings and say whether any of it reads as judgement. - Verify: A second reader's notes are recorded ## 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: - The integration breadth of the hosted product. - Writing time blocks back to your calendar. - The accumulated ritual design that makes the paid product work. - Mobile applications. - integration breadth - calendar two-way sync - daily ritual design - mobile apps - analytics - reliability If one of those is essential to you, that is the reason to keep paying for Sunsama, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Sunsama 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 a daily planning ritual: pull work in, estimate it, and shut down honestly at the end to replace Sunsama. 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. ### Rule zero This is deliberately manual. The value is not that software arranges your day, it is that you choose what goes into it and confront how much time you actually have. An automatic scheduler produces a full calendar and no reckoning; a planning ritual produces a shorter list and a decision. So the application never places work for you. It shows the capacity, it shows what you estimated against what happened, and it makes the end of the day a moment where unfinished work is moved deliberately rather than rolled over silently. If you find yourself building a solver, you are building something else. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a small server and SQLite · one person's planning data, where the interesting part is the ritual rather than the scale - Calendar: Read-only calendar access for real availability · planned hours against available hours is the whole point and needs the real calendar - Sources: Task import from where your work already lives · retyping work into a planner is why planners get abandoned - Time: A library with full zone data · a day boundary is local, and a plan that rolls over at the wrong hour is confusing daily - Storage: Local first with export · your planning history is a record of how you work and belongs to you ### Data model (create this before Phase 1) - tasks(id, source, external_id, title, notes, created_at, archived_at, project) - plans(id, on_date, planned_minutes, available_minutes, closed_at, reflection) - plan_items(id, plan_id, task_id, ordinal, estimate_minutes, actual_minutes, state, moved_to_plan_id) · state is planned, done, moved or abandoned - sessions(id, plan_item_id, started_at, ended_at) · time actually spent, which is what makes estimates improve - busy(id, source, starts_at_utc, ends_at_utc) · from your calendar, so availability is real - A task that is not finished is either moved with a decision or abandoned with one. Nothing rolls over silently. ### Phase 1 · Real availability, honestly counted Build: Read busy time from your real calendar and compute available minutes for the day within your working hours. Subtract a buffer you configure for the things that always happen and are never planned. Trap: Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. Done when: Available minutes for today match a hand calculation from your calendar and working hours. A day with back-to-back meetings shows very little available time rather than a full working day. The buffer is configurable and visible. A calendar change updates availability on refresh. Do not build yet: No tasks, planning or review. ### Phase 2 · Pulling work in from where it lives Build: Import tasks from one source you actually use, keeping the external identifier so a task is not duplicated on re-import. Show a backlog to pull from. Marking done locally reflects back to the source where the source supports it. Trap: A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. Done when: Tasks import from a real source and re-importing creates no duplicates. Completing a task locally updates the source, or the interface states clearly that it cannot. A task deleted at the source is marked rather than vanishing from a past plan. The backlog is searchable. Do not build yet: No planning or review yet. ### Phase 3 · Planning the day, with the total in front of you Build: Drag tasks into today and estimate each. Show planned total against available minutes, prominently, updating as you add. Over-commitment is shown as a figure rather than prevented, because sometimes you will do it deliberately. Trap: Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. Done when: Planned total against available minutes is visible and updates as tasks are added. Exceeding available time shows the overage clearly without blocking. Reordering tasks does not change the total. An unestimated task is flagged rather than counted as zero. Do not build yet: No tracking or shutdown yet. ### Phase 4 · Working, and recording what it took Build: Start and stop a task with one action, recording sessions. Allow entering time afterwards, because you will forget to start the timer. Compare estimate against actual per task without commentary. Trap: A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. Done when: Starting and stopping a task records a session. Entering time afterwards is as few actions as using the timer. Estimate against actual is shown per task with no judgement language. A task worked across two sessions sums correctly. Do not build yet: No shutdown or accuracy tracking yet. ### Phase 5 · The shutdown, which is the point Build: A closing flow listing everything unfinished and requiring a decision for each: move to a specific day, or abandon with a reason. Nothing rolls over automatically. Capture a short reflection and close the day. Trap: Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. Done when: Closing the day requires a decision on every unfinished item. Abandoning is one action and records a reason. A closed day cannot be silently edited afterwards. A task moved three times is flagged, because that pattern is information. Do not build yet: Accuracy tracking and export come last. ### Phase 6 · Estimate accuracy and export Build: Report estimate against actual over time, by project and by task size. Show the systematic bias rather than individual misses. Export everything. Keep the language neutral, since consistently underestimating is normal rather than a failing. Trap: Reporting individual misses invites self-criticism and teaches nothing, because any single estimate can be wrong for good reasons. The useful figure is the systematic bias across many tasks, which is stable, actionable and impersonal. Done when: A report shows the ratio of actual to estimated across many tasks, broken down by project and size. The report uses neutral language with no shaming terms, checked against a banned word list. Export produces plans, tasks and sessions as JSON and CSV. At least thirty completed tasks are required before a bias figure is shown. Do not build yet: Nothing after this. ### Out of scope (and why) - The integration breadth of the hosted product, which is the reason it fits into existing workflows without friction. - Two-way calendar sync writing blocks back, which is deliberately not done here because placing work is the thing this build refuses. - The daily ritual design itself, which is the accumulated product thinking and is most of what the subscription buys. - Mobile applications, which matter for a tool consulted through the day. ### README must contain - That the application never places work for you, and why that is the point - That available time subtracts a buffer, and how to tune it from your own history - That nothing rolls over automatically and every unfinished task needs a decision - That estimate accuracy is reported as systematic bias rather than individual misses - That the calendar is read only and no blocks are ever written ===== AGENTS.md ===== # Agent instructions · Sunsama indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a small server and SQLite, Read-only calendar access for real availability, Task import from where your work already lives, A library with full zone data, Local first with export. 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 - Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. - A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. - Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. - A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. - Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. ===== BUILD_PLAN.md ===== # Build plan · Sunsama A day view where you pull tasks from your lists and calendar into today, estimate each one, watch the planned total against the hours actually free, work through them, and close the day by deciding explicitly what moves and what was abandoned, with estimate accuracy tracked so the numbers get better. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Real availability, honestly counted The application knows how many hours you actually have today. ### Steps 1. Read the calendar read-only Busy time only. This application never writes to your calendar, which is a deliberate difference. Files: `src/calendar.ts` 2. Compute available minutes Working hours minus busy time minus buffer, in your zone. Files: `src/availability.ts` 3. Make the buffer visible Shown, not hidden in settings. It is the number that makes the capacity honest. Files: `src/ui/Capacity.tsx` ### Done when - [ ] Available minutes match a hand calculation - [ ] A meeting-heavy day shows little available time - [ ] The buffer is configurable and visible - [ ] A calendar change updates availability ### Watch out - Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. ## Phase 2 · Pulling work in from where it lives You plan from your real tasks rather than retyping them. ### Steps 1. Import with stable identifiers Source plus external identifier, unique. Re-import is then a no-op. Files: `src/import.ts` 2. Reflect completion where possible And say clearly where it is not possible, rather than diverging quietly. Files: `src/sync.ts` 3. Preserve history A task deleted at the source stays on the plans it was on. History is not editable by a later deletion. Files: `src/tasks.ts` ### Done when - [ ] Re-importing creates no duplicates - [ ] Completion reflects back, or the limitation is stated - [ ] A deleted source task stays on past plans - [ ] The backlog is searchable ### Watch out - A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. ## Phase 3 · Planning the day, with the total in front of you You cannot plan eleven hours of work into a six hour day without noticing. ### Steps 1. Build the day view Pull from backlog, order, estimate. Keyboard reachable, because this happens every morning. Files: `src/day.ts` 2. Show the total continuously Planned against available, updating live. Prominent rather than tucked away. Files: `src/ui/DayTotal.tsx` 3. Flag unestimated tasks Counting them as zero makes the total a lie. Flag and ask. Files: `src/day.ts` ### Done when - [ ] The planned against available total updates live - [ ] Over-commitment shows the overage without blocking - [ ] Reordering does not change the total - [ ] An unestimated task is flagged rather than zeroed ### Watch out - Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. ## Phase 4 · Working, and recording what it took Actual time is captured with enough ease that it actually gets captured. ### Steps 1. One action to start and stop From the day view, keyboard reachable. Files: `src/timer.ts` 2. Make retrospective entry equal Same number of actions. This is the path most days will take. Files: `src/timer.ts` 3. Show estimate against actual plainly A pair of numbers. No commentary, because the number is the information. Files: `src/ui/TaskRow.tsx` ### Done when - [ ] Start and stop records a session - [ ] Retrospective entry is as easy as the timer - [ ] Estimate and actual are shown without judgement - [ ] Two sessions on one task sum correctly ### Watch out - A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. ## Phase 5 · The shutdown, which is the point The day ends with a decision about every unfinished thing. ### Steps 1. Require a decision per item Move to a named day, or abandon with a reason. No default and no rollover. Files: `src/shutdown.ts` 2. Make abandoning easy Same effort as moving. It is frequently the correct choice and should not feel like failure. Files: `src/shutdown.ts` 3. Flag repeated moves A task moved several times is telling you something. Surface it without a verdict. Files: `src/shutdown.ts` ### Done when - [ ] Closing requires a decision on every unfinished item - [ ] Abandoning is one action with a recorded reason - [ ] A closed day is not silently editable - [ ] A repeatedly moved task is flagged ### Watch out - Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. ## Not in this build - The integration breadth of the hosted product. - Writing time blocks back to your calendar. - The accumulated ritual design that makes the paid product work. - Mobile applications. ## After v1, if you want it - A weekly review reading back the fortnight's decisions rather than its completions - Per-project time totals, which most freelancers need for invoicing anyway - An estimate suggestion from your own history for a task that resembles a previous one ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Plans, tasks and sessions. A record of how you work, so treat it accordingly. DB_PATH=./data/plans.db # Required. Local wall clock, in your zone. WORKING_HOURS=09:00-17:30 # Required. The zone working hours and day boundaries are expressed in. TIMEZONE=Europe/London # Required. Subtracted from available time for the things that always happen. Tune it from your own history. DAILY_BUFFER_MINUTES=90 # Required. Completed tasks required before a bias figure is shown. ACCURACY_MIN_SAMPLE=30 # Required. Times a task can be moved before it is flagged, without a verdict attached. MOVE_FLAG_THRESHOLD=3
You are building a production product version of Sunsama. 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 ===== # Sunsama · product brief ## Problem A daily planning dashboard is not hard, but Sunsama's value is many integrations, careful UX, calendar sync, and habit-forming workflow. ## Product outcome A planning ritual that shows you honestly how much time you have, requires a decision about everything you did not finish, and tells you how far off your estimates run without making it a criticism. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Google/Microsoft calendar OAuth - task-tool APIs - hosted backend - auth - notification/email channel ## Explicit non-goals for v1 - The integration breadth of the hosted product. - Writing time blocks back to your calendar. - The accumulated ritual design that makes the paid product work. - Mobile applications. - integration breadth - calendar two-way sync - daily ritual design - mobile apps - analytics - reliability ## Success criteria - Available minutes match a hand calculation - Re-importing tasks is confirmed to create no duplicates - Closing a day is confirmed to require a decision per item - The bias figure is confirmed to require the sample threshold - The copy lint passes and a second reader has reviewed the report wording ===== BRIEF.md ===== # Build brief · Sunsama 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 a daily planning ritual: pull work in, estimate it, and shut down honestly at the end to replace Sunsama. 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. ### Rule zero This is deliberately manual. The value is not that software arranges your day, it is that you choose what goes into it and confront how much time you actually have. An automatic scheduler produces a full calendar and no reckoning; a planning ritual produces a shorter list and a decision. So the application never places work for you. It shows the capacity, it shows what you estimated against what happened, and it makes the end of the day a moment where unfinished work is moved deliberately rather than rolled over silently. If you find yourself building a solver, you are building something else. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a small server and SQLite · one person's planning data, where the interesting part is the ritual rather than the scale - Calendar: Read-only calendar access for real availability · planned hours against available hours is the whole point and needs the real calendar - Sources: Task import from where your work already lives · retyping work into a planner is why planners get abandoned - Time: A library with full zone data · a day boundary is local, and a plan that rolls over at the wrong hour is confusing daily - Storage: Local first with export · your planning history is a record of how you work and belongs to you ### Data model (create this before Phase 1) - tasks(id, source, external_id, title, notes, created_at, archived_at, project) - plans(id, on_date, planned_minutes, available_minutes, closed_at, reflection) - plan_items(id, plan_id, task_id, ordinal, estimate_minutes, actual_minutes, state, moved_to_plan_id) · state is planned, done, moved or abandoned - sessions(id, plan_item_id, started_at, ended_at) · time actually spent, which is what makes estimates improve - busy(id, source, starts_at_utc, ends_at_utc) · from your calendar, so availability is real - A task that is not finished is either moved with a decision or abandoned with one. Nothing rolls over silently. ### Phase 1 · Real availability, honestly counted Build: Read busy time from your real calendar and compute available minutes for the day within your working hours. Subtract a buffer you configure for the things that always happen and are never planned. Trap: Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. Done when: Available minutes for today match a hand calculation from your calendar and working hours. A day with back-to-back meetings shows very little available time rather than a full working day. The buffer is configurable and visible. A calendar change updates availability on refresh. Do not build yet: No tasks, planning or review. ### Phase 2 · Pulling work in from where it lives Build: Import tasks from one source you actually use, keeping the external identifier so a task is not duplicated on re-import. Show a backlog to pull from. Marking done locally reflects back to the source where the source supports it. Trap: A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. Done when: Tasks import from a real source and re-importing creates no duplicates. Completing a task locally updates the source, or the interface states clearly that it cannot. A task deleted at the source is marked rather than vanishing from a past plan. The backlog is searchable. Do not build yet: No planning or review yet. ### Phase 3 · Planning the day, with the total in front of you Build: Drag tasks into today and estimate each. Show planned total against available minutes, prominently, updating as you add. Over-commitment is shown as a figure rather than prevented, because sometimes you will do it deliberately. Trap: Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. Done when: Planned total against available minutes is visible and updates as tasks are added. Exceeding available time shows the overage clearly without blocking. Reordering tasks does not change the total. An unestimated task is flagged rather than counted as zero. Do not build yet: No tracking or shutdown yet. ### Phase 4 · Working, and recording what it took Build: Start and stop a task with one action, recording sessions. Allow entering time afterwards, because you will forget to start the timer. Compare estimate against actual per task without commentary. Trap: A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. Done when: Starting and stopping a task records a session. Entering time afterwards is as few actions as using the timer. Estimate against actual is shown per task with no judgement language. A task worked across two sessions sums correctly. Do not build yet: No shutdown or accuracy tracking yet. ### Phase 5 · The shutdown, which is the point Build: A closing flow listing everything unfinished and requiring a decision for each: move to a specific day, or abandon with a reason. Nothing rolls over automatically. Capture a short reflection and close the day. Trap: Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. Done when: Closing the day requires a decision on every unfinished item. Abandoning is one action and records a reason. A closed day cannot be silently edited afterwards. A task moved three times is flagged, because that pattern is information. Do not build yet: Accuracy tracking and export come last. ### Phase 6 · Estimate accuracy and export Build: Report estimate against actual over time, by project and by task size. Show the systematic bias rather than individual misses. Export everything. Keep the language neutral, since consistently underestimating is normal rather than a failing. Trap: Reporting individual misses invites self-criticism and teaches nothing, because any single estimate can be wrong for good reasons. The useful figure is the systematic bias across many tasks, which is stable, actionable and impersonal. Done when: A report shows the ratio of actual to estimated across many tasks, broken down by project and size. The report uses neutral language with no shaming terms, checked against a banned word list. Export produces plans, tasks and sessions as JSON and CSV. At least thirty completed tasks are required before a bias figure is shown. Do not build yet: Nothing after this. ### Out of scope (and why) - The integration breadth of the hosted product, which is the reason it fits into existing workflows without friction. - Two-way calendar sync writing blocks back, which is deliberately not done here because placing work is the thing this build refuses. - The daily ritual design itself, which is the accumulated product thinking and is most of what the subscription buys. - Mobile applications, which matter for a tool consulted through the day. ### README must contain - That the application never places work for you, and why that is the point - That available time subtracts a buffer, and how to tune it from your own history - That nothing rolls over automatically and every unfinished task needs a decision - That estimate accuracy is reported as systematic bias rather than individual misses - That the calendar is read only and no blocks are ever written ===== ARCHITECTURE.md ===== # Architecture · Sunsama ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with a small server and SQLite | one person's planning data, where the interesting part is the ritual rather than the scale | | Calendar | Read-only calendar access for real availability | planned hours against available hours is the whole point and needs the real calendar | | Sources | Task import from where your work already lives | retyping work into a planner is why planners get abandoned | | Time | A library with full zone data | a day boundary is local, and a plan that rolls over at the wrong hour is confusing daily | | Storage | Local first with export | your planning history is a record of how you work and belongs to you | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Availability | Real capacity from a read-only calendar. | Any calendar source. Read-only and never writing back is the deliberate constraint. | | Import | Tasks from where they already live. | Any source with stable identifiers. Divergence between two lists is the failure to avoid. | | Day | Planning and the visible total. | Presentation, provided the total stays continuously visible. | | Shutdown | Requiring a decision per unfinished item. | Not swappable for automatic rollover. That is the feature this build exists to refuse. | | Accuracy | Systematic bias over time. | Any statistic, provided it stays impersonal and above a sample threshold. | ## 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 · Plans, tasks and sessions. A record of how you work, so treat it accordingly. - `WORKING_HOURS` · required · Local wall clock, in your zone. - `TIMEZONE` · required · The zone working hours and day boundaries are expressed in. - `DAILY_BUFFER_MINUTES` · required · Subtracted from available time for the things that always happen. Tune it from your own history. - `ACCURACY_MIN_SAMPLE` · required · Completed tasks required before a bias figure is shown. - `MOVE_FLAG_THRESHOLD` · required · Times a task can be moved before it is flagged, without a verdict attached. ## 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 · Sunsama product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with a small server and SQLite, Read-only calendar access for real availability, Task import from where your work already lives, A library with full zone data, Local first with export. - 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 - Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. - A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. - Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. - A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. - Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. - Reporting individual misses invites self-criticism and teaches nothing, because any single estimate can be wrong for good reasons. The useful figure is the systematic bias across many tasks, which is stable, actionable and impersonal. ===== MILESTONES.md ===== # Delivery milestones · Sunsama Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Real availability, honestly counted The application knows how many hours you actually have today. ### Steps 1. Read the calendar read-only Busy time only. This application never writes to your calendar, which is a deliberate difference. Files: `src/calendar.ts` 2. Compute available minutes Working hours minus busy time minus buffer, in your zone. Files: `src/availability.ts` 3. Make the buffer visible Shown, not hidden in settings. It is the number that makes the capacity honest. Files: `src/ui/Capacity.tsx` ### Done when - [ ] Available minutes match a hand calculation - [ ] A meeting-heavy day shows little available time - [ ] The buffer is configurable and visible - [ ] A calendar change updates availability ### Watch out - Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. ## M2 · Pulling work in from where it lives You plan from your real tasks rather than retyping them. ### Steps 1. Import with stable identifiers Source plus external identifier, unique. Re-import is then a no-op. Files: `src/import.ts` 2. Reflect completion where possible And say clearly where it is not possible, rather than diverging quietly. Files: `src/sync.ts` 3. Preserve history A task deleted at the source stays on the plans it was on. History is not editable by a later deletion. Files: `src/tasks.ts` ### Done when - [ ] Re-importing creates no duplicates - [ ] Completion reflects back, or the limitation is stated - [ ] A deleted source task stays on past plans - [ ] The backlog is searchable ### Watch out - A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. ## M3 · Planning the day, with the total in front of you You cannot plan eleven hours of work into a six hour day without noticing. ### Steps 1. Build the day view Pull from backlog, order, estimate. Keyboard reachable, because this happens every morning. Files: `src/day.ts` 2. Show the total continuously Planned against available, updating live. Prominent rather than tucked away. Files: `src/ui/DayTotal.tsx` 3. Flag unestimated tasks Counting them as zero makes the total a lie. Flag and ask. Files: `src/day.ts` ### Done when - [ ] The planned against available total updates live - [ ] Over-commitment shows the overage without blocking - [ ] Reordering does not change the total - [ ] An unestimated task is flagged rather than zeroed ### Watch out - Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. ## M4 · Working, and recording what it took Actual time is captured with enough ease that it actually gets captured. ### Steps 1. One action to start and stop From the day view, keyboard reachable. Files: `src/timer.ts` 2. Make retrospective entry equal Same number of actions. This is the path most days will take. Files: `src/timer.ts` 3. Show estimate against actual plainly A pair of numbers. No commentary, because the number is the information. Files: `src/ui/TaskRow.tsx` ### Done when - [ ] Start and stop records a session - [ ] Retrospective entry is as easy as the timer - [ ] Estimate and actual are shown without judgement - [ ] Two sessions on one task sum correctly ### Watch out - A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. ## M5 · The shutdown, which is the point The day ends with a decision about every unfinished thing. ### Steps 1. Require a decision per item Move to a named day, or abandon with a reason. No default and no rollover. Files: `src/shutdown.ts` 2. Make abandoning easy Same effort as moving. It is frequently the correct choice and should not feel like failure. Files: `src/shutdown.ts` 3. Flag repeated moves A task moved several times is telling you something. Surface it without a verdict. Files: `src/shutdown.ts` ### Done when - [ ] Closing requires a decision on every unfinished item - [ ] Abandoning is one action with a recorded reason - [ ] A closed day is not silently editable - [ ] A repeatedly moved task is flagged ### Watch out - Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. ## M6 · Estimate accuracy and export (production only) Your estimates improve because you can see how wrong they have been. ### Steps 1. Report systematic bias Across many tasks, by project and size. Not individual misses. Files: `src/accuracy.ts` 2. Require a sample size Below it, no figure. A ratio from four tasks is noise presented as insight. Files: `src/accuracy.ts` 3. Keep the language neutral A banned word list checked at build. Underestimating is normal and the tool should say so. Files: `scripts/copy-lint.ts` ### Done when - [ ] The bias figure appears only above the sample threshold - [ ] The report breaks down by project and size - [ ] The copy lint passes and fails on a planted word - [ ] Export produces plans, tasks and sessions ### Watch out - Reporting individual misses invites self-criticism and teaches nothing, because any single estimate can be wrong for good reasons. The useful figure is the systematic bias across many tasks, which is stable, actionable and impersonal. ===== OPERATIONS.md ===== # Operations · Sunsama ## Backup The database is a record of how you spend your time, which is small and slowly accumulated. Export regularly, since it is not reconstructable. ## Restore Restore and open a past plan. The history is the value here rather than the current day, which you could rebuild in ten minutes. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Nothing runs as a service beyond calendar and task sync. Worth watching is sync failure, since a stale calendar produces an availability figure that is confidently wrong. ## Incident checklist If availability looks wrong, check calendar sync freshness before the buffer. If tasks duplicate, the external identifier is not stable at the source, which is worth fixing at the import rather than deduplicating downstream. 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 - [ ] Available minutes match a hand calculation - [ ] Re-importing tasks is confirmed to create no duplicates - [ ] Closing a day is confirmed to require a decision per item - [ ] The bias figure is confirmed to require the sample threshold - [ ] The copy lint passes and a second reader has reviewed the report wording ## Launch constraint Do not market omitted Sunsama 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. Plans, tasks and sessions. A record of how you work, so treat it accordingly. DB_PATH=./data/plans.db # Required. Local wall clock, in your zone. WORKING_HOURS=09:00-17:30 # Required. The zone working hours and day boundaries are expressed in. TIMEZONE=Europe/London # Required. Subtracted from available time for the things that always happen. Tune it from your own history. DAILY_BUFFER_MINUTES=90 # Required. Completed tasks required before a bias figure is shown. ACCURACY_MIN_SAMPLE=30 # Required. Times a task can be moved before it is flagged, without a verdict attached. MOVE_FLAG_THRESHOLD=3
# Sunsama · indie build A day view where you pull tasks from your lists and calendar into today, estimate each one, watch the planned total against the hours actually free, work through them, and close the day by deciding explicitly what moves and what was abandoned, with estimate accuracy tracked so the numbers get better. Estimated effort: **multi-day**. 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 | | --- | --- | --- | | Runtime | TypeScript with a small server and SQLite | one person's planning data, where the interesting part is the ritual rather than the scale | | Calendar | Read-only calendar access for real availability | planned hours against available hours is the whole point and needs the real calendar | | Sources | Task import from where your work already lives | retyping work into a planner is why planners get abandoned | | Time | A library with full zone data | a day boundary is local, and a plan that rolls over at the wrong hour is confusing daily | | Storage | Local first with export | your planning history is a record of how you work and belongs to you | ## 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 application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Read-only access to your real calendar** · free with an existing account - Why: Available time is the number the whole ritual rests on, and a test calendar gives you a number that means nothing. - Get it: Connect with read-only scopes, which is all this needs and all it should request. - Verify: Busy time appears and matches your actual day - [ ] **Access to where your tasks already live** · free for most personal task tools - Why: Retyping work into a planner is why planners get abandoned, usually in week two. - Get it: An API token or export from your existing task tool. If your tasks live in your head, write them somewhere first, because this build cannot fix that. - Verify: Tasks import with stable identifiers - [ ] **A commitment to two weeks of honest use** · free, but it takes two weeks - Why: Estimate accuracy needs a sample, and the shutdown ritual only shows its value once you have moved the same task three times and had to admit it. - Get it: Decide to run it for two weeks with real work before judging it, and record actual time even on the days you forget the timer. - Verify: Two weeks of plans exist with actual times recorded - [ ] **A neutral wording pass on the reports** · free - Why: A planning tool that reads as criticism gets abandoned, and you cannot audit your own wording because you know what you meant. - Get it: Ask someone to read the report strings and say whether any of it reads as judgement. - Verify: A second reader's notes are recorded ## 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: - The integration breadth of the hosted product. - Writing time blocks back to your calendar. - The accumulated ritual design that makes the paid product work. - Mobile applications. - integration breadth - calendar two-way sync - daily ritual design - mobile apps - analytics - reliability If one of those is essential to you, that is the reason to keep paying for Sunsama, and the README should say so rather than pretend.
# Build brief · Sunsama 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 a daily planning ritual: pull work in, estimate it, and shut down honestly at the end to replace Sunsama. 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. ### Rule zero This is deliberately manual. The value is not that software arranges your day, it is that you choose what goes into it and confront how much time you actually have. An automatic scheduler produces a full calendar and no reckoning; a planning ritual produces a shorter list and a decision. So the application never places work for you. It shows the capacity, it shows what you estimated against what happened, and it makes the end of the day a moment where unfinished work is moved deliberately rather than rolled over silently. If you find yourself building a solver, you are building something else. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a small server and SQLite · one person's planning data, where the interesting part is the ritual rather than the scale - Calendar: Read-only calendar access for real availability · planned hours against available hours is the whole point and needs the real calendar - Sources: Task import from where your work already lives · retyping work into a planner is why planners get abandoned - Time: A library with full zone data · a day boundary is local, and a plan that rolls over at the wrong hour is confusing daily - Storage: Local first with export · your planning history is a record of how you work and belongs to you ### Data model (create this before Phase 1) - tasks(id, source, external_id, title, notes, created_at, archived_at, project) - plans(id, on_date, planned_minutes, available_minutes, closed_at, reflection) - plan_items(id, plan_id, task_id, ordinal, estimate_minutes, actual_minutes, state, moved_to_plan_id) · state is planned, done, moved or abandoned - sessions(id, plan_item_id, started_at, ended_at) · time actually spent, which is what makes estimates improve - busy(id, source, starts_at_utc, ends_at_utc) · from your calendar, so availability is real - A task that is not finished is either moved with a decision or abandoned with one. Nothing rolls over silently. ### Phase 1 · Real availability, honestly counted Build: Read busy time from your real calendar and compute available minutes for the day within your working hours. Subtract a buffer you configure for the things that always happen and are never planned. Trap: Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. Done when: Available minutes for today match a hand calculation from your calendar and working hours. A day with back-to-back meetings shows very little available time rather than a full working day. The buffer is configurable and visible. A calendar change updates availability on refresh. Do not build yet: No tasks, planning or review. ### Phase 2 · Pulling work in from where it lives Build: Import tasks from one source you actually use, keeping the external identifier so a task is not duplicated on re-import. Show a backlog to pull from. Marking done locally reflects back to the source where the source supports it. Trap: A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. Done when: Tasks import from a real source and re-importing creates no duplicates. Completing a task locally updates the source, or the interface states clearly that it cannot. A task deleted at the source is marked rather than vanishing from a past plan. The backlog is searchable. Do not build yet: No planning or review yet. ### Phase 3 · Planning the day, with the total in front of you Build: Drag tasks into today and estimate each. Show planned total against available minutes, prominently, updating as you add. Over-commitment is shown as a figure rather than prevented, because sometimes you will do it deliberately. Trap: Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. Done when: Planned total against available minutes is visible and updates as tasks are added. Exceeding available time shows the overage clearly without blocking. Reordering tasks does not change the total. An unestimated task is flagged rather than counted as zero. Do not build yet: No tracking or shutdown yet. ### Phase 4 · Working, and recording what it took Build: Start and stop a task with one action, recording sessions. Allow entering time afterwards, because you will forget to start the timer. Compare estimate against actual per task without commentary. Trap: A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. Done when: Starting and stopping a task records a session. Entering time afterwards is as few actions as using the timer. Estimate against actual is shown per task with no judgement language. A task worked across two sessions sums correctly. Do not build yet: No shutdown or accuracy tracking yet. ### Phase 5 · The shutdown, which is the point Build: A closing flow listing everything unfinished and requiring a decision for each: move to a specific day, or abandon with a reason. Nothing rolls over automatically. Capture a short reflection and close the day. Trap: Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. Done when: Closing the day requires a decision on every unfinished item. Abandoning is one action and records a reason. A closed day cannot be silently edited afterwards. A task moved three times is flagged, because that pattern is information. Do not build yet: Accuracy tracking and export come last. ### Phase 6 · Estimate accuracy and export Build: Report estimate against actual over time, by project and by task size. Show the systematic bias rather than individual misses. Export everything. Keep the language neutral, since consistently underestimating is normal rather than a failing. Trap: Reporting individual misses invites self-criticism and teaches nothing, because any single estimate can be wrong for good reasons. The useful figure is the systematic bias across many tasks, which is stable, actionable and impersonal. Done when: A report shows the ratio of actual to estimated across many tasks, broken down by project and size. The report uses neutral language with no shaming terms, checked against a banned word list. Export produces plans, tasks and sessions as JSON and CSV. At least thirty completed tasks are required before a bias figure is shown. Do not build yet: Nothing after this. ### Out of scope (and why) - The integration breadth of the hosted product, which is the reason it fits into existing workflows without friction. - Two-way calendar sync writing blocks back, which is deliberately not done here because placing work is the thing this build refuses. - The daily ritual design itself, which is the accumulated product thinking and is most of what the subscription buys. - Mobile applications, which matter for a tool consulted through the day. ### README must contain - That the application never places work for you, and why that is the point - That available time subtracts a buffer, and how to tune it from your own history - That nothing rolls over automatically and every unfinished task needs a decision - That estimate accuracy is reported as systematic bias rather than individual misses - That the calendar is read only and no blocks are ever written
# Agent instructions · Sunsama indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a small server and SQLite, Read-only calendar access for real availability, Task import from where your work already lives, A library with full zone data, Local first with export. 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 - Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. - A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. - Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. - A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. - Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer.
# Build plan · Sunsama A day view where you pull tasks from your lists and calendar into today, estimate each one, watch the planned total against the hours actually free, work through them, and close the day by deciding explicitly what moves and what was abandoned, with estimate accuracy tracked so the numbers get better. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Real availability, honestly counted The application knows how many hours you actually have today. ### Steps 1. Read the calendar read-only Busy time only. This application never writes to your calendar, which is a deliberate difference. Files: `src/calendar.ts` 2. Compute available minutes Working hours minus busy time minus buffer, in your zone. Files: `src/availability.ts` 3. Make the buffer visible Shown, not hidden in settings. It is the number that makes the capacity honest. Files: `src/ui/Capacity.tsx` ### Done when - [ ] Available minutes match a hand calculation - [ ] A meeting-heavy day shows little available time - [ ] The buffer is configurable and visible - [ ] A calendar change updates availability ### Watch out - Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. ## Phase 2 · Pulling work in from where it lives You plan from your real tasks rather than retyping them. ### Steps 1. Import with stable identifiers Source plus external identifier, unique. Re-import is then a no-op. Files: `src/import.ts` 2. Reflect completion where possible And say clearly where it is not possible, rather than diverging quietly. Files: `src/sync.ts` 3. Preserve history A task deleted at the source stays on the plans it was on. History is not editable by a later deletion. Files: `src/tasks.ts` ### Done when - [ ] Re-importing creates no duplicates - [ ] Completion reflects back, or the limitation is stated - [ ] A deleted source task stays on past plans - [ ] The backlog is searchable ### Watch out - A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. ## Phase 3 · Planning the day, with the total in front of you You cannot plan eleven hours of work into a six hour day without noticing. ### Steps 1. Build the day view Pull from backlog, order, estimate. Keyboard reachable, because this happens every morning. Files: `src/day.ts` 2. Show the total continuously Planned against available, updating live. Prominent rather than tucked away. Files: `src/ui/DayTotal.tsx` 3. Flag unestimated tasks Counting them as zero makes the total a lie. Flag and ask. Files: `src/day.ts` ### Done when - [ ] The planned against available total updates live - [ ] Over-commitment shows the overage without blocking - [ ] Reordering does not change the total - [ ] An unestimated task is flagged rather than zeroed ### Watch out - Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. ## Phase 4 · Working, and recording what it took Actual time is captured with enough ease that it actually gets captured. ### Steps 1. One action to start and stop From the day view, keyboard reachable. Files: `src/timer.ts` 2. Make retrospective entry equal Same number of actions. This is the path most days will take. Files: `src/timer.ts` 3. Show estimate against actual plainly A pair of numbers. No commentary, because the number is the information. Files: `src/ui/TaskRow.tsx` ### Done when - [ ] Start and stop records a session - [ ] Retrospective entry is as easy as the timer - [ ] Estimate and actual are shown without judgement - [ ] Two sessions on one task sum correctly ### Watch out - A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. ## Phase 5 · The shutdown, which is the point The day ends with a decision about every unfinished thing. ### Steps 1. Require a decision per item Move to a named day, or abandon with a reason. No default and no rollover. Files: `src/shutdown.ts` 2. Make abandoning easy Same effort as moving. It is frequently the correct choice and should not feel like failure. Files: `src/shutdown.ts` 3. Flag repeated moves A task moved several times is telling you something. Surface it without a verdict. Files: `src/shutdown.ts` ### Done when - [ ] Closing requires a decision on every unfinished item - [ ] Abandoning is one action with a recorded reason - [ ] A closed day is not silently editable - [ ] A repeatedly moved task is flagged ### Watch out - Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. ## Not in this build - The integration breadth of the hosted product. - Writing time blocks back to your calendar. - The accumulated ritual design that makes the paid product work. - Mobile applications. ## After v1, if you want it - A weekly review reading back the fortnight's decisions rather than its completions - Per-project time totals, which most freelancers need for invoicing anyway - An estimate suggestion from your own history for a task that resembles a previous one
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Plans, tasks and sessions. A record of how you work, so treat it accordingly. DB_PATH=./data/plans.db # Required. Local wall clock, in your zone. WORKING_HOURS=09:00-17:30 # Required. The zone working hours and day boundaries are expressed in. TIMEZONE=Europe/London # Required. Subtracted from available time for the things that always happen. Tune it from your own history. DAILY_BUFFER_MINUTES=90 # Required. Completed tasks required before a bias figure is shown. ACCURACY_MIN_SAMPLE=30 # Required. Times a task can be moved before it is flagged, without a verdict attached. MOVE_FLAG_THRESHOLD=3
# Sunsama · product brief ## Problem A daily planning dashboard is not hard, but Sunsama's value is many integrations, careful UX, calendar sync, and habit-forming workflow. ## Product outcome A planning ritual that shows you honestly how much time you have, requires a decision about everything you did not finish, and tells you how far off your estimates run without making it a criticism. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Google/Microsoft calendar OAuth - task-tool APIs - hosted backend - auth - notification/email channel ## Explicit non-goals for v1 - The integration breadth of the hosted product. - Writing time blocks back to your calendar. - The accumulated ritual design that makes the paid product work. - Mobile applications. - integration breadth - calendar two-way sync - daily ritual design - mobile apps - analytics - reliability ## Success criteria - Available minutes match a hand calculation - Re-importing tasks is confirmed to create no duplicates - Closing a day is confirmed to require a decision per item - The bias figure is confirmed to require the sample threshold - The copy lint passes and a second reader has reviewed the report wording
# Build brief · Sunsama 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 a daily planning ritual: pull work in, estimate it, and shut down honestly at the end to replace Sunsama. 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. ### Rule zero This is deliberately manual. The value is not that software arranges your day, it is that you choose what goes into it and confront how much time you actually have. An automatic scheduler produces a full calendar and no reckoning; a planning ritual produces a shorter list and a decision. So the application never places work for you. It shows the capacity, it shows what you estimated against what happened, and it makes the end of the day a moment where unfinished work is moved deliberately rather than rolled over silently. If you find yourself building a solver, you are building something else. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a small server and SQLite · one person's planning data, where the interesting part is the ritual rather than the scale - Calendar: Read-only calendar access for real availability · planned hours against available hours is the whole point and needs the real calendar - Sources: Task import from where your work already lives · retyping work into a planner is why planners get abandoned - Time: A library with full zone data · a day boundary is local, and a plan that rolls over at the wrong hour is confusing daily - Storage: Local first with export · your planning history is a record of how you work and belongs to you ### Data model (create this before Phase 1) - tasks(id, source, external_id, title, notes, created_at, archived_at, project) - plans(id, on_date, planned_minutes, available_minutes, closed_at, reflection) - plan_items(id, plan_id, task_id, ordinal, estimate_minutes, actual_minutes, state, moved_to_plan_id) · state is planned, done, moved or abandoned - sessions(id, plan_item_id, started_at, ended_at) · time actually spent, which is what makes estimates improve - busy(id, source, starts_at_utc, ends_at_utc) · from your calendar, so availability is real - A task that is not finished is either moved with a decision or abandoned with one. Nothing rolls over silently. ### Phase 1 · Real availability, honestly counted Build: Read busy time from your real calendar and compute available minutes for the day within your working hours. Subtract a buffer you configure for the things that always happen and are never planned. Trap: Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. Done when: Available minutes for today match a hand calculation from your calendar and working hours. A day with back-to-back meetings shows very little available time rather than a full working day. The buffer is configurable and visible. A calendar change updates availability on refresh. Do not build yet: No tasks, planning or review. ### Phase 2 · Pulling work in from where it lives Build: Import tasks from one source you actually use, keeping the external identifier so a task is not duplicated on re-import. Show a backlog to pull from. Marking done locally reflects back to the source where the source supports it. Trap: A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. Done when: Tasks import from a real source and re-importing creates no duplicates. Completing a task locally updates the source, or the interface states clearly that it cannot. A task deleted at the source is marked rather than vanishing from a past plan. The backlog is searchable. Do not build yet: No planning or review yet. ### Phase 3 · Planning the day, with the total in front of you Build: Drag tasks into today and estimate each. Show planned total against available minutes, prominently, updating as you add. Over-commitment is shown as a figure rather than prevented, because sometimes you will do it deliberately. Trap: Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. Done when: Planned total against available minutes is visible and updates as tasks are added. Exceeding available time shows the overage clearly without blocking. Reordering tasks does not change the total. An unestimated task is flagged rather than counted as zero. Do not build yet: No tracking or shutdown yet. ### Phase 4 · Working, and recording what it took Build: Start and stop a task with one action, recording sessions. Allow entering time afterwards, because you will forget to start the timer. Compare estimate against actual per task without commentary. Trap: A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. Done when: Starting and stopping a task records a session. Entering time afterwards is as few actions as using the timer. Estimate against actual is shown per task with no judgement language. A task worked across two sessions sums correctly. Do not build yet: No shutdown or accuracy tracking yet. ### Phase 5 · The shutdown, which is the point Build: A closing flow listing everything unfinished and requiring a decision for each: move to a specific day, or abandon with a reason. Nothing rolls over automatically. Capture a short reflection and close the day. Trap: Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. Done when: Closing the day requires a decision on every unfinished item. Abandoning is one action and records a reason. A closed day cannot be silently edited afterwards. A task moved three times is flagged, because that pattern is information. Do not build yet: Accuracy tracking and export come last. ### Phase 6 · Estimate accuracy and export Build: Report estimate against actual over time, by project and by task size. Show the systematic bias rather than individual misses. Export everything. Keep the language neutral, since consistently underestimating is normal rather than a failing. Trap: Reporting individual misses invites self-criticism and teaches nothing, because any single estimate can be wrong for good reasons. The useful figure is the systematic bias across many tasks, which is stable, actionable and impersonal. Done when: A report shows the ratio of actual to estimated across many tasks, broken down by project and size. The report uses neutral language with no shaming terms, checked against a banned word list. Export produces plans, tasks and sessions as JSON and CSV. At least thirty completed tasks are required before a bias figure is shown. Do not build yet: Nothing after this. ### Out of scope (and why) - The integration breadth of the hosted product, which is the reason it fits into existing workflows without friction. - Two-way calendar sync writing blocks back, which is deliberately not done here because placing work is the thing this build refuses. - The daily ritual design itself, which is the accumulated product thinking and is most of what the subscription buys. - Mobile applications, which matter for a tool consulted through the day. ### README must contain - That the application never places work for you, and why that is the point - That available time subtracts a buffer, and how to tune it from your own history - That nothing rolls over automatically and every unfinished task needs a decision - That estimate accuracy is reported as systematic bias rather than individual misses - That the calendar is read only and no blocks are ever written
# Architecture · Sunsama ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with a small server and SQLite | one person's planning data, where the interesting part is the ritual rather than the scale | | Calendar | Read-only calendar access for real availability | planned hours against available hours is the whole point and needs the real calendar | | Sources | Task import from where your work already lives | retyping work into a planner is why planners get abandoned | | Time | A library with full zone data | a day boundary is local, and a plan that rolls over at the wrong hour is confusing daily | | Storage | Local first with export | your planning history is a record of how you work and belongs to you | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Availability | Real capacity from a read-only calendar. | Any calendar source. Read-only and never writing back is the deliberate constraint. | | Import | Tasks from where they already live. | Any source with stable identifiers. Divergence between two lists is the failure to avoid. | | Day | Planning and the visible total. | Presentation, provided the total stays continuously visible. | | Shutdown | Requiring a decision per unfinished item. | Not swappable for automatic rollover. That is the feature this build exists to refuse. | | Accuracy | Systematic bias over time. | Any statistic, provided it stays impersonal and above a sample threshold. | ## 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 · Plans, tasks and sessions. A record of how you work, so treat it accordingly. - `WORKING_HOURS` · required · Local wall clock, in your zone. - `TIMEZONE` · required · The zone working hours and day boundaries are expressed in. - `DAILY_BUFFER_MINUTES` · required · Subtracted from available time for the things that always happen. Tune it from your own history. - `ACCURACY_MIN_SAMPLE` · required · Completed tasks required before a bias figure is shown. - `MOVE_FLAG_THRESHOLD` · required · Times a task can be moved before it is flagged, without a verdict attached. ## 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 · Sunsama product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with a small server and SQLite, Read-only calendar access for real availability, Task import from where your work already lives, A library with full zone data, Local first with export. - 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 - Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. - A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. - Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. - A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. - Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. - Reporting individual misses invites self-criticism and teaches nothing, because any single estimate can be wrong for good reasons. The useful figure is the systematic bias across many tasks, which is stable, actionable and impersonal.
# Delivery milestones · Sunsama Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Real availability, honestly counted The application knows how many hours you actually have today. ### Steps 1. Read the calendar read-only Busy time only. This application never writes to your calendar, which is a deliberate difference. Files: `src/calendar.ts` 2. Compute available minutes Working hours minus busy time minus buffer, in your zone. Files: `src/availability.ts` 3. Make the buffer visible Shown, not hidden in settings. It is the number that makes the capacity honest. Files: `src/ui/Capacity.tsx` ### Done when - [ ] Available minutes match a hand calculation - [ ] A meeting-heavy day shows little available time - [ ] The buffer is configurable and visible - [ ] A calendar change updates availability ### Watch out - Counting every non-meeting minute as available produces a capacity figure nobody can hit, and a planner that is wrong on the first number is a planner people stop trusting. Subtract a realistic buffer for interruptions and admin, and let the user tune it from the gap between planned and actual over time. ## M2 · Pulling work in from where it lives You plan from your real tasks rather than retyping them. ### Steps 1. Import with stable identifiers Source plus external identifier, unique. Re-import is then a no-op. Files: `src/import.ts` 2. Reflect completion where possible And say clearly where it is not possible, rather than diverging quietly. Files: `src/sync.ts` 3. Preserve history A task deleted at the source stays on the plans it was on. History is not editable by a later deletion. Files: `src/tasks.ts` ### Done when - [ ] Re-importing creates no duplicates - [ ] Completion reflects back, or the limitation is stated - [ ] A deleted source task stays on past plans - [ ] The backlog is searchable ### Watch out - A planner that requires you to retype your work becomes a second list that diverges from the first within a week, and then neither is trusted. Import with stable identifiers, and if the source cannot accept a completion back, say so rather than letting the two lists silently disagree. ## M3 · Planning the day, with the total in front of you You cannot plan eleven hours of work into a six hour day without noticing. ### Steps 1. Build the day view Pull from backlog, order, estimate. Keyboard reachable, because this happens every morning. Files: `src/day.ts` 2. Show the total continuously Planned against available, updating live. Prominent rather than tucked away. Files: `src/ui/DayTotal.tsx` 3. Flag unestimated tasks Counting them as zero makes the total a lie. Flag and ask. Files: `src/day.ts` ### Done when - [ ] The planned against available total updates live - [ ] Over-commitment shows the overage without blocking - [ ] Reordering does not change the total - [ ] An unestimated task is flagged rather than zeroed ### Watch out - Hiding the total until the end of planning defeats the purpose, because by then you have already committed. Show it continuously and prominently, since the moment of adding the task that tips the day over is the moment the information is useful. ## M4 · Working, and recording what it took Actual time is captured with enough ease that it actually gets captured. ### Steps 1. One action to start and stop From the day view, keyboard reachable. Files: `src/timer.ts` 2. Make retrospective entry equal Same number of actions. This is the path most days will take. Files: `src/timer.ts` 3. Show estimate against actual plainly A pair of numbers. No commentary, because the number is the information. Files: `src/ui/TaskRow.tsx` ### Done when - [ ] Start and stop records a session - [ ] Retrospective entry is as easy as the timer - [ ] Estimate and actual are shown without judgement - [ ] Two sessions on one task sum correctly ### Watch out - A time tracker that only works if you remember to start it captures a partial and misleading record, and the partial record is worse than none because it looks complete. Make after-the-fact entry as easy as the timer, since most days you will use it. ## M5 · The shutdown, which is the point The day ends with a decision about every unfinished thing. ### Steps 1. Require a decision per item Move to a named day, or abandon with a reason. No default and no rollover. Files: `src/shutdown.ts` 2. Make abandoning easy Same effort as moving. It is frequently the correct choice and should not feel like failure. Files: `src/shutdown.ts` 3. Flag repeated moves A task moved several times is telling you something. Surface it without a verdict. Files: `src/shutdown.ts` ### Done when - [ ] Closing requires a decision on every unfinished item - [ ] Abandoning is one action with a recorded reason - [ ] A closed day is not silently editable - [ ] A repeatedly moved task is flagged ### Watch out - Automatic rollover is the feature that quietly destroys this: a task moved silently for three weeks is a task you have decided not to do without ever admitting it, and the list grows until the planner is abandoned. Require the decision, and make abandoning as easy as moving, because it is often the right answer. ## M6 · Estimate accuracy and export (production only) Your estimates improve because you can see how wrong they have been. ### Steps 1. Report systematic bias Across many tasks, by project and size. Not individual misses. Files: `src/accuracy.ts` 2. Require a sample size Below it, no figure. A ratio from four tasks is noise presented as insight. Files: `src/accuracy.ts` 3. Keep the language neutral A banned word list checked at build. Underestimating is normal and the tool should say so. Files: `scripts/copy-lint.ts` ### Done when - [ ] The bias figure appears only above the sample threshold - [ ] The report breaks down by project and size - [ ] The copy lint passes and fails on a planted word - [ ] Export produces plans, tasks and sessions ### Watch out - Reporting individual misses invites self-criticism and teaches nothing, because any single estimate can be wrong for good reasons. The useful figure is the systematic bias across many tasks, which is stable, actionable and impersonal.
# Operations · Sunsama ## Backup The database is a record of how you spend your time, which is small and slowly accumulated. Export regularly, since it is not reconstructable. ## Restore Restore and open a past plan. The history is the value here rather than the current day, which you could rebuild in ten minutes. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Nothing runs as a service beyond calendar and task sync. Worth watching is sync failure, since a stale calendar produces an availability figure that is confidently wrong. ## Incident checklist If availability looks wrong, check calendar sync freshness before the buffer. If tasks duplicate, the external identifier is not stable at the source, which is worth fixing at the import rather than deduplicating downstream. 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 - [ ] Available minutes match a hand calculation - [ ] Re-importing tasks is confirmed to create no duplicates - [ ] Closing a day is confirmed to require a decision per item - [ ] The bias figure is confirmed to require the sample threshold - [ ] The copy lint passes and a second reader has reviewed the report wording ## Launch constraint Do not market omitted Sunsama 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. Plans, tasks and sessions. A record of how you work, so treat it accordingly. DB_PATH=./data/plans.db # Required. Local wall clock, in your zone. WORKING_HOURS=09:00-17:30 # Required. The zone working hours and day boundaries are expressed in. TIMEZONE=Europe/London # Required. Subtracted from available time for the things that always happen. Tune it from your own history. DAILY_BUFFER_MINUTES=90 # Required. Completed tasks required before a bias figure is shown. ACCURACY_MIN_SAMPLE=30 # Required. Times a task can be moved before it is flagged, without a verdict attached. MOVE_FLAG_THRESHOLD=3
$ choose a build depth, inspect the files, then open the complete pack in your agent
They pay for a planning habit that survives across tools, not a new to-do list.
xintegration breadth
xcalendar two-way sync
xdaily ritual design
xmobile apps
xanalytics
xreliability
Don't feel like building it? These folks already made it free.
all 3 free alternatives to Sunsama →· no votes, no pay-to-list · just what's real
Sunsama pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| pro | $22/user | $17/user | 1 user; daily planning, task/calendar integrations, and analytics; no numeric task cap published. |
| enterprise | custom | — | Custom seats, security, invoicing, and support. |
free tierno free tier
billingmonthly + annual; 14-day trial with no card; Enterprise custom
hidden costsEvery teammate added to a workspace is placed on the subscription and billed on a prorated basis.
verified 2026-08-14 · source ↗
Vibecode Sunsama
Kinda. The core of Sunsama is buildable in a weekend with the prompt on this page, but there are real gaps: integration breadth, calendar two-way sync. Read the honest list above before committing.
How much does Sunsama cost?
Sunsama costs about $22/month (Pro, checked 2026-08-14), which is $264 per year.
What do I lose by replacing Sunsama?
Honestly: integration breadth; calendar two-way sync; daily ritual design; mobile apps; analytics; reliability. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Sunsama?
Yes: Super Productivity (A local task planner with timeboxes, focus mode and its own stopwatch; less glossy, less interested in your wallet.) FlowSavvy Free (It auto-plans unlimited tasks for the next two weeks; priorities and dependencies are how the paid plan gets you.) Trevor AI Free (One calendar gets task-to-time-block planning and some AI suggestions; multiple calendars and deeper automation cost extra.) All 3 curated free alternatives are at vibecodeit.com/sunsama/alternatives. The prompt is for when you want it exactly your way.