From baa39a81a0732e95669e40a2e096bb7554cb7b23 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 12 Oct 2026 14:30:01 +0000 Subject: [PATCH 1/4] Add /travel-journal-app-with-photos public landing page Publish a photos-first travel journal page that stays explicit: JourneyDeck is built around recorded drives, not a general trip diary. Co-authored-by: Patrick --- server/src/app.ts | 4 +- server/tests/api.test.ts | 20 +++- server/tests/campaign.test.ts | 1 + server/tests/verify-live.test.ts | 1 + tools/verify-live/FEATURE_MAP.md | 3 +- tools/verify-live/expectations.json | 1 + web/apple-music-soundtrack.html | 2 +- web/beta.html | 2 +- web/private-driving-journal.html | 2 +- web/sitemap.xml | 1 + web/travel-journal-app-with-photos.html | 133 ++++++++++++++++++++++++ 11 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 web/travel-journal-app-with-photos.html diff --git a/server/src/app.ts b/server/src/app.ts index 8a9ce010..963f1813 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -22,7 +22,7 @@ import { loadTessieRouteCoordinates } from "./tessie-route.js"; declare module "fastify" { interface FastifyRequest { principal: Principal | null } } const googleSiteVerificationPath = "/google8e611381b21882a8.html"; -const publicPaths = new Set(["/healthz", "/readyz", "/login", "/login.html", "/privacy", "/privacy.html", "/apple-music-soundtrack", "/private-driving-journal", "/driving-journal", "/support", "/support.html", "/terms", "/terms.html", "/robots.txt", "/.well-known/api-catalog", "/openapi.json", "/api-docs", "/auth.md", "/sitemap.xml", "/manifest.webmanifest", "/favicon.ico", googleSiteVerificationPath]); +const publicPaths = new Set(["/healthz", "/readyz", "/login", "/login.html", "/privacy", "/privacy.html", "/apple-music-soundtrack", "/private-driving-journal", "/driving-journal", "/travel-journal-app-with-photos", "/support", "/support.html", "/terms", "/terms.html", "/robots.txt", "/.well-known/api-catalog", "/openapi.json", "/api-docs", "/auth.md", "/sitemap.xml", "/manifest.webmanifest", "/favicon.ico", googleSiteVerificationPath]); const publicAuthPaths = new Set(["/api/auth/login", "/api/auth/passkey/options", "/api/auth/passkey/verify"]); const scheduledSyncPath = "/api/spotify/sync"; const securityHeaders = { @@ -305,7 +305,7 @@ export async function createApp(overrides: CreateAppOverrides = {}) { app.get(retiredLandingPath, async (_req, reply) => reply.code(404).header("x-robots-tag", "noindex, nofollow").sendFile("404.html")); } } - app.get("/", async (_req, reply) => reply.sendFile(cfg.mode === "web" ? "beta.html" : "index.html")); app.get("/app", async (_req, reply) => reply.sendFile("index.html")); app.get("/spotify-callback", async (_req, reply) => reply.sendFile("index.html")); app.get("/login", async (_req, reply) => reply.sendFile("login.html")); app.get("/.well-known/api-catalog", async (_req, reply) => reply.type("application/linkset+json").send(await readFile(join(cfg.webRoot, "api-catalog.json")))); app.get("/api-docs", async (_req, reply) => reply.sendFile("api-docs.html")); app.get("/privacy", async (_req, reply) => reply.sendFile("privacy.html")); app.get("/apple-music-soundtrack", async (_req, reply) => reply.sendFile("apple-music-soundtrack.html")); app.get("/private-driving-journal", async (_req, reply) => reply.sendFile("private-driving-journal.html")); app.get("/driving-journal", async (_req, reply) => reply.code(301).redirect("/private-driving-journal")); app.get("/support", async (_req, reply) => reply.sendFile("support.html")); app.get("/terms", async (_req, reply) => reply.sendFile("terms.html")); app.get("/wife", async (_req, reply) => reply.sendFile("wife.html")); + app.get("/", async (_req, reply) => reply.sendFile(cfg.mode === "web" ? "beta.html" : "index.html")); app.get("/app", async (_req, reply) => reply.sendFile("index.html")); app.get("/spotify-callback", async (_req, reply) => reply.sendFile("index.html")); app.get("/login", async (_req, reply) => reply.sendFile("login.html")); app.get("/.well-known/api-catalog", async (_req, reply) => reply.type("application/linkset+json").send(await readFile(join(cfg.webRoot, "api-catalog.json")))); app.get("/api-docs", async (_req, reply) => reply.sendFile("api-docs.html")); app.get("/privacy", async (_req, reply) => reply.sendFile("privacy.html")); app.get("/apple-music-soundtrack", async (_req, reply) => reply.sendFile("apple-music-soundtrack.html")); app.get("/private-driving-journal", async (_req, reply) => reply.sendFile("private-driving-journal.html")); app.get("/travel-journal-app-with-photos", async (_req, reply) => reply.sendFile("travel-journal-app-with-photos.html")); app.get("/driving-journal", async (_req, reply) => reply.code(301).redirect("/private-driving-journal")); app.get("/support", async (_req, reply) => reply.sendFile("support.html")); app.get("/terms", async (_req, reply) => reply.sendFile("terms.html")); app.get("/wife", async (_req, reply) => reply.sendFile("wife.html")); app.get(googleSiteVerificationPath, async (_req, reply) => reply.type("text/html; charset=utf-8").send(await readFile(join(cfg.webRoot, "google8e611381b21882a8.html"), "utf8"))); app.setNotFoundHandler(async (req, reply) => { const requestPath = req.url.split("?")[0]; diff --git a/server/tests/api.test.ts b/server/tests/api.test.ts index 3f1350a5..8e3538a0 100644 --- a/server/tests/api.test.ts +++ b/server/tests/api.test.ts @@ -110,13 +110,25 @@ test("public information and discovery pages are accessible without an authentic assert.match(journal.body, /href="\/apple-music-soundtrack"/); assert.match(journal.body, /https:\/\/apps\.apple\.com\/us\/app\/journeydeck\/id6806502526/); assert.match(journal.body, /rel="canonical" href="https:\/\/journeydeck\.me\/private-driving-journal"/); + const travelPhotos = await runtime.app.inject({ method: "GET", url: "/travel-journal-app-with-photos" }); + assert.equal(travelPhotos.statusCode, 200, travelPhotos.body); + assert.match(String(travelPhotos.headers["content-type"]), /text\/html/); + assert.match(travelPhotos.body, /

Travel journal app with photos for iPhone<\/h1>/); + assert.match(travelPhotos.body, /built around the drives you choose to record/); + assert.match(travelPhotos.body, /

Frequently asked questions<\/h2>/); + assert.match(travelPhotos.body, /Is there a travel journal app with photos for iPhone\?/); + assert.match(travelPhotos.body, /href="\/privacy"/); + assert.match(travelPhotos.body, /href="\/apple-music-soundtrack"/); + assert.match(travelPhotos.body, /https:\/\/apps\.apple\.com\/us\/app\/journeydeck\/id6806502526/); + assert.match(travelPhotos.body, /rel="canonical" href="https:\/\/journeydeck\.me\/travel-journal-app-with-photos"/); + assert.doesNotMatch(travelPhotos.body, /[\u2013\u2014]/); const journalAlias = await runtime.app.inject({ method: "GET", url: "/driving-journal" }); assert.equal(journalAlias.statusCode, 301); assert.equal(journalAlias.headers.location, "/private-driving-journal"); const terms = await runtime.app.inject({ method: "GET", url: "/terms" }); assert.equal(terms.statusCode, 200, terms.body); assert.match(terms.body, /JourneyDeck Terms of Use/i); - for (const page of [privacy, support, soundtrack, journal, terms]) { + for (const page of [privacy, support, soundtrack, journal, travelPhotos, terms]) { assert.match(page.body, /class="site-header"/); assert.match(page.body, /class="desktop-nav" aria-label="Main navigation"/); assert.match(page.body, /href="\/apple-music-soundtrack"/); @@ -135,6 +147,7 @@ test("public information and discovery pages are accessible without an authentic assert.doesNotMatch(robots.body, /Disallow: \/google8e611381b21882a8\.html/i); assert.equal(googleRobotsAllowed(robots.body, "/apple-music-soundtrack"), true); assert.equal(googleRobotsAllowed(robots.body, "/private-driving-journal"), true); + assert.equal(googleRobotsAllowed(robots.body, "/travel-journal-app-with-photos"), true); assert.equal(googleRobotsAllowed(robots.body, "/google8e611381b21882a8.html"), true); assert.equal(googleRobotsAllowed(robots.body, "/login"), true); assert.equal(googleRobotsAllowed(robots.body, "/app"), false); @@ -157,6 +170,7 @@ test("public information and discovery pages are accessible without an authentic assert.match(sitemap.body, /https:\/\/journeydeck\.me\/terms<\/loc>/i); assert.match(sitemap.body, /https:\/\/journeydeck\.me\/apple-music-soundtrack<\/loc>/i); assert.match(sitemap.body, /https:\/\/journeydeck\.me\/private-driving-journal<\/loc>/i); + assert.match(sitemap.body, /https:\/\/journeydeck\.me\/travel-journal-app-with-photos<\/loc>/i); assert.doesNotMatch(sitemap.body, /https:\/\/journeydeck\.me\/(?:beta|app|login)(?:\/|<)/i); assert.doesNotMatch(sitemap.body, /google8e611381b21882a8/i); const googleVerification = await runtime.app.inject({ method: "GET", url: "/google8e611381b21882a8.html" }); @@ -200,6 +214,7 @@ test("hosted root serves the Grand Touring launch page while private routes stay assert.match(landing.body, /class="feature-cta" href="\/private-driving-journal">See the private driving journal overview/); assert.match(landing.body, /id="replay"/); assert.match(landing.body, /id="memories"/); + assert.match(landing.body, /class="feature-cta" href="\/travel-journal-app-with-photos">See the travel journal with photos/); assert.match(landing.body, /id="membership"/); assert.match(landing.body, /Ask JourneyDeck/); assert.match(landing.body, /Journey Markers/); @@ -310,11 +325,12 @@ test("public marketing pages expose one title, description, canonical, Open Grap { url: "/", canonical: "https://journeydeck.me/" }, { url: "/apple-music-soundtrack", canonical: "https://journeydeck.me/apple-music-soundtrack" }, { url: "/private-driving-journal", canonical: "https://journeydeck.me/private-driving-journal" }, + { url: "/travel-journal-app-with-photos", canonical: "https://journeydeck.me/travel-journal-app-with-photos" }, { url: "/support", canonical: "https://journeydeck.me/support" }, { url: "/privacy", canonical: "https://journeydeck.me/privacy" }, { url: "/terms", canonical: "https://journeydeck.me/terms" } ] as const; - const rewrittenDescriptions = new Set(["/support", "/privacy", "/private-driving-journal"]); + const rewrittenDescriptions = new Set(["/support", "/privacy", "/private-driving-journal", "/travel-journal-app-with-photos"]); try { for (const page of pages) { const response = await runtime.app.inject({ method: "GET", url: page.url }); diff --git a/server/tests/campaign.test.ts b/server/tests/campaign.test.ts index acb457dc..d6fdeb85 100644 --- a/server/tests/campaign.test.ts +++ b/server/tests/campaign.test.ts @@ -28,6 +28,7 @@ const publicPages = [ "/", "/apple-music-soundtrack", "/private-driving-journal", + "/travel-journal-app-with-photos", "/support", "/privacy", "/terms" diff --git a/server/tests/verify-live.test.ts b/server/tests/verify-live.test.ts index 1d5b1b57..82f1a931 100644 --- a/server/tests/verify-live.test.ts +++ b/server/tests/verify-live.test.ts @@ -56,6 +56,7 @@ test("expectations loader reads the checked-in file and rejects incomplete objec "/", "/apple-music-soundtrack", "/private-driving-journal", + "/travel-journal-app-with-photos", "/support", "/privacy", "/terms" diff --git a/tools/verify-live/FEATURE_MAP.md b/tools/verify-live/FEATURE_MAP.md index cff3b677..9b744b13 100644 --- a/tools/verify-live/FEATURE_MAP.md +++ b/tools/verify-live/FEATURE_MAP.md @@ -23,12 +23,13 @@ Public HTML is served by `server/src/app.ts` in web mode. The public route allow | `/` | `web/beta.html` via `server/src/app.ts` `GET /` | `page:/ status`, `page:/ title`, `page:/ meta-description`, `page:/ canonical`, `page:/ og:title`, `page:/ og:description`, `page:/ og:url`, `page:/ og:image`, `page:/ json-ld`, `page:/ umami`, `page:/ trial-wording` | | `/apple-music-soundtrack` | `web/apple-music-soundtrack.html` | `page:/apple-music-soundtrack status`, `page:/apple-music-soundtrack title`, `page:/apple-music-soundtrack meta-description`, `page:/apple-music-soundtrack canonical`, `page:/apple-music-soundtrack og:title`, `page:/apple-music-soundtrack og:description`, `page:/apple-music-soundtrack og:url`, `page:/apple-music-soundtrack og:image`, `page:/apple-music-soundtrack json-ld`, `page:/apple-music-soundtrack umami`, `page:/apple-music-soundtrack trial-wording` | | `/private-driving-journal` | `web/private-driving-journal.html` | `page:/private-driving-journal status`, `page:/private-driving-journal title`, `page:/private-driving-journal meta-description`, `page:/private-driving-journal canonical`, `page:/private-driving-journal og:title`, `page:/private-driving-journal og:description`, `page:/private-driving-journal og:url`, `page:/private-driving-journal og:image`, `page:/private-driving-journal json-ld`, `page:/private-driving-journal umami`, `page:/private-driving-journal trial-wording` | +| `/travel-journal-app-with-photos` | `web/travel-journal-app-with-photos.html` | `page:/travel-journal-app-with-photos status`, `page:/travel-journal-app-with-photos title`, `page:/travel-journal-app-with-photos meta-description`, `page:/travel-journal-app-with-photos canonical`, `page:/travel-journal-app-with-photos og:title`, `page:/travel-journal-app-with-photos og:description`, `page:/travel-journal-app-with-photos og:url`, `page:/travel-journal-app-with-photos og:image`, `page:/travel-journal-app-with-photos json-ld`, `page:/travel-journal-app-with-photos umami`, `page:/travel-journal-app-with-photos trial-wording` | | `/support` | `web/support.html` | `page:/support status`, `page:/support title`, `page:/support meta-description`, `page:/support canonical`, `page:/support og:title`, `page:/support og:description`, `page:/support og:url`, `page:/support og:image`, `page:/support json-ld`, `page:/support umami`, `page:/support trial-wording` | | `/privacy` | `web/privacy.html` | `page:/privacy status`, `page:/privacy title`, `page:/privacy meta-description`, `page:/privacy canonical`, `page:/privacy og:title`, `page:/privacy og:description`, `page:/privacy og:url`, `page:/privacy og:image`, `page:/privacy json-ld`, `page:/privacy umami`, `page:/privacy trial-wording` | | `/terms` | `web/terms.html` | `page:/terms status`, `page:/terms title`, `page:/terms meta-description`, `page:/terms canonical`, `page:/terms og:title`, `page:/terms og:description`, `page:/terms og:url`, `page:/terms og:image`, `page:/terms json-ld`, `page:/terms umami`, `page:/terms trial-wording` | | `/login` | `web/login.html` | `page:/login status`, `page:/login umami` | -JSON-LD blocks live in `web/beta.html`, `web/apple-music-soundtrack.html`, `web/private-driving-journal.html`, and `web/support.html`. Privacy and terms have none today. Every `application/ld+json` block that is present must parse. +JSON-LD blocks live in `web/beta.html`, `web/apple-music-soundtrack.html`, `web/private-driving-journal.html`, `web/travel-journal-app-with-photos.html`, and `web/support.html`. Privacy and terms have none today. Every `application/ld+json` block that is present must parse. ## Umami tag diff --git a/tools/verify-live/expectations.json b/tools/verify-live/expectations.json index f96adb97..56fc2831 100644 --- a/tools/verify-live/expectations.json +++ b/tools/verify-live/expectations.json @@ -3,6 +3,7 @@ "/", "/apple-music-soundtrack", "/private-driving-journal", + "/travel-journal-app-with-photos", "/support", "/privacy", "/terms" diff --git a/web/apple-music-soundtrack.html b/web/apple-music-soundtrack.html index a91d97d2..ec84faa1 100644 --- a/web/apple-music-soundtrack.html +++ b/web/apple-music-soundtrack.html @@ -105,7 +105,7 @@

Is JourneyDeck a navigation app?

No. Set up recording before driving and do not interact with the app while operating a vehicle.

- + diff --git a/web/beta.html b/web/beta.html index 7714b1ec..37a89064 100644 --- a/web/beta.html +++ b/web/beta.html @@ -99,7 +99,7 @@

Take the road.
Keep the story.<
02 / THE SOUNDTRACK

The song takes
you right back.

Record a drive, then revisit it with an optional soundtrack matched from your Apple Music listening history. Hear a song you want to keep? Tap Identify Song to recognize it with ShazamKit. The microphone listens briefly for that request; audio is never saved.

Explore Apple Music soundtracks
-
03 / THE MEMORY

Make a collection
of good days.

Group related drives into Memories and add your own photos and notes.

+
diff --git a/web/private-driving-journal.html b/web/private-driving-journal.html index 05b647c7..3e749cc2 100644 --- a/web/private-driving-journal.html +++ b/web/private-driving-journal.html @@ -92,7 +92,7 @@

How it works

  • Download JourneyDeck and allow Location when you want route recording.
  • Tap Start Journey on Home (or use Siri or Apple Watch). Put the phone away; the route keeps recording while locked.
  • When parked, end the journey. Explore the route on your device.
  • -
  • Optionally connect Apple Music for a soundtrack, or group drives into a Memory with photos and notes.
  • +
  • Optionally connect Apple Music for a soundtrack, or group drives into a Memory with photos and notes. See the travel journal app with photos.
  • diff --git a/web/sitemap.xml b/web/sitemap.xml index 7c620909..bf93df7f 100644 --- a/web/sitemap.xml +++ b/web/sitemap.xml @@ -3,6 +3,7 @@ https://journeydeck.me/2026-09-16 https://journeydeck.me/apple-music-soundtrack2026-09-23 https://journeydeck.me/private-driving-journal2026-09-24 + https://journeydeck.me/travel-journal-app-with-photos2026-10-12 https://journeydeck.me/privacy2026-09-13 https://journeydeck.me/support2026-09-13 https://journeydeck.me/terms2026-09-13 diff --git a/web/travel-journal-app-with-photos.html b/web/travel-journal-app-with-photos.html new file mode 100644 index 00000000..9e81d839 --- /dev/null +++ b/web/travel-journal-app-with-photos.html @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Travel journal app with photos for iPhone | JourneyDeck + + + + + + +
    +
    +

    PHOTOS, NOTES, THE ROAD

    +

    Travel journal app with photos for iPhone

    +

    JourneyDeck is a travel journal app with photos for iPhone, built around the drives you choose to record. After a journey, bring related drives into a Memory, add your own photos and notes, and revisit the route on a map. Journey Markers let you drop a note and photos along the way. It is not a general travel diary for flights or hotel stays. It is a private driving journal that keeps the roads, photos, and optional Apple Music soundtrack together.

    + +
    +
    +

    MEMORIES / JOURNEYDECK 4.0

    Keep the whole story.

    Bring related drives into a Memory, then add your own photos and notes. Review photo suggestions before adding anything to your archive.

    +
    JourneyDeck 4.0 screenshot showing a Memory with photos, notes, and saved journeys
    JourneyDeck 4.0 Memories on iPhone
    +
    +
    +
    +

    Photos sit with the drive

    +

    JourneyDeck is a private driving journal first. You tap Start Journey on iPhone, save the route, then keep pictures of the day with that road. Group related journeys into Memories. Add notes. Replay the map of the roads you have driven.

    +
    +
    +

    How photos get into the journal

    +
      +
    1. Record a journey on iPhone. Your route, distance, and drive time stay on your device.
    2. +
    3. When safely parked, add notes and photos during a drive with Journey Markers, or add them later.
    4. +
    5. Bring related drives into a Memory and add your own photos and notes.
    6. +
    7. Review photo suggestions before adding anything to your archive.
    8. +
    +
    +
    +

    A travel diary with a map, for drives

    +

    If you searched for a travel diary app with a map, JourneyDeck shows the route you recorded, then lets photos and notes live with that journey. Optional Apple Music soundtracks match songs from your authorized listening history. Create a journey share card when you choose; sensitive Home, Work, and School places can be protected in the shared view.

    +

    Read the private driving journal overview and how Apple Music soundtracks work.

    +
    +
    +

    Frequently asked questions

    +

    Is there a travel journal app with photos for iPhone?

    +

    Yes. JourneyDeck lets you bring related drives into a Memory, then add your own photos and notes. It is a travel journal built around the drives you record on iPhone, not a general diary for flights or hotel stays.

    +

    Does JourneyDeck include a map?

    +

    Yes. After you record a journey, you can revisit its route on your device. Memories keep photos with the roads you have driven.

    +

    Can I add photos during a drive?

    +

    Add notes and photos during a drive with Journey Markers when you are safely parked. You can also add your own photos after the drive when you group journeys into a Memory. Review photo suggestions before adding anything to your archive.

    +

    Is this a general travel diary app?

    +

    No. JourneyDeck is a private driving journal. Photos, notes, and maps stay with the journeys you start on iPhone.

    +

    Do photos leave my device?

    +

    Your archive is stored on your device first. Optional private iCloud sync keeps your library available across your iPhone and iPad. Sign-in and iCloud sync are optional. You review photo suggestions before anything is added.

    +
    +
    + +
    + + From 50f9565e378b90ee28762bdd3de7a79e1a41db7f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 12 Oct 2026 14:50:48 +0000 Subject: [PATCH 2/4] Shorten the travel journal with photos intro Use the reviewed Memories copy and keep meta and FAQ JSON-LD aligned. Co-authored-by: Patrick --- server/tests/api.test.ts | 2 +- web/travel-journal-app-with-photos.html | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/tests/api.test.ts b/server/tests/api.test.ts index 8e3538a0..2888716b 100644 --- a/server/tests/api.test.ts +++ b/server/tests/api.test.ts @@ -114,7 +114,7 @@ test("public information and discovery pages are accessible without an authentic assert.equal(travelPhotos.statusCode, 200, travelPhotos.body); assert.match(String(travelPhotos.headers["content-type"]), /text\/html/); assert.match(travelPhotos.body, /

    Travel journal app with photos for iPhone<\/h1>/); - assert.match(travelPhotos.body, /built around the drives you choose to record/); + assert.match(travelPhotos.body, /Group your drives into Memories, then add your own photos and notes\. The route, the pictures and the soundtrack stay together, privately on your iPhone\./); assert.match(travelPhotos.body, /

    Frequently asked questions<\/h2>/); assert.match(travelPhotos.body, /Is there a travel journal app with photos for iPhone\?/); assert.match(travelPhotos.body, /href="\/privacy"/); diff --git a/web/travel-journal-app-with-photos.html b/web/travel-journal-app-with-photos.html index 9e81d839..eb998ede 100644 --- a/web/travel-journal-app-with-photos.html +++ b/web/travel-journal-app-with-photos.html @@ -4,12 +4,12 @@ - + - + @@ -36,7 +36,7 @@ "name": "Is there a travel journal app with photos for iPhone?", "acceptedAnswer": { "@type": "Answer", - "text": "Yes. JourneyDeck lets you bring related drives into a Memory, then add your own photos and notes. It is a travel journal built around the drives you record on iPhone, not a general diary for flights or hotel stays." + "text": "Yes. Group your drives into Memories, then add your own photos and notes. The route, the pictures and the soundtrack stay together, privately on your iPhone. It is a travel journal built around the drives you record on iPhone, not a general diary for flights or hotel stays." } }, { @@ -87,7 +87,7 @@

    PHOTOS, NOTES, THE ROAD

    Travel journal app with photos for iPhone

    -

    JourneyDeck is a travel journal app with photos for iPhone, built around the drives you choose to record. After a journey, bring related drives into a Memory, add your own photos and notes, and revisit the route on a map. Journey Markers let you drop a note and photos along the way. It is not a general travel diary for flights or hotel stays. It is a private driving journal that keeps the roads, photos, and optional Apple Music soundtrack together.

    +

    Group your drives into Memories, then add your own photos and notes. The route, the pictures and the soundtrack stay together, privately on your iPhone.

    @@ -116,7 +116,7 @@

    A travel diary with a map, for drives

    Frequently asked questions

    Is there a travel journal app with photos for iPhone?

    -

    Yes. JourneyDeck lets you bring related drives into a Memory, then add your own photos and notes. It is a travel journal built around the drives you record on iPhone, not a general diary for flights or hotel stays.

    +

    Yes. Group your drives into Memories, then add your own photos and notes. The route, the pictures and the soundtrack stay together, privately on your iPhone. It is a travel journal built around the drives you record on iPhone, not a general diary for flights or hotel stays.

    Does JourneyDeck include a map?

    Yes. After you record a journey, you can revisit its route on your device. Memories keep photos with the roads you have driven.

    Can I add photos during a drive?

    From a1e9fcdddf9b6cb4ea1ef88998a2d5518f17ac43 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 12 Oct 2026 15:00:16 +0000 Subject: [PATCH 3/4] Retrigger CI for the reviewed travel journal copy Co-authored-by: Patrick From 86a9546941e18936807cda032ce88b335edeb9ed Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 12 Oct 2026 16:00:09 +0000 Subject: [PATCH 4/4] Keep #268 journal-app routes so this PR can land after it. Add the same journal-app lines and page, and place the travel journal entries after the tracker so later SEO PRs can merge in order. Co-authored-by: Patrick --- server/src/app.ts | 4 +- server/tests/api.test.ts | 49 ++++++---- server/tests/campaign.test.ts | 3 +- server/tests/verify-live.test.ts | 3 +- tools/verify-live/FEATURE_MAP.md | 5 +- tools/verify-live/expectations.json | 3 +- web/apple-music-soundtrack.html | 2 +- web/private-driving-journal.html | 2 + web/road-trip-journal-app.html | 147 ++++++++++++++++++++++++++++ web/sitemap.xml | 3 +- 10 files changed, 195 insertions(+), 26 deletions(-) create mode 100644 web/road-trip-journal-app.html diff --git a/server/src/app.ts b/server/src/app.ts index dd94bcd2..619aba55 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -22,7 +22,7 @@ import { loadTessieRouteCoordinates } from "./tessie-route.js"; declare module "fastify" { interface FastifyRequest { principal: Principal | null } } const googleSiteVerificationPath = "/google8e611381b21882a8.html"; -const publicPaths = new Set(["/healthz", "/readyz", "/login", "/login.html", "/privacy", "/privacy.html", "/apple-music-soundtrack", "/private-driving-journal", "/driving-journal", "/travel-journal-app-with-photos", "/road-trip-tracker-app", "/support", "/support.html", "/terms", "/terms.html", "/robots.txt", "/.well-known/api-catalog", "/openapi.json", "/api-docs", "/auth.md", "/sitemap.xml", "/manifest.webmanifest", "/favicon.ico", googleSiteVerificationPath]); +const publicPaths = new Set(["/healthz", "/readyz", "/login", "/login.html", "/privacy", "/privacy.html", "/apple-music-soundtrack", "/private-driving-journal", "/driving-journal", "/road-trip-journal-app", "/road-trip-tracker-app", "/travel-journal-app-with-photos", "/support", "/support.html", "/terms", "/terms.html", "/robots.txt", "/.well-known/api-catalog", "/openapi.json", "/api-docs", "/auth.md", "/sitemap.xml", "/manifest.webmanifest", "/favicon.ico", googleSiteVerificationPath]); const publicAuthPaths = new Set(["/api/auth/login", "/api/auth/passkey/options", "/api/auth/passkey/verify"]); const scheduledSyncPath = "/api/spotify/sync"; const securityHeaders = { @@ -305,7 +305,7 @@ export async function createApp(overrides: CreateAppOverrides = {}) { app.get(retiredLandingPath, async (_req, reply) => reply.code(404).header("x-robots-tag", "noindex, nofollow").sendFile("404.html")); } } - app.get("/", async (_req, reply) => reply.sendFile(cfg.mode === "web" ? "beta.html" : "index.html")); app.get("/app", async (_req, reply) => reply.sendFile("index.html")); app.get("/spotify-callback", async (_req, reply) => reply.sendFile("index.html")); app.get("/login", async (_req, reply) => reply.sendFile("login.html")); app.get("/.well-known/api-catalog", async (_req, reply) => reply.type("application/linkset+json").send(await readFile(join(cfg.webRoot, "api-catalog.json")))); app.get("/api-docs", async (_req, reply) => reply.sendFile("api-docs.html")); app.get("/privacy", async (_req, reply) => reply.sendFile("privacy.html")); app.get("/apple-music-soundtrack", async (_req, reply) => reply.sendFile("apple-music-soundtrack.html")); app.get("/private-driving-journal", async (_req, reply) => reply.sendFile("private-driving-journal.html")); app.get("/travel-journal-app-with-photos", async (_req, reply) => reply.sendFile("travel-journal-app-with-photos.html")); app.get("/road-trip-tracker-app", async (_req, reply) => reply.sendFile("road-trip-tracker-app.html")); app.get("/driving-journal", async (_req, reply) => reply.code(301).redirect("/private-driving-journal")); app.get("/support", async (_req, reply) => reply.sendFile("support.html")); app.get("/terms", async (_req, reply) => reply.sendFile("terms.html")); app.get("/wife", async (_req, reply) => reply.sendFile("wife.html")); + app.get("/", async (_req, reply) => reply.sendFile(cfg.mode === "web" ? "beta.html" : "index.html")); app.get("/app", async (_req, reply) => reply.sendFile("index.html")); app.get("/spotify-callback", async (_req, reply) => reply.sendFile("index.html")); app.get("/login", async (_req, reply) => reply.sendFile("login.html")); app.get("/.well-known/api-catalog", async (_req, reply) => reply.type("application/linkset+json").send(await readFile(join(cfg.webRoot, "api-catalog.json")))); app.get("/api-docs", async (_req, reply) => reply.sendFile("api-docs.html")); app.get("/privacy", async (_req, reply) => reply.sendFile("privacy.html")); app.get("/apple-music-soundtrack", async (_req, reply) => reply.sendFile("apple-music-soundtrack.html")); app.get("/private-driving-journal", async (_req, reply) => reply.sendFile("private-driving-journal.html")); app.get("/road-trip-journal-app", async (_req, reply) => reply.sendFile("road-trip-journal-app.html")); app.get("/road-trip-tracker-app", async (_req, reply) => reply.sendFile("road-trip-tracker-app.html")); app.get("/travel-journal-app-with-photos", async (_req, reply) => reply.sendFile("travel-journal-app-with-photos.html")); app.get("/driving-journal", async (_req, reply) => reply.code(301).redirect("/private-driving-journal")); app.get("/support", async (_req, reply) => reply.sendFile("support.html")); app.get("/terms", async (_req, reply) => reply.sendFile("terms.html")); app.get("/wife", async (_req, reply) => reply.sendFile("wife.html")); app.get(googleSiteVerificationPath, async (_req, reply) => reply.type("text/html; charset=utf-8").send(await readFile(join(cfg.webRoot, "google8e611381b21882a8.html"), "utf8"))); app.setNotFoundHandler(async (req, reply) => { const requestPath = req.url.split("?")[0]; diff --git a/server/tests/api.test.ts b/server/tests/api.test.ts index 25c4dca2..3f8a7a3e 100644 --- a/server/tests/api.test.ts +++ b/server/tests/api.test.ts @@ -124,18 +124,18 @@ test("public information and discovery pages are accessible without an authentic assert.match(journal.body, /fetchpriority="high"/); assert.match(journal.body, /srcset="/); assert.doesNotMatch(journal.body, /class="hero-photo"[^>]*loading="lazy"/); - const travelPhotos = await runtime.app.inject({ method: "GET", url: "/travel-journal-app-with-photos" }); - assert.equal(travelPhotos.statusCode, 200, travelPhotos.body); - assert.match(String(travelPhotos.headers["content-type"]), /text\/html/); - assert.match(travelPhotos.body, /

    Travel journal app with photos for iPhone<\/h1>/); - assert.match(travelPhotos.body, /Group your drives into Memories, then add your own photos and notes\. The route, the pictures and the soundtrack stay together, privately on your iPhone\./); - assert.match(travelPhotos.body, /

    Frequently asked questions<\/h2>/); - assert.match(travelPhotos.body, /Is there a travel journal app with photos for iPhone\?/); - assert.match(travelPhotos.body, /href="\/privacy"/); - assert.match(travelPhotos.body, /href="\/apple-music-soundtrack"/); - assert.match(travelPhotos.body, /https:\/\/apps\.apple\.com\/us\/app\/journeydeck\/id6806502526/); - assert.match(travelPhotos.body, /rel="canonical" href="https:\/\/journeydeck\.me\/travel-journal-app-with-photos"/); - assert.doesNotMatch(travelPhotos.body, /[\u2013\u2014]/); + const roadTripJournal = await runtime.app.inject({ method: "GET", url: "/road-trip-journal-app" }); + assert.equal(roadTripJournal.statusCode, 200, roadTripJournal.body); + assert.match(String(roadTripJournal.headers["content-type"]), /text\/html/); + assert.match(roadTripJournal.body, /

    Road trip journal app for iPhone<\/h1>/); + assert.match(roadTripJournal.body, /A private road trip journal for iPhone\. Start a journey, and JourneyDeck keeps the route, the miles and the music\./); + assert.match(roadTripJournal.body, /

    Frequently asked questions<\/h2>/); + assert.match(roadTripJournal.body, /What is a road trip journal app\?/); + assert.match(roadTripJournal.body, /href="\/privacy"/); + assert.match(roadTripJournal.body, /href="\/apple-music-soundtrack"/); + assert.match(roadTripJournal.body, /https:\/\/apps\.apple\.com\/us\/app\/journeydeck\/id6806502526/); + assert.match(roadTripJournal.body, /rel="canonical" href="https:\/\/journeydeck\.me\/road-trip-journal-app"/); + assert.doesNotMatch(roadTripJournal.body, /[\u2013\u2014]/); const roadTripTracker = await runtime.app.inject({ method: "GET", url: "/road-trip-tracker-app" }); assert.equal(roadTripTracker.statusCode, 200, roadTripTracker.body); assert.match(String(roadTripTracker.headers["content-type"]), /text\/html/); @@ -148,13 +148,25 @@ test("public information and discovery pages are accessible without an authentic assert.match(roadTripTracker.body, /https:\/\/apps\.apple\.com\/us\/app\/journeydeck\/id6806502526/); assert.match(roadTripTracker.body, /rel="canonical" href="https:\/\/journeydeck\.me\/road-trip-tracker-app"/); assert.doesNotMatch(roadTripTracker.body, /[\u2013\u2014]/); + const travelPhotos = await runtime.app.inject({ method: "GET", url: "/travel-journal-app-with-photos" }); + assert.equal(travelPhotos.statusCode, 200, travelPhotos.body); + assert.match(String(travelPhotos.headers["content-type"]), /text\/html/); + assert.match(travelPhotos.body, /

    Travel journal app with photos for iPhone<\/h1>/); + assert.match(travelPhotos.body, /Group your drives into Memories, then add your own photos and notes\. The route, the pictures and the soundtrack stay together, privately on your iPhone\./); + assert.match(travelPhotos.body, /

    Frequently asked questions<\/h2>/); + assert.match(travelPhotos.body, /Is there a travel journal app with photos for iPhone\?/); + assert.match(travelPhotos.body, /href="\/privacy"/); + assert.match(travelPhotos.body, /href="\/apple-music-soundtrack"/); + assert.match(travelPhotos.body, /https:\/\/apps\.apple\.com\/us\/app\/journeydeck\/id6806502526/); + assert.match(travelPhotos.body, /rel="canonical" href="https:\/\/journeydeck\.me\/travel-journal-app-with-photos"/); + assert.doesNotMatch(travelPhotos.body, /[\u2013\u2014]/); const journalAlias = await runtime.app.inject({ method: "GET", url: "/driving-journal" }); assert.equal(journalAlias.statusCode, 301); assert.equal(journalAlias.headers.location, "/private-driving-journal"); const terms = await runtime.app.inject({ method: "GET", url: "/terms" }); assert.equal(terms.statusCode, 200, terms.body); assert.match(terms.body, /JourneyDeck Terms of Use/i); - for (const page of [privacy, support, soundtrack, journal, travelPhotos, roadTripTracker, terms]) { + for (const page of [privacy, support, soundtrack, journal, roadTripJournal, roadTripTracker, travelPhotos, terms]) { assert.match(page.body, /class="site-header"/); assert.match(page.body, /class="desktop-nav" aria-label="Main navigation"/); assert.match(page.body, /href="\/apple-music-soundtrack"/); @@ -173,8 +185,9 @@ test("public information and discovery pages are accessible without an authentic assert.doesNotMatch(robots.body, /Disallow: \/google8e611381b21882a8\.html/i); assert.equal(googleRobotsAllowed(robots.body, "/apple-music-soundtrack"), true); assert.equal(googleRobotsAllowed(robots.body, "/private-driving-journal"), true); - assert.equal(googleRobotsAllowed(robots.body, "/travel-journal-app-with-photos"), true); + assert.equal(googleRobotsAllowed(robots.body, "/road-trip-journal-app"), true); assert.equal(googleRobotsAllowed(robots.body, "/road-trip-tracker-app"), true); + assert.equal(googleRobotsAllowed(robots.body, "/travel-journal-app-with-photos"), true); assert.equal(googleRobotsAllowed(robots.body, "/google8e611381b21882a8.html"), true); assert.equal(googleRobotsAllowed(robots.body, "/login"), true); assert.equal(googleRobotsAllowed(robots.body, "/app"), false); @@ -197,8 +210,9 @@ test("public information and discovery pages are accessible without an authentic assert.match(sitemap.body, /https:\/\/journeydeck\.me\/terms<\/loc>/i); assert.match(sitemap.body, /https:\/\/journeydeck\.me\/apple-music-soundtrack<\/loc>/i); assert.match(sitemap.body, /https:\/\/journeydeck\.me\/private-driving-journal<\/loc>/i); - assert.match(sitemap.body, /https:\/\/journeydeck\.me\/travel-journal-app-with-photos<\/loc>/i); + assert.match(sitemap.body, /https:\/\/journeydeck\.me\/road-trip-journal-app<\/loc>/i); assert.match(sitemap.body, /https:\/\/journeydeck\.me\/road-trip-tracker-app<\/loc>/i); + assert.match(sitemap.body, /https:\/\/journeydeck\.me\/travel-journal-app-with-photos<\/loc>/i); assert.doesNotMatch(sitemap.body, /https:\/\/journeydeck\.me\/(?:beta|app|login)(?:\/|<)/i); assert.doesNotMatch(sitemap.body, /google8e611381b21882a8/i); const googleVerification = await runtime.app.inject({ method: "GET", url: "/google8e611381b21882a8.html" }); @@ -357,13 +371,14 @@ test("public marketing pages expose one title, description, canonical, Open Grap { url: "/", canonical: "https://journeydeck.me/" }, { url: "/apple-music-soundtrack", canonical: "https://journeydeck.me/apple-music-soundtrack" }, { url: "/private-driving-journal", canonical: "https://journeydeck.me/private-driving-journal" }, - { url: "/travel-journal-app-with-photos", canonical: "https://journeydeck.me/travel-journal-app-with-photos" }, + { url: "/road-trip-journal-app", canonical: "https://journeydeck.me/road-trip-journal-app" }, { url: "/road-trip-tracker-app", canonical: "https://journeydeck.me/road-trip-tracker-app" }, + { url: "/travel-journal-app-with-photos", canonical: "https://journeydeck.me/travel-journal-app-with-photos" }, { url: "/support", canonical: "https://journeydeck.me/support" }, { url: "/privacy", canonical: "https://journeydeck.me/privacy" }, { url: "/terms", canonical: "https://journeydeck.me/terms" } ] as const; - const rewrittenDescriptions = new Set(["/support", "/privacy", "/private-driving-journal", "/travel-journal-app-with-photos", "/road-trip-tracker-app"]); + const rewrittenDescriptions = new Set(["/support", "/privacy", "/private-driving-journal", "/road-trip-journal-app", "/road-trip-tracker-app", "/travel-journal-app-with-photos"]); try { for (const page of pages) { const response = await runtime.app.inject({ method: "GET", url: page.url }); diff --git a/server/tests/campaign.test.ts b/server/tests/campaign.test.ts index 127adb78..46881f57 100644 --- a/server/tests/campaign.test.ts +++ b/server/tests/campaign.test.ts @@ -28,8 +28,9 @@ const publicPages = [ "/", "/apple-music-soundtrack", "/private-driving-journal", - "/travel-journal-app-with-photos", + "/road-trip-journal-app", "/road-trip-tracker-app", + "/travel-journal-app-with-photos", "/support", "/privacy", "/terms" diff --git a/server/tests/verify-live.test.ts b/server/tests/verify-live.test.ts index 2b573131..cd232384 100644 --- a/server/tests/verify-live.test.ts +++ b/server/tests/verify-live.test.ts @@ -56,8 +56,9 @@ test("expectations loader reads the checked-in file and rejects incomplete objec "/", "/apple-music-soundtrack", "/private-driving-journal", - "/travel-journal-app-with-photos", + "/road-trip-journal-app", "/road-trip-tracker-app", + "/travel-journal-app-with-photos", "/support", "/privacy", "/terms" diff --git a/tools/verify-live/FEATURE_MAP.md b/tools/verify-live/FEATURE_MAP.md index 006a3a25..2de8beae 100644 --- a/tools/verify-live/FEATURE_MAP.md +++ b/tools/verify-live/FEATURE_MAP.md @@ -23,14 +23,15 @@ Public HTML is served by `server/src/app.ts` in web mode. The public route allow | `/` | `web/beta.html` via `server/src/app.ts` `GET /` | `page:/ status`, `page:/ title`, `page:/ meta-description`, `page:/ canonical`, `page:/ og:title`, `page:/ og:description`, `page:/ og:url`, `page:/ og:image`, `page:/ json-ld`, `page:/ umami`, `page:/ trial-wording` | | `/apple-music-soundtrack` | `web/apple-music-soundtrack.html` | `page:/apple-music-soundtrack status`, `page:/apple-music-soundtrack title`, `page:/apple-music-soundtrack meta-description`, `page:/apple-music-soundtrack canonical`, `page:/apple-music-soundtrack og:title`, `page:/apple-music-soundtrack og:description`, `page:/apple-music-soundtrack og:url`, `page:/apple-music-soundtrack og:image`, `page:/apple-music-soundtrack json-ld`, `page:/apple-music-soundtrack umami`, `page:/apple-music-soundtrack trial-wording` | | `/private-driving-journal` | `web/private-driving-journal.html` | `page:/private-driving-journal status`, `page:/private-driving-journal title`, `page:/private-driving-journal meta-description`, `page:/private-driving-journal canonical`, `page:/private-driving-journal og:title`, `page:/private-driving-journal og:description`, `page:/private-driving-journal og:url`, `page:/private-driving-journal og:image`, `page:/private-driving-journal json-ld`, `page:/private-driving-journal umami`, `page:/private-driving-journal trial-wording` | -| `/travel-journal-app-with-photos` | `web/travel-journal-app-with-photos.html` | `page:/travel-journal-app-with-photos status`, `page:/travel-journal-app-with-photos title`, `page:/travel-journal-app-with-photos meta-description`, `page:/travel-journal-app-with-photos canonical`, `page:/travel-journal-app-with-photos og:title`, `page:/travel-journal-app-with-photos og:description`, `page:/travel-journal-app-with-photos og:url`, `page:/travel-journal-app-with-photos og:image`, `page:/travel-journal-app-with-photos json-ld`, `page:/travel-journal-app-with-photos umami`, `page:/travel-journal-app-with-photos trial-wording` | +| `/road-trip-journal-app` | `web/road-trip-journal-app.html` | `page:/road-trip-journal-app status`, `page:/road-trip-journal-app title`, `page:/road-trip-journal-app meta-description`, `page:/road-trip-journal-app canonical`, `page:/road-trip-journal-app og:title`, `page:/road-trip-journal-app og:description`, `page:/road-trip-journal-app og:url`, `page:/road-trip-journal-app og:image`, `page:/road-trip-journal-app json-ld`, `page:/road-trip-journal-app umami`, `page:/road-trip-journal-app trial-wording` | | `/road-trip-tracker-app` | `web/road-trip-tracker-app.html` | `page:/road-trip-tracker-app status`, `page:/road-trip-tracker-app title`, `page:/road-trip-tracker-app meta-description`, `page:/road-trip-tracker-app canonical`, `page:/road-trip-tracker-app og:title`, `page:/road-trip-tracker-app og:description`, `page:/road-trip-tracker-app og:url`, `page:/road-trip-tracker-app og:image`, `page:/road-trip-tracker-app json-ld`, `page:/road-trip-tracker-app umami`, `page:/road-trip-tracker-app trial-wording` | +| `/travel-journal-app-with-photos` | `web/travel-journal-app-with-photos.html` | `page:/travel-journal-app-with-photos status`, `page:/travel-journal-app-with-photos title`, `page:/travel-journal-app-with-photos meta-description`, `page:/travel-journal-app-with-photos canonical`, `page:/travel-journal-app-with-photos og:title`, `page:/travel-journal-app-with-photos og:description`, `page:/travel-journal-app-with-photos og:url`, `page:/travel-journal-app-with-photos og:image`, `page:/travel-journal-app-with-photos json-ld`, `page:/travel-journal-app-with-photos umami`, `page:/travel-journal-app-with-photos trial-wording` | | `/support` | `web/support.html` | `page:/support status`, `page:/support title`, `page:/support meta-description`, `page:/support canonical`, `page:/support og:title`, `page:/support og:description`, `page:/support og:url`, `page:/support og:image`, `page:/support json-ld`, `page:/support umami`, `page:/support trial-wording` | | `/privacy` | `web/privacy.html` | `page:/privacy status`, `page:/privacy title`, `page:/privacy meta-description`, `page:/privacy canonical`, `page:/privacy og:title`, `page:/privacy og:description`, `page:/privacy og:url`, `page:/privacy og:image`, `page:/privacy json-ld`, `page:/privacy umami`, `page:/privacy trial-wording` | | `/terms` | `web/terms.html` | `page:/terms status`, `page:/terms title`, `page:/terms meta-description`, `page:/terms canonical`, `page:/terms og:title`, `page:/terms og:description`, `page:/terms og:url`, `page:/terms og:image`, `page:/terms json-ld`, `page:/terms umami`, `page:/terms trial-wording` | | `/login` | `web/login.html` | `page:/login status`, `page:/login umami` | -JSON-LD blocks live in `web/beta.html`, `web/apple-music-soundtrack.html`, `web/private-driving-journal.html`, `web/travel-journal-app-with-photos.html`, `web/road-trip-tracker-app.html`, and `web/support.html`. Privacy and terms have none today. Every `application/ld+json` block that is present must parse. +JSON-LD blocks live in `web/beta.html`, `web/apple-music-soundtrack.html`, `web/private-driving-journal.html`, `web/road-trip-journal-app.html`, `web/road-trip-tracker-app.html`, `web/travel-journal-app-with-photos.html`, and `web/support.html`. Privacy and terms have none today. Every `application/ld+json` block that is present must parse. ## Umami tag diff --git a/tools/verify-live/expectations.json b/tools/verify-live/expectations.json index 4e54ac2c..2d50e6c0 100644 --- a/tools/verify-live/expectations.json +++ b/tools/verify-live/expectations.json @@ -3,8 +3,9 @@ "/", "/apple-music-soundtrack", "/private-driving-journal", - "/travel-journal-app-with-photos", + "/road-trip-journal-app", "/road-trip-tracker-app", + "/travel-journal-app-with-photos", "/support", "/privacy", "/terms" diff --git a/web/apple-music-soundtrack.html b/web/apple-music-soundtrack.html index 3c3a4479..bb981fc6 100644 --- a/web/apple-music-soundtrack.html +++ b/web/apple-music-soundtrack.html @@ -107,7 +107,7 @@

    Is JourneyDeck a navigation app?

    No. Set up recording before driving and do not interact with the app while operating a vehicle.

    - + diff --git a/web/private-driving-journal.html b/web/private-driving-journal.html index 067ec449..09efd134 100644 --- a/web/private-driving-journal.html +++ b/web/private-driving-journal.html @@ -87,6 +87,8 @@

    Private driving journal for iPhone

    Who it’s for

    Drivers who want a private archive of the roads they take, everyday drives and longer trips, without signing up for a cloud tracker or handing over a continuous trip history account.

    +

    Looking for a road trip journal app, car trip log, or trip journal on iPhone? That overview stays with the same private, local-first recorder.

    +

    If you need a personal driving history app or drive log, that page explains the journal, and that it is not a DMV record or mileage tax log.

    How it works

    diff --git a/web/road-trip-journal-app.html b/web/road-trip-journal-app.html new file mode 100644 index 00000000..055b1f6f --- /dev/null +++ b/web/road-trip-journal-app.html @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Road trip journal app for iPhone | JourneyDeck + + + + + + +
    +
    +

    YOUR ROADS, YOUR ARCHIVE

    +

    Road trip journal app for iPhone

    +

    A private road trip journal for iPhone. Start a journey, and JourneyDeck keeps the route, the miles and the music.

    + +
    +
    +

    THE ROAD / JOURNEYDECK 4.0

    Every journey, kept.

    Manually start a journey on iPhone to save your route, distance, and drive time on your device. No account required. Optional Apple Music soundtracks and Memories with photos and notes sit with the road.

    +
    JourneyDeck 4.0 screenshot showing a saved journey, route map, soundtrack, and replay control
    A saved journey in JourneyDeck 4.0
    +
    +
    +
    +

    Who it is for

    +

    Drivers who want a private archive of the roads they take, everyday drives and longer trips, without signing up for a cloud tracker or handing over a continuous trip history account.

    +
    +
    +

    How it works

    +
      +
    1. Download JourneyDeck and allow Location when you want route recording.
    2. +
    3. Tap Start Journey on Home (or use Siri, the Start a Journey widget, or Apple Watch). Put the phone away; the route keeps recording while locked.
    4. +
    5. When parked, end the journey. Explore the route on your device.
    6. +
    7. Optionally connect Apple Music for a soundtrack, or group drives into a Memory with photos and notes.
    8. +
    +
    +
    +

    What stays with each trip

    +
      +
    • The route, distance, and drive time you recorded.
    • +
    • Places along the drive, including Home, Work, or School labels you choose to save.
    • +
    • Photos and notes you add to a Memory or a Journey Marker.
    • +
    • An optional soundtrack from your authorized Apple Music listening history, or Identify Song with ShazamKit. Audio is never saved.
    • +
    +
    +
    +

    What private and local-first mean

    +
      +
    • Core recording works offline, without an account or subscription.
    • +
    • Journey data starts on your iPhone and remains there until you delete it, delete the relevant profile, or remove the app. Private iCloud continuity is optional.
    • +
    • Sign in with Apple is optional.
    • +
    • Journeys never start automatically. You start and finish intentionally.
    • +
    +

    Read the JourneyDeck privacy policy and the private driving journal overview for more.

    +
    +
    +

    Frequently asked questions

    +

    What is a road trip journal app?

    +

    A road trip journal app keeps a personal archive of trips you choose to record. JourneyDeck is a private driving journal for iPhone: you tap Start Journey before you set off, and your route, distance, and drive time are saved on your device.

    +

    Is JourneyDeck a road trip log or car trip log app?

    +

    Yes, as a manual journal. You start and finish each drive yourself. It is not an automatic mileage tracker or a cloud trip log that records every drive in the background.

    +

    Does JourneyDeck need an account?

    +

    No. Core recording works without an account or subscription. Sign in with Apple and private iCloud sync are optional.

    +

    Does it record every trip automatically?

    +

    No. You tap Start Journey when you want to keep a drive.

    +

    Is JourneyDeck a navigation app?

    +

    No. Set up recording before driving and do not interact with the app while operating a vehicle.

    +
    +
    + +
    + + diff --git a/web/sitemap.xml b/web/sitemap.xml index af4de1d9..de929126 100644 --- a/web/sitemap.xml +++ b/web/sitemap.xml @@ -3,8 +3,9 @@ https://journeydeck.me/2026-09-16 https://journeydeck.me/apple-music-soundtrack2026-09-23 https://journeydeck.me/private-driving-journal2026-09-24 - https://journeydeck.me/travel-journal-app-with-photos2026-10-12 + https://journeydeck.me/road-trip-journal-app2026-10-12 https://journeydeck.me/road-trip-tracker-app2026-10-12 + https://journeydeck.me/travel-journal-app-with-photos2026-10-12 https://journeydeck.me/privacy2026-09-13 https://journeydeck.me/support2026-09-13 https://journeydeck.me/terms2026-09-13