Designing How Abstria Breaks Down a Yearly Goal: Calendar Periods, Shared Tasks, and Progress

Introduction

I build and run Abstria, a goal app for people whose yearly goals never turn into daily action. You write a goal for the year, break it into months and weeks, and then plan the days that move those weeks forward.

How to set a good yearly goal is a question about method, and this article doesn't cover it. This one is about the model underneath: how the app represents a goal and its breakdown, and why it ended up the way it is. Some of these decisions changed after the first version, so where that happened I describe the old shape and the current one separately.

The article covers three topics:

  • Four levels, each with a fixed period on the calendar
  • Daily tasks that can count toward more than one weekly goal
  • How progress rolls up from the day to the year

1. Four levels, each pinned to the calendar

A goal in Abstria has four levels:

  1. Year. The goal you want to reach this year.
  2. Month. What that goal needs in a given month.
  3. Week. What that month needs in a given week.
  4. Day. The concrete tasks you put on specific days.

Each of the first three levels belongs to exactly one parent: a monthly goal belongs to one yearly goal, and a weekly goal belongs to one monthly goal. That makes the tree easy to read, and it means a period can be checked against its parent.

The periods come from the calendar, not from the date you happened to start:

  • A yearly goal ends on December 31 of its year, whichever month it starts in.
  • A monthly goal covers one calendar month.
  • A weekly goal covers one calendar week. Whether weeks start on Monday or Sunday is a user setting. A week that crosses into the next month is cut at the end of the month, so the last week of a month can be shorter than seven days.

The first design worked differently. A yearly goal ran for twelve months from its start, and weeks were counted in seven-day blocks from the start of the month, so a week could spill into the next month. Under that design, a goal started in April ran into the next year, and a week near the end of a month also covered days of the following month. I moved to calendar periods and fixed the yearly goals that had already been saved with the old end date.

The cost is the short weeks at the edges of a month. In exchange, every week belongs to one month and every month to one year, a rule that needs no manual.

2. One task, several weekly goals

The fourth level is where the model stops being a tree. A task on a given day can be linked to more than one weekly goal.

The reason is that real work doesn't split cleanly. Say one yearly goal is to publish a book and another is to grow a newsletter. A two-hour writing session on Tuesday moves both. If a task could belong to only one goal, I would have to pick one and let the other look idle, or enter the same session twice.

So the link between tasks and weekly goals is many-to-many. When the task is done, every weekly goal it is linked to sees it as done.

Before: two kinds of task

In the first version, goal tasks and everything else were separate things. A daily plan linked to weekly goals was one kind of record, and a chore with no goal, like renewing a domain or answering an email, was another. Earlier still, a daily plan was a template that produced a separate record for each day. That per-day record was removed first, and the date moved onto the plan itself.

That left two kinds of record with almost the same fields: a title, a status, a scheduled date, a due date, and repeat settings. The screen for today showed both in one list anyway, but each had its own edit form, and every change to one had to be made to the other.

After: one kind of task

Now there is one kind of task. Whether it serves a goal is decided only by whether it has links to weekly goals. A task with links counts toward those goals. A task with none is a chore.

The two edit forms became one, and the screen for today works from a single list. Turning a chore into goal work is now just adding a link, with no copying or retyping. The change deleted about twice as many lines of code as it added.

3. Progress that rolls up by counting

Every level has the same three statuses: not started, in progress, and done. A parent's status is worked out from its children with one rule, applied at every level:

  • If every child is done, the parent is done.
  • If any child is in progress or done, the parent is in progress.
  • If every child is not started, the parent is not started.
  • If the parent has no children yet, its status stays whatever you set by hand.

The children of a weekly goal are its linked tasks. The children of a monthly goal are its weeks, and the children of a yearly goal are its months. Finishing the last task of a week closes the week. If that was the last open week of the month, the month closes too, and the change can carry on up to the year.

Progress is shown the same way: as the number of direct children that are done, out of the total. A yearly goal shows how many of its months are done, and a weekly goal shows how many of its tasks are done.

The first design described a richer formula. It worked out a percentage for each daily plan from how many of its repeats were done, summed those for each week, and averaged the weeks into months and the months into a year. The current version counts direct children instead. A figure like "3 of 12 months done" needs no explanation, and it matches what you see when you open the goal.

Keeping the status in the database

The rule runs in the database, as triggers on the tables, not in the screens. Tasks change from more places than the web app: an API, and sync with outside tools. If the rule lived in the front end, a task finished through the API would leave its week showing the old status.

Running it in the database also showed me where the rule had holes. The first version reacted only to a change in a task's status. It missed cases where the set of children changed:

  • Linking a new task to a week that was already done left the week marked done, even though it now had an open task.
  • Deleting the last unfinished week of a month left the month in progress, even though everything in it was done.

Both were fixed the same way: the parent is worked out again whenever a child is added, removed, or linked, not only when a child's status changes.

Conclusion

The three decisions share one idea: keep the model close to how people already think about time and work.

  • Periods follow the calendar, so where a week belongs is never a question.
  • A task can serve several goals, because a single piece of work often does.
  • Progress is a count of finished children, worked out in one place, so every screen and every integration agrees.

Each of these started out more flexible or more precise than it is now, and in each case the current version is the simpler one to explain.

All posts

Building for the Japanese market? Let’s start with what to build first.

Email me