Build Fibery

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 workspace whose hard problems are letting the user define types without the schema becoming unmanageable, links that stay honest when something is deleted, and recurrence rules that behave correctly on the thirty first of February.

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

RuntimeNext.js 15 with TypeScript, one process on localhostStorageSQLite with a typed entity table plus a per-type field definition tableRecurrenceA documented subset of the iCalendar recurrence rule, expanded into concrete instancesViewsSaved filters, sorts and groupings stored as dataExportOne JSON document containing the schema and the data together

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 seed scripts.

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

    Verify node --version prints v22 or higher

  2. installfree

    Why Recurrence is a specification with published edge cases, and every hand-rolled version gets short months or clock changes wrong in a way that only shows up months later.

    Get it A maintained library for your runtime that implements the recurrence rule and its exception dates.

    Verify A monthly rule on the thirty first skips short months in its output

  3. have readyfree

    Why Recurrence across a clock change is decided by the zone rules for that date, and stale zone data produces tasks an hour out for weeks at a time.

    Get it Your runtime's bundled data, updated, or the system database.

    Verify Both of this year's transition dates resolve correctly in a test

  4. have readyfree

    Why The value-per-row design only reveals its cost above a certain number of entities, and that number is well above what a hand-made test workspace contains.

    Get it A script that generates tens of thousands of entities across several types.

    Verify A three-condition view over the seeded data returns within your stated limit

  5. decidefree

    Why The full specification is large, and supporting half of it silently is worse than supporting a quarter of it loudly.

    Get it Write down which frequencies and modifiers you accept, and reject the rest at save time with a clear message.

    Verify An unsupported rule is refused when saved, naming the unsupported part

Data model

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

- types(id, name, icon, created_at) · user-defined, because the point is that your work does not fit three fixed tables
- fields(id, type_id, key, label, kind, options_json, required, ordinal, retired_at) · retired rather than deleted, so old values still have a definition
- entities(id, type_id, created_at, updated_at, deleted_at) · one row for everything, typed by its type
- values(entity_id, field_id, value_text, value_number, value_date) · one row per field per entity, which is slower to query and impossible to corrupt
- relations(id, from_type_id, to_type_id, name, inverse_name, cardinality) and links(relation_id, from_entity_id, to_entity_id) · both directions from one row
- recurrences(entity_id, rule_text, dtstart, timezone, until_or_count, exceptions_json) · the rule plus the exceptions, never a list of dates alone
- views(id, name, type_id, filter_json, sort_json, group_by_field_id) · the saved question, which is most of what a workspace actually is

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/workspace.dbEverything. One file, backed up on a schedule.
APP_BINDrequired127.0.0.1:3000Localhost. A single-user workspace has no business on a network interface.
TIMEZONErequiredEurope/LondonUsed for recurrence expansion. Stored per recurrence, not assumed globally.
RECURRENCE_WINDOW_DAYSrequired180How far ahead instances are materialised. Beyond this they are computed on demand.
BACKUP_DIRrequired./data/backupsScheduled copies of the database file. The restore is tested in the suite.
BACKUP_KEEPrequired14Retention by count. A workspace file is small and keeping two weeks costs nothing.
VIEW_QUERY_BUDGET_MSrequired200A view slower than this logs a warning with the compiled query, so it is findable.

The build, in order

  1. Types you can define without breaking yesterday

    Fields are added, renamed and retired while every existing value keeps its meaning.

    1. The same separation that makes a form builder survivable makes a workspace survivable.

      Files src/schema/fields.ts

    2. Hidden and gone look identical in the interface and are not the same thing at all.

      Files src/schema/retire.tstest/retire.test.ts

    3. A typed store that accepts anything is an untyped store with extra tables.

      Files src/values/validate.ts

    done when · tick each as it passes
    watch out
    • Deleting a field and its values because the user removed it from the form throws away data the user thought they were merely hiding, and they find out three months later when they want it back. Retire the field, hide it from the form, keep the values, and offer a genuine delete as a separate and clearly worded action.
  2. Links that survive a deletion

    Entities link in both directions and a deleted entity never leaves a dangling reference.

    1. Two rows for one relationship is two chances to disagree.

      Files src/relations/links.ts

    2. Foreign keys are free and your code is not.

      Files src/db/schema.sqltest/dangling.test.ts

    3. Deleting a project should never quietly orphan forty tasks.

      Files src/components/DeleteDialog.tsx

    done when · tick each as it passes
    watch out
    • Storing the relationship twice, once on each side, means the two rows disagree the first time a write fails halfway, and from then on a task appears under a project that does not list it. Store one row, derive both directions from it, and let the database enforce that both ends exist.
  3. Saved views that stay fast

    Filters, sorts and groupings are stored as data and still answer quickly at real size.

    1. In-memory filtering is fine until the day it is not, and that day arrives silently.

      Files src/views/compile.ts

    2. Two hundred rows will validate a design that cannot work.

      Files scripts/seed-large.ts

    3. A broken view should fail in the editor, not in the morning.

      Files src/views/validate.ts

    done when · tick each as it passes
    watch out
    • A value-per-row store makes every multi-condition filter a chain of joins, and a view that is instant over two hundred entities takes seconds over twenty thousand, which is the size a personal workspace reaches in about two years. Compile filters to a single query with indexed lookups, and put a realistic row count into the test data now rather than discovering this later.
  4. Recurrence that survives February

    Repeating work expands correctly on short months, clock changes and edited series.

    1. An invalid date is skipped, not moved, and this is the whole bug.

      Files src/recurrence/expand.tstest/recurrence.test.ts

    2. An infinite series materialised into rows is a full disk with a to-do list on it.

      Files src/recurrence/window.ts

    3. Editing a series should never erase what somebody already did.

      Files src/recurrence/instances.ts

    done when · tick each as it passes
    watch out
    • A monthly task on the thirty first is where every homemade recurrence engine goes wrong: clamping to the last day of a short month invents an occurrence on the thirtieth that the rule never described, and if the series is bounded by a count it also moves where the series ends. The iCalendar specification is unambiguous that instances with an invalid date must be ignored and must not be counted as part of the recurrence set, and it also forbids a rule carrying both a count and an end date. Follow the specification and test the thirty first of every month.
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.