{"id":182,"date":"2026-10-19T15:15:48","date_gmt":"2026-10-19T14:15:48","guid":{"rendered":"https:\/\/sripuwath.com\/index.php\/2026\/10\/19\/donncha-travel-steps-is-a-travel-journal\/"},"modified":"2026-10-19T15:15:48","modified_gmt":"2026-10-19T14:15:48","slug":"donncha-travel-steps-is-a-travel-journal","status":"publish","type":"post","link":"https:\/\/sripuwath.com\/index.php\/2026\/10\/19\/donncha-travel-steps-is-a-travel-journal\/","title":{"rendered":"Donncha: Travel Steps is a travel journal"},"content":{"rendered":"<p class=\"wp-block-paragraph\">I made a WordPress plugin for my holidays. It\u2019s called Travel Steps, and it turns a trip into a page with a map down one side and the story of each day down the other.<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" alt=\"\" class=\"wp-image-89593171 not-transparent\" height=\"1250\" src=\"https:\/\/odd.blog\/files\/2026\/10\/01-trip-header.avif\" width=\"2000\" \/><\/figure>\n<\/div>\n<p class=\"wp-block-paragraph\">Every photo I take ends up in <a href=\"https:\/\/immich.app\/\">Immich<\/a>, on my own server. My phone sends where I\u2019ve been to <a href=\"https:\/\/dawarich.app\/\">Dawarich<\/a>, on the same server. So after a trip I have all the photos and the whole route, sitting in two apps on my private server. The travel apps that make nice trip pages want your photos and your location on their servers. I\u2019d rather keep them on mine.<\/p>\n<h2 class=\"wp-block-heading\">How it works<\/h2>\n<p class=\"wp-block-paragraph\">You make a trip, give it a first and last day, and press Sync. Travel Steps asks Dawarich for your location over those days, and Immich for the photos you took. You can pick one Immich album instead. If you don\u2019t use Immich at all, upload the photos to the trip. It will detect the start and end dates from the uploaded photos, or an Immich album too.<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" alt=\"\" class=\"wp-image-89593182 not-transparent\" height=\"1480\" src=\"https:\/\/odd.blog\/files\/2026\/10\/08-sync-and-steps.avif\" width=\"1520\" \/><figcaption class=\"wp-element-caption\">One sync, and a step for each day.<\/figcaption><\/figure>\n<\/div>\n<p class=\"wp-block-paragraph\">Sync makes a draft post for every day that has favourite photos. Each one is a \u201cstep\u201d. It splits the day into places too. A new place starts when a favourite is more than 2 km from the last one and in a different town. Each place gets a heading and a block of photos, so all that\u2019s left is the writing.<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" alt=\"\" class=\"wp-image-89593175 not-transparent\" height=\"1250\" src=\"https:\/\/odd.blog\/files\/2026\/10\/09-block-editor.avif\" width=\"2000\" \/><figcaption class=\"wp-element-caption\">Each day is a post you write in the block editor. The photos under each heading come straight from Immich.<\/figcaption><\/figure>\n<\/div>\n<p class=\"wp-block-paragraph\">Place names come from <a href=\"https:\/\/www.geonames.org\/\">GeoNames<\/a> data that ships with the plugin, so naming a town doesn\u2019t send your location anywhere. Immich only names a place from a photo\u2019s own GPS. A photo from a camera without GPS gets placed from your Dawarich history instead, and named the same way.<\/p>\n<p class=\"wp-block-paragraph\">Publish the days you want people to see. Drafts stay private, and so do their photos.<\/p>\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" alt=\"\" class=\"wp-image-89593174 not-transparent\" height=\"1250\" src=\"https:\/\/odd.blog\/files\/2026\/10\/02-map-timeline.avif\" width=\"2000\" \/><figcaption class=\"wp-element-caption\">The map follows you as you scroll. Get to day 3 and day 3\u2019s markers light up.<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">The distance at the top follows the whole route, flights included. GPS glitches that put you in the Atlantic for a second are dropped. Anything you\u2019d have needed to travel faster than 1,080 km\/h to reach counts as a glitch. Sorry, Concorde.<\/p>\n<h2 class=\"wp-block-heading\">Your photos stay at home<\/h2>\n<p class=\"wp-block-paragraph\">Visitors never talk to Immich. WordPress fetches each photo, caches it and serves it itself, so Immich and the original files stay private. It caches a trip\u2019s photos in the background after a sync. The trip keeps working when Immich is down. A daily sweep deletes cached photos older than a number of days you choose, whether a trip still uses them or not, and they\u2019re fetched again the next time someone looks.<\/p>\n<p class=\"wp-block-paragraph\">Photos you upload to a trip keep their GPS in the original, but it\u2019s stripped from every copy WordPress makes, and only those copies are ever sent. A trip can have a password, and you can let some logged-in people skip it.<\/p>\n<h2 class=\"wp-block-heading\">Pin, hide, cover<\/h2>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" alt=\"\" class=\"wp-image-89593176 not-transparent\" height=\"1060\" src=\"https:\/\/odd.blog\/files\/2026\/10\/03-photo-tools.avif\" width=\"1300\" \/><\/figure>\n<\/div>\n<p class=\"wp-block-paragraph\">Hover over a photo to pin it (always show it), hide it, or make it the trip\u2019s cover. The rest of the day\u2019s photos sit behind a \u201cShow more photos\u201d link.<\/p>\n<p class=\"wp-block-paragraph\">On phones, those buttons show up after you press Edit photos as illustrated by the images below.<\/p>\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" alt=\"\" class=\"wp-image-89593177 not-transparent\" height=\"2000\" src=\"https:\/\/odd.blog\/files\/2026\/10\/04-phone-trip.avif\" width=\"924\" \/><\/figure>\n<\/div>\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" alt=\"\" class=\"wp-image-89593178 not-transparent\" height=\"2000\" src=\"https:\/\/odd.blog\/files\/2026\/10\/05-phone-edit-photos.avif\" width=\"924\" \/><\/figure>\n<\/div>\n<\/div>\n<p class=\"wp-block-paragraph\">Tap a photo and it opens full screen. On a phone you swipe from one photo to the next, edge to edge. On a desktop there are arrows, and the arrow keys work too.<\/p>\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" alt=\"\" class=\"wp-image-89593185 not-transparent\" height=\"2000\" src=\"https:\/\/odd.blog\/files\/2026\/10\/07-phone-lightbox.avif\" width=\"924\" \/><\/figure>\n<\/div>\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" alt=\"\" class=\"wp-image-89593179 not-transparent\" height=\"2000\" src=\"https:\/\/odd.blog\/files\/2026\/10\/06-phone-lightbox-swipe.avif\" width=\"924\" \/><\/figure>\n<\/div>\n<\/div>\n<h2 class=\"wp-block-heading\">Try it<\/h2>\n<p class=\"wp-block-paragraph\">Here\u2019s a <a href=\"https:\/\/playground.wordpress.net\/?blueprint-url=https:\/\/raw.githubusercontent.com\/donnchawp\/travel-steps\/master\/demo\/blueprint.json\">demo in WordPress Playground<\/a>. It\u2019s a week in the west of Ireland, running in your browser that installs nothing. Stand-in Immich and Dawarich servers feed the real sync code with photos from Unsplash. You\u2019re logged in as an admin, so press Sync, pin and hide photos, and edit the days in the block editor. It all disappears when you close the tab.<\/p>\n<p class=\"wp-block-paragraph\">The code is <a href=\"https:\/\/github.com\/donnchawp\/travel-steps\">on GitHub<\/a>. To install it, download <code>travel-steps.zip<\/code> from the <a href=\"https:\/\/github.com\/donnchawp\/travel-steps\/releases\/latest\">latest release<\/a> and upload it under Plugins \u2192 Add New. It needs WordPress 6.7 and PHP 8.0. Immich and Dawarich are both optional. It has been submitted to WordPress.org but if you read this blog, you\u2019ll know how to install a plugin from a zip file!<\/p>\n<h2 class=\"wp-block-heading\">WpApp<\/h2>\n<p class=\"wp-block-paragraph\">Travel Steps is built on <a href=\"https:\/\/github.com\/akirk\/wp-app\">WpApp<\/a>, Alex Kirk\u2019s framework for building apps on top of WordPress.<\/p>\n<p class=\"wp-block-paragraph\">A plugin usually adds screens to wp-admin, or puts its pages inside your theme and hopes the CSS gets along. A WpApp app gets its own URLs instead (<code>\/travel-steps\/<\/code> here), and its pages are plain HTML with no theme in the way. WordPress still does the boring work: users and logins, roles, the block editor, the REST API, cron and the database. WpApp adds the bits in between:<\/p>\n<ul class=\"wp-block-list\">\n<li>URL routing, using WordPress rewrite rules,<\/li>\n<li>pages that don\u2019t load your theme,<\/li>\n<li>the admin bar on every page, with the app\u2019s own icon (the green map at the top of my screenshots),<\/li>\n<li>access control using WordPress capabilities.<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">You can start one with <code>composer create-project akirk\/create-wp-app my-app<\/code>. Alex has a <a href=\"https:\/\/wpapps.kirk.at\/\">showcase of apps built with it<\/a>, including a cookbook and a family wiki.<\/p>\n<h2 class=\"wp-block-heading\">Built with Claude Code<\/h2>\n<p class=\"wp-block-paragraph\">I wrote Travel Steps with <a href=\"https:\/\/claude.ai\/code\">Claude Code<\/a>. The first commit was on 11 October, and there are many more now. Most of the work went round the same loop. I file an issue, Claude fixes it test-first on a branch and opens a pull request, and other agents review it. I deploy it to my own server and try it. The tests run in WordPress Playground too, the same as the demo.<\/p>\n<p class=\"wp-block-paragraph\">If you run Immich, give it a whirl. Bug reports welcome on GitHub <img decoding=\"async\" alt=\"\ud83d\ude42\" class=\"wp-smiley\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/1f642.png\" \/><\/p>\n<p class=\"has-small-font-size wp-block-paragraph\">Demo photos from <a href=\"https:\/\/unsplash.com\/\">Unsplash<\/a>: Cliffs of Moher by <a href=\"https:\/\/unsplash.com\/photos\/Zf9bvJ6182k\">Jarek Skowron<\/a>, Temple Bar by <a href=\"https:\/\/unsplash.com\/photos\/-ABmxr3Yo4Y\">Sarah<\/a>, Galway by Daniel Zbroja and Anna Hunko, Kilronan by Jonathan Borba. Map \u00a9 <a href=\"https:\/\/www.openstreetmap.org\/copyright\">OpenStreetMap<\/a> contributors.<\/p>\n<p>#TravelSteps #WordPress #WordPressplugin<\/p>","protected":false},"excerpt":{"rendered":"<p>I made a WordPress plugin for my holidays. It\u2019s called Travel Steps, and it turns a trip into a page with a map down one side and the story of [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":183,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-182","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry"],"_links":{"self":[{"href":"https:\/\/sripuwath.com\/index.php\/wp-json\/wp\/v2\/posts\/182","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sripuwath.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sripuwath.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sripuwath.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sripuwath.com\/index.php\/wp-json\/wp\/v2\/comments?post=182"}],"version-history":[{"count":0,"href":"https:\/\/sripuwath.com\/index.php\/wp-json\/wp\/v2\/posts\/182\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sripuwath.com\/index.php\/wp-json\/wp\/v2\/media\/183"}],"wp:attachment":[{"href":"https:\/\/sripuwath.com\/index.php\/wp-json\/wp\/v2\/media?parent=182"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sripuwath.com\/index.php\/wp-json\/wp\/v2\/categories?post=182"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sripuwath.com\/index.php\/wp-json\/wp\/v2\/tags?post=182"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}