Build Wanderfugl
KINDAreplaces $9/mosaves $108/yrback to the verdict
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.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the local-first application.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - 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 - 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 - 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 - 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.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DB_PATH | required | ~/.local/share/trips/trips.db | Trips and itineraries, stored on the device. |
DOCUMENTS_DIR | required | ~/.local/share/trips/documents | Confirmation files, stored locally so they work with no connection. |
MIN_CONNECTION_MINUTES | required | 60 | An assumption, stated as one rather than presented as authoritative. |
OFFLINE_REQUIRED | required | always | Present as a constant. The moment you need this is the moment you have no signal. |
BASE_CURRENCY | required | EUR | For totals. Individual costs keep their own currency and rate. |
SYNC_REMOTE | optional | s3://my-trips/store | Optional. Absent means one device, which works completely. |
The build, in order
Times that mean the right place
A flight shows its departure in the departure city's time.
Not to the trip. The trip spans several.
Files
src/places.tsA flight departs and arrives in different zones. It is the classic error.
Files
src/items.tssrc/items.test.tsA bare time on a trip across zones is an ambiguity.
Files
src/display.ts
done when · tick each as it passeswatch 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.
Everything available with no connection
The whole trip including documents opens with the network off.
A reference behind a login you cannot reach is not a reference.
Files
src/documents.tsAssuming it works offline is how it does not.
Files
tests/offline.test.tsSilently missing is the worst version of missing.
Files
src/documents.ts
done when · tick each as it passeswatch 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.
Getting bookings in without retyping
A confirmation email becomes an itinerary item, or manual entry is just as fast.
A silently wrong departure time is discovered at the airport.
Files
src/import.tsIt is the path that always works and will be used often.
Files
src/manual.tsIt is the actual evidence of the booking.
Files
src/documents.ts
done when · tick each as it passeswatch 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.
Catching the impossible connection
A plan that cannot physically work is flagged before you travel.
Across zones the displayed comparison is meaningless.
Files
src/conflicts.tssrc/conflicts.test.tsThe gap nobody notices until they arrive.
Files
src/conflicts.tsA minimum connection time is a guess and should read as one.
Files
src/conflicts.ts
done when · tick each as it passeswatch 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.
Costs and sharing the planproduct builder
You know what the trip cost and the person meeting you has the details.
A total that changes weekly is not a total.
Files
src/costs.tsThe person meeting you is also travelling.
Files
src/share.tsA calendar file with wrong times is worse than none.
Files
src/export.ts
done when · tick each as it passeswatch out- Converting costs at today's rate rather than the rate when paid means the trip total changes every time you open it, and the figure you tell someone is different from the one you told them last week. Store the rate with the cost and never re-convert.
That is the whole plan for Wanderfugl. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Booking anything.
- Live flight status and disruption handling.
- Destination recommendations and content.
- Group trips with shared editing.
- Live flight, train and hotel pricing or availability
- Automatic booking email parsing into your itinerary
- A real mobile app with offline access while travelling
- Timezone and schedule-change handling done correctly by someone else
- Sharing a polished read-only itinerary with travel companions
- A day view that shows only what is next, which is what you want while travelling
- Currency spending totals per country, which is the question after the trip
- Travel document expiry warnings, since a passport rule is the most expensive thing to miss
Need the files? The project pack on the verdict page hands your agent the whole brief · more travel.