Skip to main content

Travel Photo Metadata Workflow

This document is the dedicated specification for travel album pages in this repo. It has two goals:

  • Make travel photos stably display a subtle one-line metadata strip in the lightbox
  • Make it repeatable: give AI a Markdown file full of image links and have it produce a complete travel album page

If you continue adding new travel pages to docs/Travel, default to this document.

Understanding the Current Implementation

The current travel photo metadata pipeline in this repo works like this:

  1. You prepare photos with valid EXIF data.
  2. Photos are converted to web-friendly JPEG and uploaded to stable paths under https://picture.nevergpdzy.cn/.
  3. In docs/Travel/*.mdx, photos are referenced by domain-relative paths using createTravelPhotos([...]).
  4. Running npm run verify:travel (which internally runs npm run generate:travel then npm run build).
  5. The script scripts/generate-travel-photo-metadata.mjs downloads the JPEGs by these relative paths, reads EXIF / GPS, and calls AMap reverse geocoding when GPS is present. It generates src/data/travelPhotoMetadata.generated.json.
  6. The script scripts/generate-travel-map-data.mjs aggregates article places and per-photo GPS points by the TravelGallery order in docs/Travel/*.mdx. It generates src/data/travelMap.generated.json.
  7. src/components/TravelGallery/index.js reads the photo metadata. src/components/TravelMap/* reads the map data, rendering the place overview map on the Travel category page and the capture point map on article pages.

If the Travel map shows issues like "all markers shifted to the other side of the road," start here:

The lightbox currently prioritizes these fields:

FieldSourceDisplayed
displayLocationPrefer GPS + AMap reverse geocoding simplified result; falls back to locationLabel when no GPS or request failsYes
capturedAtEXIF DateTimeOriginal; falls back to DateTimeYes
deviceEXIF Make + ModelYes
lensEXIF LensModel; iPhone names are normalized to short labelsYes
locationLabelInherited from the travel article title; serves as fallback location when GPS is missing or the request failsNot directly prioritized
gps / reverseGeocodeNormalized cache of EXIF GPS and AMap reverse geocoding resultsNot directly displayed

Additionally, hasMetadata is generated to flag whether at least some usable metadata was extracted for a given image.

Key Facts to Remember

1. Displayed Location Prefers Reverse Geocoding; Title Is the Fallback

The location text displayed on the page comes from the photo GPS via AMap reverse geocoding simplified result, not from the article title by default. Only under these conditions does it fall back to the title in the article front matter:

  • The photo has no usable GPS
  • The reverse geocoding request failed
  • The reverse geocoding succeeded but returned no readable landmark name or administrative district combination

For example:

---
title: Wuhan
---

The lightbox will display Wuhan.

Another example:

---
title: Huanglong and Jiuzhai Valley
---

The lightbox will display Huanglong and Jiuzhai Valley.

So if you want the location display to be correct, the most important thing is to write the travel article title correctly -- do not expect photo GPS to determine the frontend copy.

2. The Metadata Script Only Processes JPEG; Standard Input Is Domain-Relative Paths

The current script recognizes .jpg / .jpeg image references. The standard syntax is a domain-relative path, for example:

img_for_Typora/IMG_5867_WuHan.jpg
JingXi/IMG_5867_WuHan.jpg
Travel/JingXi/IMG_5867_WuHan.jpg

This means:

  • Travel pages should reference JPEG files
  • New articles should prefer domain-relative paths instead of bare filenames
  • If your originals are HEIC / HEIF, convert to JPEG first

For backward compatibility with legacy articles, the component still accepts bare filenames, but it will automatically interpret:

IMG_5867_WuHan.jpg

as:

img_for_Typora/IMG_5867_WuHan.jpg

This is a compatibility rule, not a new recommended convention.

For HEIF-to-JPEG conversion commands, continue referring to:

3. iPhone Lens Names Are Compressed to Short Labels

The current frontend and generation scripts both normalize iPhone lens names to short labels. The final output retains only these:

  • Ultra Wide
  • Main
  • Telephoto
  • Front

If the device is not an iPhone, or the lens model does not match these rules, the original lens field is preserved.

Pre-Upload Preparation Guide

If you want AI to correctly carry photo metadata going forward, satisfy these prerequisites before uploading.

1. Preserve the "Original Pipeline" for Source Photos

Priority recommendation:

  1. Phone or camera originals
  2. AirDrop / cable export / "Export Unmodified Originals" from the photo library
  3. Batch conversions that explicitly preserve EXIF
  4. Last resort: any social app, chat app, or web intermediary

Do not use these as final publish sources:

  • Images forwarded through WeChat, QQ, Weibo, Xiaohongshu, or other social platforms
  • Images "saved as" from chat windows
  • Downloaded images already re-compressed by a website
  • Output from online compression tools

These paths are most likely to strip DateTimeOriginal, Model, LensModel, GPS, and other information entirely.

2. Prefer One Location Title Per Travel Article

Because locationLabel is still inherited from the article title and now serves as the "fallback location when GPS is missing," each docs/Travel/*.mdx should describe one primary location. Otherwise:

  • The page title becomes vague
  • Every image in the lightbox displays the same location name
  • Photos without GPS all uniformly fall back to that title location

3. Keep Filenames Stable, Readable, and Decided Once

Most travel photo filenames in the current repo look like:

IMG_5867_WuHan.jpg
IMG_7107_HongKong.jpg
IMG_0248_JiuzhaiValley.jpg

Continue this style:

  • Keep the original camera sequence number
  • Append a location identifier in English to the suffix
  • Do not repeatedly rename after uploading

Once a filename changes, you must also update:

  • The remote image URL
  • The reference in docs/Travel/*.mdx
  • Regenerate the metadata manifest

If you use multi-level directories, the path itself must be stable. For example:

img_for_Typora/IMG_5867_WuHan.jpg
Travel/JingXi/IMG_5867_WuHan.jpg

If you give AI "a Markdown file full of image links," those links should ideally:

  • Open directly in a browser
  • Be stable URLs without temporary auth parameters
  • Land under the https://picture.nevergpdzy.cn/ domain
  • Have a .jpg or .jpeg file extension

If you provide temporary links, short links, redirect chains, or cloud storage auth links, AI can modify the document, but the metadata script may not be able to stably fetch the final file.

How to Check Whether Photos Have Lost Valid Metadata Before Upload

For this repo, "valid metadata" means at least some of the following still exist:

  • Capture time DateTimeOriginal
  • Device model Model
  • Lens model LensModel
  • Optional GPS information

If all these fields are gone, the lightbox typically shows only the location or no photography information at all.

Obvious Red Flags First

If an image shows any of these signs, suspect it is no longer the original EXIF-bearing version:

  • File size is abnormally small -- only a few hundred KB for what should be a phone original
  • Resolution is compressed to a fixed long edge, like 1280, 1600, 2048, etc.
  • Capture time, device model, and lens model are all missing
  • Within the same set of photos, only a very few still carry device info
  • You had location services on, but all photos have zero GPS

If you shot with an iPhone and your JPEGs have universally become "noticeably smaller + uniformly compressed dimensions + EXIF essentially empty," they are almost certainly a version processed by an intermediary platform.

If your machine has exiftool installed, use it first. It is best for checking "how much metadata remains."

Check a single image:

exiftool -DateTimeOriginal -Model -LensModel -GPSLatitude -GPSLongitude -ImageWidth -ImageHeight IMG_0001.jpg

To also check for post-processing software traces, add Software:

exiftool -DateTimeOriginal -Model -LensModel -GPSLatitude -GPSLongitude -Software IMG_0001.jpg

The ideal state is:

  • DateTimeOriginal has a value
  • Model has a value
  • LensModel has a value
  • If location services were on, GPSLatitude / GPSLongitude also have values

If only pixel dimensions remain and capture time, device, and lens are all gone, do not use this image for travel metadata display.

Windows Check Without Extra Tools

If you do not have exiftool, use PowerShell to read the key EXIF fields directly:

Add-Type -AssemblyName System.Drawing

function Read-ExifAscii($image, $id) {
$prop = $image.PropertyItems | Where-Object Id -eq $id | Select-Object -First 1
if (-not $prop) {
return $null
}

return ([System.Text.Encoding]::ASCII.GetString($prop.Value)).Trim([char]0)
}

$path = (Resolve-Path .\IMG_0001.jpg).Path
$image = [System.Drawing.Image]::FromFile($path)

[pscustomobject]@{
DateTimeOriginal = Read-ExifAscii $image 0x9003
Model = Read-ExifAscii $image 0x0110
LensModel = Read-ExifAscii $image 0xA434
Width = $image.Width
Height = $image.Height
}

$image.Dispose()

How to judge the results:

  • DateTimeOriginal has a value: capture time is likely still intact
  • Model has a value: device info is still intact
  • LensModel has a value: lens info is likely still intact
  • All three are empty: this JPEG's critical EXIF has almost certainly been stripped

macOS Check Without Extra Tools

macOS can use the built-in mdls for a quick screening:

mdls -name kMDItemContentCreationDate \
-name kMDItemAcquisitionMake \
-name kMDItemAcquisitionModel \
-name kMDItemPixelWidth \
-name kMDItemPixelHeight \
IMG_0001.jpg

If capture time and device model are completely invisible here, the image is likely unsuitable as a travel metadata source.

Keep in mind:

  • mdls is better for quick screening
  • For lens and GPS and other more complete EXIF, exiftool is more reliable

GUI First-Round Check

If you do not want to run commands first, you can also check via the system UI:

  • Windows: right-click the image, open "Properties -> Details"
  • macOS: select the file, press Space for preview or "Get Info"

If capture date, device, dimensions, and other info are completely invisible here, there is no point expecting the repo scripts to extract complete data from this image.

This workflow is suitable for continuing to add new travel pages.

Step 1: Prepare Photos

Requirements:

  • Preserve EXIF as much as possible
  • Final format must be JPEG
  • Filename and path must be stable
  • Images already uploaded to a stable URL under https://picture.nevergpdzy.cn/

If the original format is HEIC, first refer to Apple HEIF Photo Display Guide to convert to JPEG before proceeding.

Step 2: Prepare Markdown for AI

If you want AI to stably take over in the future, write the source Markdown as "title + sections + image links per section." For example:

# Wuhan

## Arrival

![](https://picture.nevergpdzy.cn/img_for_Typora/IMG_5867_WuHan.jpg)
![](https://picture.nevergpdzy.cn/img_for_Typora/IMG_6001_WuHan.jpg)

## Streets

![](https://picture.nevergpdzy.cn/img_for_Typora/IMG_6069_WuHan.jpg)
![](https://picture.nevergpdzy.cn/img_for_Typora/IMG_6076_WuHan.jpg)

This way AI can directly map each section to a createTravelPhotos([...]) array.

If you provide a single block of Markdown with no headings and only image URLs, AI can handle it, but it will only guess groupings from image order, paths, and filenames -- the result will be less stable than explicit sections.

If AI needs to look at photos first to decide grouping, headings, or body text, do not read originals directly by default:

  • Generate compressed previews first for AI to read
  • Reading originals directly easily triggers conversation context size limits; the empirical risk threshold is around 20MB
  • Default first-tier preview uses a long edge of 1280px
  • If 1280px is still unclear for small text, signs, menus, receipts, or key details, step up to a long edge of 1920px
  • For multi-image tasks, compress and read in batches -- do not feed the entire set of originals at once
  • Compressed previews are only for AI to understand scenes, groupings, and write copy -- not for EXIF checks or metadata generation

If you do not first write a complete Markdown yourself but instead give AI these pieces:

  • The location name for this trip
  • A batch of already-uploaded OSS image links
  • A few short subjective descriptions For example: "this trip leans toward seaside and night scenes," "first half is arrival and streets, second half is night scenes and return," "keep the writing restrained, not like a travel guide"

That works perfectly with the current repo's standard pipeline.

Recommended minimum input:

  • Location name Example: Jinan
  • Stable, accessible JPEG OSS links Preferably all under https://picture.nevergpdzy.cn/
  • At least 2 to 5 sentences of your own feelings, order hints, or style requirements For example:
    • What scenes to emphasize
    • Roughly what order to view the photos
    • Whether the tone should be calm, narrative, or diary-like
    • Which photos must be standalone sections, which can be grouped

When AI takes over, it should follow this order:

  1. First check whether the links are stable and accessible, and whether they are JPEG.
  2. If there are only links and no groupings, do an initial grouping by file order, scene changes, and your descriptions.
  3. If groupings are still uncertain, read compressed previews -- only to judge scenes and write body text, not to read the entire set of originals.
  4. First produce a "source Markdown draft," organizing headings, ## sections, body paragraphs, and image links.
  5. Then hand this source draft to scripts/create-travel-article.mjs to convert into the final docs/Travel/<slug>.mdx.
  6. After generation, refresh photo metadata, map data, and run build verification.
  7. If any images lack valid EXIF, lack GPS, have broken links, or the location naming is clearly unstable, explicitly tell the user.

In other words, AI should not hand-write the final MDX directly. A more reliable approach:

  1. First produce a source draft that a human can also read
  2. Then use the script to generate the final MDX
  3. Then run verification

This way, when you later want to revise copy, adjust groupings, add or remove images, you can reuse the same pipeline without regressing to one-off manual page assembly.

An input template that best fits your current habits:

Location: Jinan

Requirements:
- Don't write it like a travel guide; lean toward album narration
- First half is arrival and springs, second half is night scenes
- Tone: restrained, not too lyrical

Images:
https://picture.nevergpdzy.cn/Travel/Jinan/IMG_6319_Jinan.jpg
https://picture.nevergpdzy.cn/Travel/Jinan/IMG_6352_Jinan.jpg
https://picture.nevergpdzy.cn/Travel/Jinan/IMG_6376_Jinan.jpg
https://picture.nevergpdzy.cn/Travel/Jinan/IMG_6386_Jinan.jpg

When AI receives this kind of input, the standard output should be:

  1. First produce a source draft like drafts/travel/<slug>-source.md
  2. Then run:
npm run create:travel-article -- --source drafts/travel/<slug>-source.md --verify

This command generates the final docs/Travel/<slug>.mdx, then refreshes Travel photo metadata, map data, and builds the site. It is equivalent to generating the article then running npm run verify:travel.

  1. Finally report back to the user, including:
  • The path of the newly generated Travel page
  • Whether metadata and map refresh succeeded
  • Whether the build passed
  • Which images are missing EXIF / GPS / reverse geocoding

Step 3: AI Converts Markdown into a Travel Page in This Repo

When AI takes over in this repo, it should follow these rules.

1. Page Location

New articles go to:

  • docs/Travel/<slug>.mdx

2. Front Matter

Must have at minimum:

---
title: Wuhan
description: A short summary of this travel album.
sidebar_position: -20241218
hide_table_of_contents: true
---

Where:

  • title affects the location name displayed in the lightbox
  • If you want the location to display in Chinese, the Chinese location must come first in the title
  • sidebar_position uses the negative of the trip start date in -YYYYMMDD format, used to sort Travel cards, sidebar, and prev/next navigation with the newest trip first

Do not write sidebar_position as a plain sequence number like 1, 2, 3..., or you will need to renumber everything when adding a newer trip later. Do not rename Travel files for sorting; the current map data and homepage cards still use the file basename as the article id.

For multiple articles on the same day needing fine-tuned order, use decimal suffixes:

sidebar_position: -20260430.02
sidebar_position: -20260430.01

Do not write appended integer-digit values like -2026043001, as it will be much smaller than a normal date and cause the article to permanently rank in the wrong position.

3. Component Imports

At the top of the page, uniformly use:

import TravelGallery, {createTravelPhotos} from '@site/src/components/TravelGallery';

4. Image Array Syntax

Image arrays should use domain-relative paths. Do not write full URLs into createTravelPhotos([...]):

export const arrivalPhotos = createTravelPhotos([
'img_for_Typora/IMG_5867_WuHan.jpg',
'img_for_Typora/IMG_6001_WuHan.jpg',
]);

The reasons:

  • This syntax reliably supports multi-directory structures
  • The metadata script currently works around the "relative path -> download -> parse EXIF" pipeline
  • If different directories contain same-named images, relative paths avoid conflicts

5. Grouping Rules

When AI processes user-uploaded Markdown, prioritize grouping by this order:

  1. Group by the source Markdown's H2 or H3 headings
  2. If no headings, group by obvious scene paragraphs
  3. If still no information, split by original order into 2 to 4 groups

6. Preserve Image Order

Do not adjust the order of images within a group by default. Because:

  • Capture time is, in most cases, already sequential
  • The user has usually already arranged them once for browsing narrative

7. Relationship Between Copy and Images

The metadata below images displays automatically, so AI does not need to manually write capture time, device, or lens into the body text.

The body text is better suited for:

  • Travel narration
  • Scene transitions
  • Rhythm descriptions between image groups
---
title: Wuhan
description: A short album-style record of a brief stay in Wuhan, slowing down to take it in.
sidebar_position: -20241218
hide_table_of_contents: true
---

import TravelGallery, {createTravelPhotos} from '@site/src/components/TravelGallery';

export const arrivalPhotos = createTravelPhotos([
'img_for_Typora/IMG_5867_WuHan.jpg',
'img_for_Typora/IMG_6001_WuHan.jpg',
]);

export const streetPhotos = createTravelPhotos([
'img_for_Typora/IMG_6069_WuHan.jpg',
'img_for_Typora/IMG_6076_WuHan.jpg',
]);

Opening text goes here.

<TravelGallery images={arrivalPhotos} />

## Slowing Down the Pace

Second paragraph text goes here.

<TravelGallery images={streetPhotos} />

Step 4: Generate the Metadata Manifest

First configure a local environment variable file in the repo root once:

If you have a plain Markdown source draft with body text, ## sections, and image OSS links under https://picture.nevergpdzy.cn/, run:

npm run create:travel-article -- --source drafts/travel/jinan-source.md

If you want to also refresh photo metadata and map data after generating docs/Travel/*.mdx:

npm run sync:travel-article -- --source drafts/travel/jinan-source.md

The script will automatically:

  • Read front matter
  • Derive the output filename from slug, file_name, fileName, title, or the source filename; front matter output can also explicitly specify the output path
  • Preserve ## section structure
  • Extract JPEG image links under picture.nevergpdzy.cn and convert to domain-relative paths
  • Generate createTravelPhotos([...])
  • Inject TravelStoryMap and TravelGallery
  • Output the final docs/Travel/<slug>.mdx

scripts/create-travel-article.mjs preserves the sidebar_position from the source draft's front matter. So the source template should have sidebar_position: -YYYYMMDD written in advance -- do not wait to manually add it after generating the MDX.

Several parameter boundaries to remember:

  • --source or -s is required and points to the source Markdown.
  • --sync runs npm run generate:travel after writing the final MDX.
  • --verify runs npm run generate:travel and npm run build after writing the final MDX.
  • If the target file already exists, the script refuses to overwrite by default; add --force only when you truly need to overwrite.
  • The output field in the source is only used to specify the output path and is not written into the final front matter.

Template location:

scripts/templates/travel-article-source.example.md
Copy-Item .env.example .env.local

Then fill in the AMap keys in .env.local:

AMAP_WEB_SERVICE_KEY=your-amap-web-service-key
AMAP_JSAPI_KEY=your-amap-jsapi-key
AMAP_SERVICE_HOST=
AMAP_SECURITY_JS_CODE=your-amap-security-code

Details:

  • scripts/generate-travel-photo-metadata.mjs automatically reads .env.local and .env.development.local from the repo root
  • If AMAP_WEB_SERVICE_KEY is already explicitly set in the current shell, it takes precedence over local files
  • AMAP_WEB_SERVICE_KEY is only used by the photo metadata script; AMAP_JSAPI_KEY and AMAP_SERVICE_HOST / AMAP_SECURITY_JS_CODE are used by the frontend Travel map component
  • The frontend map loads via @amap/amap-jsapi-loader installed through npm; it no longer uses page-injected loader.js
  • .env.local is already ignored by .gitignore; do not commit real keys
  • Production prefers AMAP_SERVICE_HOST
  • AMAP_SECURITY_JS_CODE is only a fallback for local development or no-proxy environments
  • If you inject AMAP_SECURITY_JS_CODE directly into the Docusaurus frontend config, it will enter the build artifacts and remain visible to clients even after server deployment
  • The current repo has protection: once AMAP_SERVICE_HOST is configured, the build no longer injects AMAP_SECURITY_JS_CODE into customFields.amap

For example, in production you can set:

AMAP_SERVICE_HOST=/_AMapService

and let your own server proxy handle the actual AMap requests.

If you only upload the raw static build/ directory to a server without configuring a proxy, and you want others to load the map normally, you must continue using the frontend direct AMAP_SECURITY_JS_CODE approach. The map will work, but the security key will also be exposed in the build artifacts.

A minimal maintainable approach:

  1. Frontend build environment keeps only AMAP_JSAPI_KEY and AMAP_SERVICE_HOST=/_AMapService
  2. Server reverse-proxies /_AMapService
  3. The server injects the real AMap security key during proxy forwarding

For example, nginx can start with a minimal Web service proxy:

location /_AMapService/ {
set $args "$args&jscode=your-security-code";
proxy_pass https://restapi.amap.com/;
proxy_set_header Host restapi.amap.com;
}

If you later enable custom map styles, add the /_AMapService/v4/map/styles proxy rule per AMap's official security documentation.

After the page content is complete, if you only want to refresh photo metadata independently:

npm run generate:travel-metadata

If you only want to refresh Travel map data independently:

npm run generate:travel-map-data

If you want to refresh both travel photo metadata and maps in one go:

npm run generate:travel

If you are only temporarily overriding local file keys for this session, you can also set them directly in the current shell:

$env:AMAP_WEB_SERVICE_KEY='your-amap-web-service-key'
npm run generate:travel-metadata

This command will:

  • Scan .mdx files under docs/Travel
  • Extract all JPEG relative paths from createTravelPhotos([...])
  • Reuse valid metadata already in src/data/travelPhotoMetadata.generated.json
  • Only download new images or images whose EXIF / GPS extraction is still incomplete
  • Read the missing EXIF / GPS for these images
  • Call AMap reverse geocoding for images with GPS but no reverse geocoding cache
  • Automatically throttle AMap requests to no more than 3/second
  • Generate src/data/travelPhotoMetadata.generated.json

If the command succeeds, you will see something like this at the end of the terminal:

Wrote N travel photo records to .../src/data/travelPhotoMetadata.generated.json (X metadata cached, Y metadata fetched, A regeo cached, B regeo requested, AMap capped at <= 3/s)

Step 5: Build Verification

The default recommendation is to run one complete verification command:

npm run verify:travel

It sequentially runs:

  • npm run generate:travel
  • npm run build

Applicable scenarios:

  • Added a new travel page
  • Added new photos
  • Added new GPS / reverse geocoding caches
  • Adjusted TravelGallery or photo metadata display logic

Check at least:

  • The page builds successfully
  • The travel page opens normally
  • The lightbox opens and navigates normally
  • The one-line metadata below each photo displays correctly
  • On wide and narrow screens, the date and "device + lens" line-break logic is correct

Going forward, when you upload a Markdown to AI and want it to complete the travel page in this repo, AI should follow this order.

1. Extract Image Relative Paths First

If the source content is:

![](https://picture.nevergpdzy.cn/img_for_Typora/IMG_5867_WuHan.jpg)

AI should write this in the repo:

'img_for_Typora/IMG_5867_WuHan.jpg'

Not the full URL.

If the source content is:

![](https://picture.nevergpdzy.cn/Travel/JingXi/IMG_5867_WuHan.jpg)

AI should write:

'Travel/JingXi/IMG_5867_WuHan.jpg'

2. Preserve the Original Markdown's Section Structure

If the source Markdown already has:

  • Headings
  • Sub-headings
  • Paragraphs

AI should preserve this structure as much as possible, mapping each section to a createTravelPhotos([...]) array and a <TravelGallery />.

3. Use the Title to Determine Location Copy

If the user says this is about "Wuhan," the front matter title should be something like:

title: Wuhan

Do not write only the English name, or the lightbox location copy will also display in English.

4. Do Not Fabricate EXIF Data

AI can organize page structure, but should not hand-fabricate these fields:

  • Capture time
  • Device model
  • Lens model
  • GPS

These should come from the photos themselves. If the photo metadata is already lost, accept that the lightbox will show less information, or explicitly tell the user that these images are not suitable for photography metadata display.

5. Only Read Compressed Previews

If AI needs to look at photo content, it must read compressed previews first, not originals.

Default rules:

  • First tier uses a long edge of 1280px
  • Step up to 1920px when details are unclear
  • Approaching 20MB originals or multi-image inputs must be compressed and batched first
  • Previews are only for judging scenes, order, groupings, and body descriptions
  • capturedAt, device, lens, and GPS can still only come from original JPEGs, remote stable JPEGs, exiftool, or npm run generate:travel-metadata

6. Run a Verification Command After Generation

If the repo already has .env.local configured at the root, AI should run this by default after modifying travel files:

npm run verify:travel

Only when explicitly wanting to refresh metadata without build verification, run:

npm run generate:travel-metadata

Without running npm run verify:travel, the travel workflow is usually not considered complete.

Going forward, you can give AI this instruction block along with your Markdown:

Please convert the Markdown I uploaded into a travel album page in this repo, with these requirements:
1. New file goes under docs/Travel, using MDX.
2. Front matter title: Chinese location first, then English location.
3. Front matter must include `sidebar_position: -YYYYMMDD` using the trip start date, for sorting Travel cards, sidebar, and prev/next with newest trip first.
4. Preserve the original Markdown's section structure and generate createTravelPhotos([...]) per section.
5. createTravelPhotos keeps only the relative path after `https://picture.nevergpdzy.cn/`, not the full URL.
6. Use `TravelGallery` to render each image group.
7. Insert `<TravelStoryMap />` before the first `TravelGallery`, so the article page auto-reuses the default-collapsed capture point map.
8. Do not fabricate capture time, device, lens, or GPS.
9. If image reading is needed, generate compressed previews first; default long edge 1280px, step up to 1920px if needed, do not read originals directly.
10. Default to using `AMAP_WEB_SERVICE_KEY`, `AMAP_JSAPI_KEY`, `AMAP_SERVICE_HOST` / `AMAP_SECURITY_JS_CODE` from the repo root `.env.local`.
11. Run `npm run verify:travel` after making changes.
12. If photos lack valid EXIF, explicitly tell me which images are missing metadata.
13. To overwrite an existing Travel page, confirm first, then use `--force`.

The purpose of this template is to have AI directly use the implementation already in this repo, not to invent another image component or metadata structure on the fly.

Acceptance Checklist

After every new travel page, verify at least:

  • Front matter has sidebar_position: -YYYYMMDD using the trip start date
  • /docs/Travel card order, Travel sidebar order, and prev/next navigation all sort newest trip first
  • All images in docs/Travel/<slug>.mdx use only the relative path after https://picture.nevergpdzy.cn/
  • These relative paths are accessible on the remote domain
  • Repo root has .env.local configured with working AMAP_WEB_SERVICE_KEY, AMAP_JSAPI_KEY, AMAP_SERVICE_HOST / AMAP_SECURITY_JS_CODE
  • npm run verify:travel runs successfully
  • src/data/travelPhotoMetadata.generated.json contains the new images
  • src/data/travelMap.generated.json is refreshed
  • If --force was used to overwrite an old page, confirm it overwrote the expected file and no hand-written body text was lost
  • Key images with GPS have reverseGeocode and simplified displayLocation filled in
  • Key images show at least capturedAt or device or lens
  • Page builds successfully
  • Location copy in the lightbox is correct
  • Travel maps in the category page and article page appear correctly, and the article page map expands on button click
  • Lightbox layout is correct on both mobile and desktop

Common Issues and Fixes

1. Generation Script Succeeded but Some Image Metadata Is Empty

Common causes:

  • The image itself was compressed or had its EXIF stripped
  • What was downloaded is not the original JPEG
  • The image never had lens or time fields to begin with

Priority order:

  1. First use exiftool or a system command to check whether this JPEG still has metadata locally
  2. Then confirm whether the remote link points to the same file
  3. If the remote version is not the original, re-upload the EXIF-preserving version

2. Page Location Display Is Wrong

Check these first:

  1. src/data/travelPhotoMetadata.generated.json -- this image's gps, reverseGeocode, and displayLocation
  2. Whether the AMap reverse geocoding result was too long and was correctly simplified
  3. If this image has no GPS or reverse geocoding failed, check the article front matter title

The current frontend displays location primarily from the photo GPS reverse geocoding simplified result. Only when GPS is missing or the request fails does it fall back to the article title.

3. The Same Image Reused Across Multiple Travel Pages Causes an Error

The current script checks whether the same path has been assigned different location labels by multiple travel pages. If the same image appears in two articles and the two article titles differ, the script may throw:

Conflicting location labels for ...

In this case, either:

  • Do not reuse the same image across pages
  • Or ensure they inherit the same location title

The most stable form for the current travel metadata pipeline is:

  • Public JPEG
  • Stable path
  • Directly downloadable

If the source does not match this, first organize the assets into compliant JPEG, then enter the travel page workflow.

5. Chinese Garbled Text in PowerShell When Viewing Documents or JSON

This repo occasionally shows Chinese display issues in Windows PowerShell, but the files themselves are usually still valid UTF-8.

If this happens:

  • Open the file directly in an editor to confirm
  • Or use node to read the UTF-8 content

Do not misjudge the document as corrupted just because the terminal displays garbled text.