A small home for apps built and run by Claude Code. Pick one below.
Under the hood it's one lightweight reverse proxy, routing HTTPS
traffic to whichever small apps are running behind it — the
interesting part isn't the proxy itself, it's what it enables: a
standing framework for spinning up a new project, wiring it in,
and having it live at its own address within the hour, built
entirely from a phone or tablet on the road. No laptop, no
per-app hosting setup — just point it at a port and go.
What Trip Log does
Trip Log turns a phone into a proper travel diary: an interactive
map tracks every stop, live GPS tracking follows a trip in real
time while it's happening, and photo and video pins land
automatically at the spot where they were captured — no
manual tagging. Multiple trips stay organized side by side, each
with its own map and timeline to browse back through later.
A native Android companion app extends that further: in-app
camera capture that keeps full location data (something the
phone's regular photo picker strips out), background tracking
that keeps recording even if the app is closed or the phone
reboots, and bulk import from an existing photo gallery so a trip
already underway can be brought in all at once.
from the build log
The little things: building Trip Log with an AI pair and an Android tablet
Ben Wallens · sessions 01–28 · updated 2026-07-24
Trip Log is the second app on the Mobile Vibe stack — a self-hosted
trip tracker: a map, a passphrase-gated write side, live GPS tracking,
photo/video pins, and a native Android companion app. None of that is
the interesting part. The interesting part is everything that broke
along the way, because almost none of it broke where the docs said it
would.
The actual dev environment for this entire project: a split-screen tablet, one side running Claude Code over SSH, the other watching the live app update in real time.
Here's the constraint that shapes everything below: this whole project
was built on a Google Compute Engine VM running Claude Code, with the
only human interface being an Android tablet — Termius
as the SSH client, nothing else. No laptop, no desktop, at any point.
Every file transfer, every real photo, every real device test, every
wireless-debugging pairing session — all of it happened through a
tablet talking to a cloud box. That's not a gimmick detail; it's the
reason the discipline below (get the real artifact, test against the
real device) wasn't optional. There was no local dev environment to
fall back on, no “let me just try this on my machine real quick.”
The tablet was the machine, and the VM was the only place code
actually ran.
Twenty-eight sessions in, the pattern is consistent: the framework-level
plan is usually right, and the actual bugs live one layer down, in the
gap between what a spec says and what a real device does. Here's the
running list.
Python 3.13 quietly deleted a stdlib module
The first session needed multipart form parsing for photo uploads. The
obvious move is cgi.FieldStorage — except Python 3.13
removed the cgi module outright (deprecated since 3.11,
gone for real now). First real obstacle, session one, before a single
feature existed: hand-roll a multipart parser against the stdlib's raw
socket/HTTP primitives instead. Small thing, but it set the tone —
check the actual runtime before trusting a memorized API.
The real Google Takeout file didn't match anyone's docs
Session 3 needed to import a Google Maps Timeline export. Every
description of the format — including the ones baked into earlier
planning — assumed clean, numeric {lat, lon} pairs.
The real on-device export, pulled via the tablet and SFTP'd over, told
a different story: three different segment shapes mixed together
(timelinePath, visit, activity),
coordinates as literal strings like "42.61°, -71.41°",
and timestamps carrying real UTC offsets that needed normalizing before
they were usable. The parser got rewritten against the real file, not
the assumed one, and CSV support — which the design docs also
called for — got explicitly deferred because no real CSV sample
existed to build against. That became a standing rule for the rest of
the project: nothing gets built against assumed structure when a real
sample can be obtained first.
EXIF GPS parsing: byte-exact, and still wrong the first time
Photo geolocation (session 4) meant walking raw JPEG bytes by hand
— APP1/Exif markers, a TIFF header,
IFD0's GPS-IFD pointer, DMS RATIONAL triples. First pass
worked against 7 real photos from the tablet. It broke the moment a
second device entered the picture: an iPhone photo, tested a session
later, turned out to be big-endian TIFF where the
tablet's photos were little-endian. The byte-walker handled both
without changes once the assumption “assume the tablet's
endianness” was replaced with “read the header's own
endianness flag” — but that gap only surfaced because a
second real device got tested, not because anyone predicted it.
The Android GPS-stripping saga
This is the one that ate the most sessions, and the one worth telling
in full, because the fix kept looking finished and then wasn't.
The finding: Android redacts GPS EXIF from any photo
handed to a web app through the system file/photo picker, unless the
app holds the special ACCESS_MEDIA_LOCATION permission. No
browser API exists to request that permission — so this isn't a
Trip Log bug, it's every web app on Android, full stop.
First attempted fix:capture="environment"
on the file input, to force the camera instead of the picker. Confirmed
on a real device: it doesn't reliably work. The browser/OS still landed
on a chooser.
Second attempted fix: assume it's the picker
UI that's the problem, and try a plain generic file browser
instead of a photos/camera-specific dialog. Also confirmed not to help
— the redaction happens at the content-resolver level, independent
of which picker UI presents the file. Two attempts, two real-device
tests, two dead ends, and a clean negative result recorded either way:
no in-browser file-selection path sidesteps this. That's not
wasted effort — knowing definitively what doesn't work is what let
session 13 go straight to a real fix instead of trying a third picker
variant.
The actual fix: stop reading files through the picker
at all. Session 13 built an in-page live camera with
getUserMedia and MediaRecorder — shutter
button draws a video frame to an offscreen canvas, video capture
streams through MediaRecorder, and location comes from
navigator.geolocation at the moment of capture, not from
EXIF at all. A canvas frame has no EXIF to redact. Confirmed working on
the real tablet: both a live photo and a live video landed as
correctly-placed pins.
Where it stands: in-the-moment capture is solved.
Gallery-picked photos were still affected at the time — that's
what led to session 14's native Android app, where
ACCESS_MEDIA_LOCATION plus
MediaStore.setRequireOriginal() pulls the original,
unredacted bytes directly off the device and uploads them through the
exact same /api/entries endpoint the browser path already
used. Same backend, zero server changes — the fix was entirely
about which client reads the file.
Building a native Android app needed a second VM
The dev VM had no Java, no Gradle, no Android SDK, and not enough RAM
or disk to get any of them running comfortably. Rather than cramming a
full Android toolchain onto a box that wasn't sized for it, session 14
spun up a second, purpose-built GCE VM just for compiling —
powered off between builds to save cost, its disk (JDK, SDK, Gradle
caches) persisting so nothing reinstalls per build. The dev VM's own
boot disk still needed a live resize (10GB → 20GB, no downtime)
just for headroom. No git remote exists between the two, so the
Android source and the built APK move by gcloud compute scp.
The toolchain specifics weren't guessed in advance, either — they
were pinned by what actually built: JDK 21 (Debian 13 doesn't package
17), Android Gradle Plugin 9.2.1. One genuinely surprising discovery,
found via a failed first build rather than anticipated:
AGP 9.0+ builds Kotlin support in — no separate
Kotlin Gradle plugin, no kotlinOptions{} block. Most
Android/Kotlin guides written before 2026 still describe the old
two-plugin setup, and following them verbatim produces a build error,
not a working app.
androidx.security is dead, so the token store got hand-built
Google deprecated androidx.security:security-crypto in
2025 with no stable replacement. The login flow needed to persist a
session token without stashing it in plaintext SharedPreferences,
so SecureTokenStore.kt wraps Android Keystore-backed
AES/GCM directly — one more case where the textbook answer had
quietly expired underneath the project.
A native menu got reverted the same day it shipped
Not every fix in this project was a bug — some were second-guessing
a decision fast enough that it never cost anything. A native ActionBar
(camera icon, login/logout, an overflow menu for the import flows) went
in, got built, got installed — and came back out a few hours
later, on direct real-device feedback. The web buttons already did
everything the native ones would have: the browser bridge inside the
WebView gave them GPS-preserving gallery access and camera/mic/location
permissions just by running inside this app, no native duplicate
required. That reset a rule for the rest of the project: native Android
surface area only for what a browser genuinely can't do, so the web
layer keeps iterating without ever forcing a rebuild.
The same session turned up something worse, unrelated to the menu at
all: every native photo upload since session 14 had been silently
broken. TripLogApi.kt sent uploads with chunked
Transfer-Encoding and no Content-Length
header; server.py's body reader only knows how to read a
Content-Length. The entry still got created — a real
row, a real id — with no photo and no GPS, and nothing about a
200 response said otherwise. It surfaced as 17 real
“ghost” entries sitting in the live trip, found by looking,
not by a failing test. Soft-deleted after confirming with the user; the
fix was one line (stop asking for chunked mode; the default buffering
computes Content-Length correctly on its own).
The app's own title bar was invisible, and the DOM disagreed
Real-device testing after a run of Android sessions turned up a bug
that shouldn't have been possible: the web page's top bar — trip
picker, “☰ Menu,” the login chip — was completely
unreachable in the installed app. Not broken-looking. Gone. Except the
DOM said otherwise: getBoundingClientRect() reported
correct bounds, hit-testing found the right element, everything about
the page insisted it was sitting right there, visible and tappable.
The bug, on the real device: nothing below the native “Trip Log” title strip — the whole web UI was there, just painted over.
The resolution came from treating “the DOM says it's fine”
as data, not an answer — adb forward onto the
WebView's own DevTools socket, and a hand-rolled WebSocket client
speaking Chrome DevTools Protocol directly (no browser was available in
this environment to just open chrome://inspect). That
confirmed the page was exactly where it said it was — a separate,
higher native layer was simply painting over it. The real cause: this
device enforces edge-to-edge layout by default, and the Activity never
consumed its own window insets, so the WebView drew full-bleed
underneath the native title strip instead of below it.
Two native-side attempts at a fix each independently caused the
WebView's renderer to hang completely — one padding the WebView
from an insets listener, the other reading the theme's action-bar size
from a separate DecorView listener and only ever handing
the number to the page via evaluateJavascript(). Neither
touched WebView layout directly on the second attempt, and it hung
anyway. Both were reverted immediately rather than ship a hang, and
why either one coincided with the hang was never pinned down.
The real fix, written live against the running app — and the comment recording exactly why, for whoever reads this next.
The fix that actually shipped needed no native code at all: the page
detects window.AndroidApp itself and sets a CSS custom
property, --native-top-inset, that the top bar and its
dropdown already consume, defaulting to 0px everywhere
else. The exact pixel value came from live DevTools CSS injection
against the running app, not a guess — 56px still clipped part of
the bar, 70px looked clear until a later real-device check found it
still slightly covered, 84px cleared it with headroom.
A one-line permission gap, found by logcat, not by guessing
The top bar being reachable again surfaced the next bug immediately:
tapping “Start tracking” crashed the app outright.
logcat named the exact line — a background-connectivity
callback added for persistent tracking called an API that throws a
SecurityException without ACCESS_NETWORK_STATE,
a normal manifest permission nobody had added. One line fixed it. The
interesting part isn't the bug, which was trivial once found —
it's that “trivial once found” only happened because the
fix came from reading the actual crash log on the actual device, not
from re-reading the code and guessing.
The real permission dialog behind all of this — background location tracking only works if a real device grants it, and a real device is the only place that's provable.The other half of it — the crash log, the missing manifest permission, and the fix, all in the same Claude Code session on the tablet.
An outage test cut the connection doing the testing
Verifying that tracking survives a real connectivity outage meant
staging one — disabling WiFi and mobile data on the tablet via
adb for a stretch, to confirm queued points survive and
flush cleanly once the connection returns. They did: 24 points queued
locally with zero loss or reordering across a real 15-plus-minute gap.
What the test plan hadn't accounted for: the wireless-adb
connection driving that same tablet rides the same network being
switched off. The outage cut the tunnel controlling the test along with
the tunnel being tested, and the tablet had to be manually reconnected
once WiFi came back. Real finding, filed away for next time: don't
toggle connectivity on a device you're controlling over that
connectivity.
The whole test marathon, driven from the tablet itself — there was never a second machine to fall back on.
A live import landed on the wrong trip
Testing session 26's bulk video import against real gallery content
found a bug that had actually been live since session 14: the native
import path never read which trip was selected in the web UI at all
— it always uploaded to the server's separate notion of the
“active” trip, silently. A real test import proved it the
hard way, landing 20 entries on the live, public “Vietnam
2026” trip instead of the private test trip that was visibly
selected on screen at the time. Cleaned up immediately — all 20
soft-deleted, the real trip's entry count and newest id confirmed back
to exactly where they'd been — and fixed by having the native
side read the web <select>'s current value before
uploading, the same value the browser upload path had been reading all
along.
The bulk-import picker that exposed the bug — 188 real photos and videos from an actual trip, not a synthetic fixture.
Diagnosing a choppy video without guessing a third fix
A user report — a specific video, recorded at 12:43:50 PM local
time, played back choppily in the installed app — turned into a
small lesson in knowing when to stop patching and go measure. First
fix: the media server always sent a full file with one 200
and no Accept-Ranges, which a mobile WebView needs for a
big video to seek or sometimes even play at all. Added real HTTP Range
support. Didn't fully fix it. Second fix: the server was still
defaulting to HTTP/1.0, closing the TCP connection after every
response, which meant every one of a video's many sequential range
requests paid for a fresh connection — expensive over mobile RTT.
Added HTTP/1.1 keep-alive. Still choppy.
Rather than guess a third server-side change, the next step was to go
look: a real headless Chromium (Playwright, already on this VM) drove
the actual live page, logged in through the real form, and reproduced
the exact video element the app's own click handler creates, then
recorded 20 seconds of real playback against real network timing. The
finding: every stall had 10 to 30 seconds already buffered ahead of the
playhead. Not network-starved — decode-bound. A small hand-written
MP4 box parser (no ffprobe available, and this project
doesn't reach for external tools anyway) confirmed the file was 1080p
H.264 High Profile Level 4.0 at roughly 20 Mbps, close to the
ceiling that level supports. The user confirmed the same file played
back smoothly through the phone's own native player over a regular
mobile browser download — a WebView decode limitation, not a
server problem, and not one more server-side fix to chase. The one real
fix that came out of the whole investigation was a download button, so
a video that a WebView can't decode smoothly can still be handed to a
player that can.
Bundled into the same rebuild, at the user's explicit request: a
rotation bug the user specifically remembered as needing a native
rebuild. Worth double-checking memory against the record before
trusting either one blindly — a quick pass through git
log and this project's own docs could easily have suggested the
wrong thing, since an earlier, unrelated fix already covered a
web-only pan/zoom quirk on rotation. The user's memory turned
out to be right: the real fix (android:configChanges, so
the Activity resizes in place instead of being destroyed and recreated)
had been scoped once before but never actually written. Shipped with
the known, flagged risk left visible rather than hidden: the same
category of WebView-resize operation had correlated with a renderer
hang twice already in this project, and that history sat in front of
the user before deciding to ship it anyway.
The launchpad had never seen a POST
Trip Log's write routes (login, tracking, entries) sit behind a shared
reverse-proxy launchpad that fronts every app on the box — the
same one serving the page you're reading this on. That launchpad had,
up to this point, only ever served read-only apps — its
do_POST handler didn't exist. Invisible until the first
app that actually needed to write data showed up. Fixed on the
launchpad side (a separate, frozen repo this project doesn't touch
directly), but worth noting as a class of bug: infrastructure that's
“worked fine” for years because nothing before you actually
exercised the path.
The launchpad also wedged once in production — stopped accepting
connections on :443 entirely while the app behind it kept answering
fine on its own port, diagnosed via a backed-up listen queue and a
19-minute gap in its own logs. systemctl restart fixed it
in seconds; the actual root cause (single-threaded HTTPServer
blocking on a hung client, unconfirmed at the time) got its own
dedicated root-cause pass in a later webplatform session — the
fix moved the TLS handshake off the shared accept loop and into each
connection's own worker thread, so one bad handshake can no longer wedge
every other visitor's connection, including this one.
The proxy was buffering everything in memory
A report that media was loading painfully slowly — thumbnails and
videos specifically, while map tiles and the small JSON behind pins
loaded normally — landed the day after the video quality bump made
every file bigger, and that wasn't a coincidence. I measured instead of
guessing: the same 66MB file took 0.2 seconds served straight from Trip
Log's own backend, and 1.1 seconds through the shared reverse-proxy that
actually fronts it in production — a real, measured 5.5x overhead.
Reading that proxy's own code — read-only, nothing edited, that
repo stays off-limits from this one — turned up the cause: it
reads an entire proxied response into memory before writing a single
byte back to the client, for every app it fronts, not just this one.
Three concurrent large downloads pushed its own memory from about
176MB to 255MB in real time, consistent with memory spikes already
visible in its own systemd accounting over a six-day uptime.
The fix is small and already written up — swap that one full read
for a chunked read/write loop, so memory use per request stays flat
instead of scaling with file size — but it lives in the same
off-limits repo as the do_POST bug and the earlier TLS
wedge, so it gets handed off rather than patched from here. Restarting
the service was enough to get memory back down and everyone reachable
again in the meantime.
A same-second ordering bug, caught live
Small one, but a good example of what “smoke test against
something real” catches that a synthetic test doesn't: the trip
picker's “most recent trip first” ordering used
started_at DESC alone. Two trips created in the same
second sorted non-deterministically. Caught during a live smoke test,
fixed by adding id DESC as an explicit tiebreaker —
the kind of bug that a hand-written test fixture would have had to go
out of its way to reproduce, but that real usage found in minutes.
The very last piece — actually installing the Android app on the
real tablet — turned into its own small saga, and it started
before adb even entered the picture: the GCE VM has no
direct network path to a tablet sitting on a home WiFi network, so
step one was standing up a WireGuard tunnel between the two just to
give each side an IP the other could reach at all.
With the tunnel up, the next problem was purely human-interface:
Android's wireless-debugging pairing screen gets backgrounded (and its
one-time pairing code invalidated) the moment you switch apps to go
read the code off it — which is exactly what pairing normally
requires you to do. The fix wasn't a code change to Trip Log at all: a
tiny, dependency-free Python web form, bound only to the WireGuard
tunnel IP, serving a two-step form that shell-runs adb pair
and then adb connect.
That form still needed to be opened from a second device on
the tunnel, though — and the only other device around was his
wife's phone. She loaded the form and typed in the six-digit code read
off the tablet, so the pairing went through without a single app-switch
on the tablet itself. Small, slightly absurd detail, but a real one:
the last mile of getting a native Android app onto the one tablet this
whole project runs on required borrowing a second phone. Also worth
remembering for next time: the pairing screen's port and the main
“connect” port are different and single-use — reusing
the pairing port for adb connect fails with a misleading
“connection refused” that looks like a tunnel problem and
isn't.
Google strips GPS from shared albums, on purpose
Not a bug, but a real finding worth recording: a shared Google Photos
album was investigated as a possible “let anyone drop photos in
without a Trip Log account” surface. Tested against five real
photos from an actual shared album, including a Canon EOS R6 shot
alongside tablet photos — zero of five carried GPS, even though
other EXIF (camera model, date, orientation) survived intact, and even
at full original resolution via the =d download variant.
Google strips GPS specifically, as a deliberate privacy default, from
anything served through a public share link. Confirmed with real data
rather than assumed from a privacy policy — and it changed the
shape of a whole feature idea before any code got written for it.
A public link for photos, and a tap for the ones with no GPS
Every other write in this app sits behind a login — the
passphrase-gated half of the public-reads/passphrase-writes split this
project has stuck to from day one. But not everyone who might have a
photo worth adding is a Trip Log user, and the only realistic way to
get a photo off someone else's phone and into the app is a link they
can just tap. So GET /upload exists as this app's one
deliberate exception: a public write endpoint, no login required. I had
Claude flag it explicitly against the project's own
public-reads/passphrase-writes rule before it got built, rather than
letting it slide in as an ordinary feature — a public write
surface needs guardrails an authenticated one doesn't, so anything that
lands through it gets tagged source='unsorted', a hard
200MB size cap checked before the body is even read, and an extension
allowlist none of the other upload paths bother with.
The other side of the same problem: what happens when a photo —
from that link, from a gallery import, from anywhere — has no
location data at all. I didn't want that guessed at or defaulted to
something. If I'm logged in and looking at an entry with no pin, I can
drop one myself: “📍 Pick location on map,” a
literal tap on the live map.
The real menu on a real trip — “Pick location on map” for anything missing GPS, and the new Dashcam Gallery link, both live at once.
124 dashcam clips with no GPS at all
The next real batch of footage wasn't phone photos — it was
dashcam video, front and rear channels, roughly 124 clips, and none of
it carrying any location data whatsoever. I had that confirmed by hand,
by parsing the AVI/RIFF container directly rather than trusting an
assumption about the format. Getting it into the app meant solving two
separate problems: compressing about 35GB of raw footage down to
something the site could actually serve, and figuring out where each
clip had actually been shot.
Compression pulled ffmpeg into this project — only
the second dependency this whole stdlib-only build has ever taken on,
after the Leaflet/OSM map tiles, and I signed off on installing it
explicitly rather than it just quietly showing up. Location came from
something simpler than GPS: matching each clip's own filename timestamp
against the real GPS trail the phone had already logged that day. 96 of
96 real clips matched, most within a few seconds of the nearest logged
point, worst case under two minutes off. The gallery doesn't hide that
uncertainty either — it says “close match” or however
many minutes off right on the clip, instead of pretending every match
is exact.
The dashcam gallery, matched against the real trail — timestamp, coordinates, and how close the match actually is, shown plainly on every clip.
One aside worth keeping from partway through that batch: I stopped a
full re-verification pass midway through and told Claude to just check
one file's sha256 instead, since the source footage hadn't changed and
re-hashing all of it was burning real cycles for no reason. Small
thing, but a good instinct — match how much verification a step
actually needs, not the maximum available.
The excuse that stopped being true
Choppy video playback got a real diagnosis back in session 28 —
a genuine WebView decode limit, confirmed with a headless browser and
a hand-written MP4 parser, not a server bug at all — and shipped
with a download-button workaround instead of a real fix, because at
the time there was no video encoder anywhere in this project to build
a real fix with. That was a reasonable place to stop. It just didn't
stay true.
ffmpeg landed in this repo less than a week later, for a
completely unrelated reason: compressing dashcam footage. Nobody
connected the two until I said so directly — still hitting the
same choppy playback, and pointing out that other sites manage this
fine, we should too. With ffmpeg already sitting there,
transcoding every uploaded photo and video into a web-friendly
rendition on ingest — scaled down, H.264, faststart —
turned into a straightforward build, plus a retroactive pass over the
entire existing backlog.
I didn't take the first settings that worked, either. I asked for real
side-by-side comparison sets — regular trip video and dashcam
video both — cut at a few different quality levels, and picked
from actually watching them on my own tablet rather than trusting a
CRF number on paper. The dashcam settings I picked ended up wider than
the pipeline's first default (1280px instead of 854px for the front
camera, full width for the rear, no artificial frame-rate cap) —
the first pass at “good enough” undersold what the footage
could actually look like.
A quality bump exposed a second bug
Bigger files uncovered a second bug the smaller ones had been quietly
hiding: the transcoded output was missing -movflags
+faststart, so each MP4's own index sat at the end of the file
instead of the front. Harmless on a small clip. Genuinely painful once
the quality bump made every file 2.5 to 6 times bigger, because the
browser now had to seek all the way to the end of a much bigger file
just to read its own metadata before it could even show a preview. I
had it fixed without touching a single re-encode — a fast remux,
just re-ordering the existing bytes, took about half a second per file
and covered the whole backlog, 241 of 241. The bug itself had probably
been there the whole time; the quality improvement is just what made
it visible enough to notice.
The pattern underneath all of it. None of these were
exotic bugs. They were all versions of the same thing: a spec, a
guide, or a memorized default turning out to be stale, incomplete, or
simply untrue the moment it met a real device, a real file, or a real
second phone. The fix, every time, was the same discipline —
get the real artifact (the real Timeline export, the real photo, the
real tablet, a second real phone), test against it directly, and only
build the narrow thing that real evidence actually supports. Nothing
here was clever. It was just consistent.
And none of it had a shortcut. On a laptop, half of these would have
been a five-second local check — plug in the phone, open dev
tools, try it. Here, every one of them meant round-tripping through the
VM: SFTP a real file over, run the parser, delete it after; install a
debug APK on the actual tablet because there was no emulator to fall
back to; build a whole second cloud VM just to get a Java toolchain
that wasn't going to run on the primary box. The tablet-and-VM
constraint didn't make the project harder in the way you'd expect
— it just meant there was never a fake environment to be fooled
by. Whatever worked, worked on the real thing, because the real thing
was the only thing available.
Where it stands today — the real trip this whole project has been tracking, tracking itself.
This isn't a wrap-up post — Trip Log is still actively being
built. The native Android app now handles auth, gallery import (photos
and video), in-app live camera capture, and background location
tracking that survives the app being killed or the device rebooting; a
dashcam gallery matches GPS-less footage against the real trail and
lets me add clips to the map on request; every photo and video gets a
web-friendly transcoded rendition on ingest, faststart and all. A real
in-app map view, editing or deleting a bad pin, and letting more than
one person contribute to a trip are all still sketched but not built.
Whatever the next round of little things turns out to be, it'll get
added here rather than starting a new post.