Build Wanderfugl

KINDAreplaces $9/mosaves $108/yrback 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 travel planning tool whose defining requirement is that it works when you most need it, which is in a foreign airport with no data connection, and whose hardest correctness problem is that every time in a trip belongs to a different place.

estimated effort multi-daythe files for this build are in the project pack

RuntimeTypeScript with a local-first store and optional syncTimeEvery item stored as an instant with the timezone of its locationDocumentsConfirmations stored as files on the deviceImportConfirmation parsing with manual entry as an equal pathSharingRead-only trip links that also work offline once opened

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 local-first application.

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

    Verify node --version prints v22 or higher

  2. installfree

    Why Zone rules change by government decision and a stale database puts a flight an hour wrong, which is the failure that costs you the flight.

    Get it Your platform's zone data with a plan for keeping it current, plus a library handling ambiguous local times explicitly.

    Verify A daylight transition produces the correct duration for a flight spanning it

  3. have readyfree

    Why Timezone handling, connection checking and parsing all break on real bookings, and a single-country trip exercises almost none of it.

    Get it Use a real trip with at least three countries, an overnight flight and a connection under two hours.

    Verify A real trip with three zones and a tight connection is entered

  4. have readyfree

    Why The central claim is that it works with no connection, and testing that with a network available proves nothing.

    Get it Any device where you can disable networking entirely, ideally the one you would actually travel with.

    Verify Every screen is exercised with networking disabled

  5. have readyfree

    Why Parsing breaks on the format that matters, and the mixture of email, attachment and screenshot is what real bookings actually look like.

    Get it Collect confirmations from several airlines and hotels, including one that is only a screenshot.

    Verify At least six confirmations exist across three formats

Data model

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

- trips(id, title, starts_on, ends_on, base_currency)
- items(id, trip_id, kind, title, starts_at_utc, ends_at_utc, start_place_id, end_place_id, confirmation_ref, notes)
- places(id, name, timezone, country, coordinates, address) · the timezone belongs to the place
- documents(id, item_id, path, kind, added_at, offline_available) · stored on the device
- costs(item_id, amount_minor, currency, rate_to_base, paid_by, at)
- conflicts(trip_id, kind, item_ids_json, detail, detected_at) · overlaps and impossible connections
- An item stores its start and end place separately, because a flight's departure and arrival are in different timezones and treating them as one is the classic error.

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
DB_PATHrequired~/.local/share/trips/trips.dbTrips and itineraries, stored on the device.
DOCUMENTS_DIRrequired~/.local/share/trips/documentsConfirmation files, stored locally so they work with no connection.
MIN_CONNECTION_MINUTESrequired60An assumption, stated as one rather than presented as authoritative.
OFFLINE_REQUIREDrequiredalwaysPresent as a constant. The moment you need this is the moment you have no signal.
BASE_CURRENCYrequiredEURFor totals. Individual costs keep their own currency and rate.
SYNC_REMOTEoptionals3://my-trips/storeOptional. Absent means one device, which works completely.

The build, in order

  1. Times that mean the right place

    A flight shows its departure in the departure city's time.

    1. Not to the trip. The trip spans several.

      Files src/places.ts

    2. A flight departs and arrives in different zones. It is the classic error.

      Files src/items.tssrc/items.test.ts

    3. A bare time on a trip across zones is an ambiguity.

      Files src/display.ts

    done when · tick each as it passes
    watch out
    • Storing a trip in one timezone means a flight leaving at seven in the morning shows as three in the morning once you have travelled, which is exactly when you are relying on it. Every place carries its own zone and every item's two ends are resolved separately.
  2. Everything available with no connection

    The whole trip including documents opens with the network off.

    1. A reference behind a login you cannot reach is not a reference.

      Files src/documents.ts

    2. Assuming it works offline is how it does not.

      Files tests/offline.test.ts

    3. Silently missing is the worst version of missing.

      Files src/documents.ts

    done when · tick each as it passes
    watch out
    • Storing confirmation documents as links to a cloud service means that at the immigration desk, with no roaming and no airport network, the booking reference you need is behind a login you cannot reach. Download and store the files, and check that the whole application works with networking disabled rather than assuming it does.
  3. Getting bookings in without retyping

    A confirmation email becomes an itinerary item, or manual entry is just as fast.

    1. A silently wrong departure time is discovered at the airport.

      Files src/import.ts

    2. It is the path that always works and will be used often.

      Files src/manual.ts

    3. It is the actual evidence of the booking.

      Files src/documents.ts

    done when · tick each as it passes
    watch out
    • A parser that silently produces a wrong departure time is far worse than one that fails, because you will act on it and only find out at the airport. Parse conservatively, require confirmation of the extracted times, and fall back to manual entry rather than guessing.
  4. Catching the impossible connection

    A plan that cannot physically work is flagged before you travel.

    1. Across zones the displayed comparison is meaningless.

      Files src/conflicts.tssrc/conflicts.test.ts

    2. The gap nobody notices until they arrive.

      Files src/conflicts.ts

    3. A minimum connection time is a guess and should read as one.

      Files src/conflicts.ts

    done when · tick each as it passes
    watch out
    • Comparing a landing time and a departure time as displayed strings, rather than as instants, makes a forty minute connection look like an eight hour one when the two are in different zones. Compare instants, and state the minimum connection assumption you are using rather than implying it is authoritative.
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 travel.