Build Zenkit

YESback to the verdict

0%0 of 16 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 work manager whose hard problems are letting a list grow new fields without a schema migration every time, keeping every view a query rather than a second copy of the truth, and expanding recurring items correctly across February and a clock change.

estimated effort weekendthe files for this build are in the project pack

RuntimeOne Node process serving server-rendered pages, no client frameworkStorageSQLite in WAL mode, one file, custom field values in typed columnsRecurrenceA documented subset of the iCalendar recurrence rule, stored as text and expanded on readTimeInstants in UTC for anything with a time, plain dates for all-day itemsExportWhole workspace to JSON and any view to CSV, written in the first phase

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 single process that serves the app.

    Get it nodejs.org, or your version manager. open ↗

    Verify node --version prints v22 or higher

  2. installfree

    Why Needed to inspect the schema, confirm that adding a field ran no migration, and run a restore by hand.

    Get it Your package manager, or sqlite.org. open ↗

    Verify sqlite3 --version prints a version

  3. have readyfree

    Why The recurrence subset is copied from the specification rather than invented, and the rules about invalid dates and about count with an end date come straight from it.

    Get it Read the section and write down which parts of the rule you support. open ↗

    Verify A documented list of supported rule parts exists before any recurrence code

  4. decidefree

    Why Each kind needs a typed column, a sort, a filter and a form control, and adding a kind later touches all four, so the list is a decision rather than a discovery.

    Get it Write down the kinds you will support and the column each one writes into.

    Verify The list is in the README and matches the values table columns

Data model

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

- lists(id, name, icon, position, created_at, archived_at)
- fields(id, list_id, key, label, kind, options_json, position, required) · kind is text, number, date, datetime, select, multi_select, checkbox or person
- items(id, list_id, title, status, position, created_at, updated_at, completed_at, deleted_at, parent_item_id)
- field_values(item_id, field_id, text_value, num_value, date_value, datetime_utc, zone) · one typed column populated per row, never everything as text
- views(id, list_id, name, shape, filter_json, sort_json, group_by_field_id) · shape is table, board, calendar or list
- recurrences(id, item_id, rule_text, dtstart, zone, count_done, ends_at) · the rule, not a pile of generated rows
- activity(id, item_id, at, kind, before_json, after_json) · append-only, and the source of undo
- A view owns no items. It owns a filter, a sort and a grouping field, so the board and the table cannot disagree about what is true.

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/work.dbThe whole workspace. This is the file to back up.
BACKUP_DIRrequired./data/backupsWhere the backup interface writes copies, and where a restore is rehearsed from.
DEFAULT_TIMEZONErequiredEurope/LondonUsed to expand recurrence rules and to render all-day dates.
PORTrequired4180Bound to localhost. There is no authentication because there is one user.
TRASH_RETENTION_DAYSrequired30How long a deleted item stays recoverable before purge offers to remove it.

The build, in order

  1. One item table that survives a new field

    A user adds a field to a list without a migration and without losing the ability to sort or filter on it.

    1. One text column for everything is what makes sorting and filtering wrong later.

      Files src/db/schema.sqlsrc/fields.js

    2. The user invents fields faster than you ship releases.

      Files src/fields.jstests/fields.test.js

    3. Nine against ten, and March against May.

      Files tests/sorting.test.js

    done when · tick each as it passes
    watch out
    • The obvious shape stores every custom value as text in one column, and then a number field sorts 10 before 9, a date filter compares strings so March sorts before May, and an empty value and the string 'null' become the same thing. Give the values table a column per kind, write into exactly one of them, and index the column the field actually uses.
  2. Views are saved queries, never second copies

    The same items shown as a table, a board and a calendar always agree.

    1. A view that caches rows is a second truth that drifts by Friday.

      Files src/views.js

    2. The board is a grouping, so moving a card is an update.

      Files src/views.jssrc/public/board.js

    3. Field deletion is common and should not produce a blank page.

      Files src/views.jstests/views.test.js

    done when · tick each as it passes
    watch out
    • Building the board as its own table of cards with their own positions means moving an item in the table view and moving it on the board write to two different places, and within a week the two views disagree about which column a task is in with no way to tell which is right. Make the board a grouping over the item rows, so dragging a card is an update to one field.
  3. Recurring items that survive February

    A repeating task generates the right dates across short months, clock changes and a completed occurrence.

    1. Adding a month to a date is how the 31st becomes fiction.

      Files src/recurrence.jstests/recurrence.test.js

    2. Section 3.3.10 is explicit that they are ignored, not rounded.

      Files src/recurrence.js

    3. Ticking Tuesday off should not end the habit.

      Files src/items.jstests/occurrences.test.js

    done when · tick each as it passes
    watch out
    • Generating the next date by adding one month to the previous one turns the 31st of January into an invented 31st of February, or quietly shifts a monthly task to the 28th forever once it lands there. Expand from the rule and the original start each time, and drop invalid dates the way the specification says rather than rounding them into the nearest real day.
  4. Capture in three seconds, and undo

    Adding a task is faster than opening a form, and every destructive action is reversible.

    1. Before and after values, because undo has to know both.

      Files src/activity.js

    2. A single-user app has no colleague with a copy.

      Files src/items.jstests/trash.test.js

    3. Capture that needs navigation does not get used.

      Files src/public/capture.js

    done when · tick each as it passes
    watch out
    • Hard deleting an item on a mis-click is unrecoverable in a single-user app because there is nobody to ask for a copy, and the user discovers it a week later when the task never came back. Delete by timestamp, keep the row, and put purge behind its own screen with a count of what is about to go.
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.