Build Height

YESback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A personal project tracker whose hard problems are recurrence that behaves the way the calendar standard specifies, saved views that survive a schema change, and due dates that arrive on the day the person meant.

estimated effort one sittingthe files for this build are in the project pack

RuntimeNext.js with TypeScript, single user, bound to the loopback interfaceStorageSQLite with statuses and views stored as rowsRecurrenceA documented subset of the calendar recurrence rule, including its skip semanticsDatesDate only fields stored as dates, timed fields as instants with a zoneViewsStructured filter documents compiled to queries at read time

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the application and the test suite.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree

    Why Hand rolling recurrence is where the skip rules get invented rather than followed, and the standard states them plainly.

    Get it A maintained recurrence library for your runtime, or the recurrence rule section of RFC 5545. open ↗

    Verify The monthly rule on the 31st example from the standard produces the dates the standard shows

  3. decidefree

    Why The categories are what the code branches on, so inventing them while writing the first list view guarantees a fourth category appears later.

    Get it Write down each status, its category of open, done or cancelled, and its order.

    Verify The list is in the seed data and every status maps to one of three categories

  4. have readyfree

    Why Recurring tasks, odd due dates and half finished work only exist in a real list, and a seeded demo list will pass every test that matters.

    Get it Export your current task list to CSV, or copy fifty real tasks by hand.

    Verify It imports with the dates and the recurrence rules intact

  5. decidefree

    Why Date handling looks perfect from a machine set to UTC, which is why the bug reaches everyone else.

    Get it Pick a zone at least eight hours from UTC with a daylight saving change and run the suite under it.

    Verify The suite passes with that zone set and the two transition dates included

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- projects(id, name, key, archived_at)
- statuses(id, project_id, name, category, ordinal) · category is open, done or cancelled, and code branches on category and never on the name
- tasks(id, project_id, title, body, status_id, priority, due_date, due_at, due_zone, created_at, completed_at, archived_at)
- recurrences(task_id, rule_text, dtstart_date, dtstart_zone, count_remaining, until_date, materialised_to) · a documented subset of the recurrence rule
- instances(id, recurrence_task_id, occurrence_date, task_id, skipped_reason) · materialised ahead, and skipped occurrences are recorded with why
- views(id, name, filter_json, sort_json, group_by, ordinal) · a structured document compiled to a query, never stored SQL
- activity(id, task_id, at, kind, detail) · the only record of what a task looked like before the last edit

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_PATHrequired./data/tracker.dbProjects, tasks, views and recurrence rules.
BIND_ADDRESSrequired127.0.0.1The whole access control model. Changing it should require thinking.
PORTrequired4321Local port for the interface.
USER_TIMEZONErequiredPacific/AucklandUsed for today, overdue and this week. Set it to something other than UTC while testing.
MATERIALISE_AHEAD_DAYSrequired90How far recurring occurrences are generated in advance.
ARCHIVE_AFTER_DAYSrequired30Completed tasks leave views after this, and stay in exports.
EXPORT_DIRrequired./exportsWhere the JSON and CSV exports are written.

The build, in order

  1. Tasks, and a workflow that is data

    Statuses are rows with a category, so changing the workflow is not a migration.

    1. Three categories is enough, and it is what the code is allowed to see.

      Files src/db/schema.sqlsrc/statuses.ts

    2. A grep in the test suite is a fine way to enforce it.

      Files tests/no-status-name-checks.test.ts

    3. Orphaned tasks disappear from lists and reappear as a mystery.

      Files src/statuses.ts

    done when · tick each as it passes
    watch out
    • Writing statuses as a fixed list in code means the first workflow change is a migration, and the second one strands tasks in a status the code no longer knows about so they vanish from every list. Store statuses as rows with a category, branch on the category, and make deletion ask where the tasks move.
  2. Views that are saved queries

    A named filter keeps working after the schema changes.

    1. Fields, operators and values, each validated against the schema.

      Files src/views/filter.ts

    2. Every value bound, every time, with a test that proves it.

      Files src/views/compile.tstests/filter-injection.test.ts

    3. An empty list looks like an answer and is not one.

      Files src/views/render.tsx

    done when · tick each as it passes
    watch out
    • Saving the generated SQL for a filter means renaming a column silently turns a saved view into an empty list, and if any part of that string ever came from user input it is also the injection surface you thought you did not have. Store the filter as data and compile it fresh every time.
  3. Recurrence the way the standard defines it

    A repeating task generates the right dates, including the ones that must not exist.

    1. Naming the parts you support is what keeps the behaviour honest.

      Files src/recurrence/parse.tsdocs/recurrence.md

    2. Not clamped, not shifted, and not counted toward the count.

      Files src/recurrence/expand.tstests/recurrence-invalid.test.ts

    3. Ten dates on screen catches every misunderstanding in one second.

      Files src/recurrence/preview.tsx

    done when · tick each as it passes
    watch out
    • Implementing 'every month on the 31st' by clamping to the last day puts a task on 28 February that should not exist, and the standard is explicit: RFC 5545 says recurrence instances with an invalid date or a nonexistent local time must be ignored and must not be counted as part of the recurrence set, so a skipped month must not consume the count either. Skip, record why, and show the generated dates before the rule is saved.
  4. Dates that arrive on the day you meant

    Quick entry produces the task and the date the person had in mind.

    1. Different type, different column, and never converted.

      Files src/dates.tstests/date-only.test.ts

    2. Quick entry that guesses silently creates the wrong task quickly.

      Files src/quickadd.tsx

    3. Midnight is a local event, and every list depends on it.

      Files src/dates.ts

    done when · tick each as it passes
    watch out
    • Storing a date only due date as midnight in UTC and rendering it locally makes every task due a day early or a day late depending on which side of the meridian the person is on, and the error is invisible to whoever wrote it and constant for everybody else. Keep date only fields as plain dates and never let them touch an instant conversion.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more tasks.