Misaka's Personal Footprint Map: From Requirements to AI-Powered Launch

Misaka's Personal Footprint Map: From Requirements to AI-Powered Launch
Misaka10013More than ten years ago I was already using Google My Maps. It started with Google Earth — precise, absurdly rich in detail, and in my eyes perfectly suited to hunting down the places I had been to and pinning them down as a record. Later I linked that My Maps map from the “About” page of my blog, and that was how my footprints became viewable on my own site.
1 | <iframe src="https://www.google.com/maps/d/embed?mid=1FnQqdlXYFoEnO6WvZ8QEHj9RYHg" width="100%" height="600"></iframe> |
But using it came with pain points. For one thing, Google Maps has always been blocked in China, so every time I wanted to update my footprints I had to be on a VPN — which is exactly why I updated it so rarely. For another, Google’s satellite imagery and its roads and points of interest are offset from each other inside China, so it just looks bad. Time and again I’d be staring at a spot, unable to decide whether to place the marker against the satellite image or against the street layout. In recent years I’ve also dabbled with other options, like saving places into Baidu Maps or Amap favourites. But whenever I wanted to look back and ask “where have I been, and what happened there?”, the data was scattered across a pile of apps. I’d have to open one app after another and dig, wading past walls of ads and loan offers. Maddeningly slow.
What I actually wanted was very simple: a map that belongs to me — every marker a memory, where I can write text, attach images, string points into routes in time order, and where the data always stays in my hands.
So, with AI helping me both to think it through and to build it, this project came into being: a pure front-end footprint map embedded in my Hexo blog. This post records the whole journey from requirements discussion to launch — including three very representative pitfalls, and the to-dos I left for my future self.
1. Aligning on Requirements: Two Rounds of Discussion with Yuanbao
I didn’t hand this project straight to an AI and tell it to start coding. I first went to Yuanbao to talk the requirements through, letting the AI keep asking me questions over several rounds until my own needs were actually pinned down. That process also forced me to think my own ideas straight.
Yuanbao and I started from a vague notion — “I want to record the footprints of my life” — and gradually converged on a core feature set: map markers, a category system (routes / good food / good drinks / fun / scenery / points of interest), Markdown popups with text and images, a route mode ordered by time, group filtering, and data import/export.
When we were done, Yuanbao produced two documents for me:
| Document | Audience | Focus |
|---|---|---|
| Requirements & Functional Design | Humans | The “what” and the “why”, plus interaction rules |
| AI Developer Handover Doc | An AI developer | Data model, priorities (P0/P1/P2), acceptance checklist, development constraints |
The first document is essentially a user-facing description of the requirements; the second is a development guide aimed at AI. In practice, though, I came to feel it’s better not to let the AI doing the actual work be completely steered by that second document. Different AIs think differently, for one thing. And an AI in the middle of implementation, working alongside a human tester, will always run into places where the original technical design doesn’t match the way things really work.
The reason I thought of hashing out the plan with AI first is that in my own work on government IT systems, I’m constantly sitting with clients in front of a prototype, discussing back and forth — and that’s how requirements actually get nailed down. It matters. Skip it, and the build phase turns into endless rework. AI may not get tired and can iterate forever, but with vague requirements, my tokens and my time are real costs. The more precisely the direction is described at the document stage, the higher the odds the AI picks the right approach, and the fewer rounds of bickering.
I think that when you talk to the person doing the work, every word has to count. Explaining ideas to an AI is genuinely good practice for the collaboration and communication side of the job. I’ve also lived through projects that dragged on, accumulated more and more user interaction, and ended up in a heap of problems. With AI-assisted programming, the scariest scenario, I think, is this: you finally have something that’s basically perfect, then you ask the AI to fix a bug and it destroys the features that were already working. Ha. Which is why, during development, I kept telling the AI to mind the backups at every version.
Before starting, I also talked things over with AI and hunted down similar existing solutions to use as references, so we weren’t inventing wheels out of thin air. We borrowed architectural ideas from three projects: MarkdownMap, leaflet-search and Exping.
The build principles were basically settled up front:
- Pure front-end: no backend, no database; static hosting is enough to run it
- Data ownership: everything lives in JSON files, exportable at any time, never locked in
- Single-file delivery: all CSS and JS inlined into one
index.html— drop it intosource/map/and you’re done
2. Development: Six Rounds of Iteration with WorkBuddy
Once the docs went to WorkBuddy, we had six versions out in no time. Here’s the iteration in a table:
| Version | Main content | Nature |
|---|---|---|
| v1 | Implement everything per the handover doc | Initial |
| v2 | Inline the external dependencies for reliability | Optimization |
| v3 | Tune the data structures | Optimization |
| v4 | Fix “popup can’t be reopened after closing” | Bug fix |
| v5 | Fix the search dropdown being occluded + Enter to select + OSM/Carto basemaps | Bug fix + spin-off |
| v6 | Replace the layer cycle button with a dropdown + Esri satellite basemap | Spin-off requirement |
The early versions (v1–v3) went unexpectedly smoothly — the handover doc was detailed enough that the AI basically got it right in one pass, and the vast majority of the acceptance checklist passed outright. The real time went into the last three rounds, where I was the one testing in real use, hunting bugs, and judging whether the thing actually met my needs.
3. Pitfall Log
Pitfall 1: The popup that couldn’t be reopened (v4)
Symptom: click a marker and the popup opens fine. Click the ✕ in its top-right corner to close it, then click the same marker again — the popup absolutely refuses to appear. Refresh the page and it opens once more.
This bug is particularly deceptive: “it opens the first time” means the open path is fine; “it won’t open after closing” means the close operation dirtied something. My job was to reproduce the fault and gather the logs through different operations; finding the cause was the AI’s job.
The AI dug down into Leaflet’s source. Leaflet’s Marker._openPopup has a toggle inside it:
1 | _openPopup: function (t) { |
Meanwhile, the click handler the AI had custom-written for my requirements and Leaflet’s own internal handler were listening for clicks at the same time. The two conflicted. A case of borrowing a third-party library and importing trouble along with it. So no, you can’t just be a copy-paste engineer. Ha.
4. Spin-off Requirements: Improving It Through Use
These three features weren’t in the original requirements document — they came out of pain points I hit while actually using the thing:
- Foreign basemaps. It originally used Amap tiles, which look fine inside China, but the moment I wanted to mark a place abroad and zoomed in, it was blank. So I added OSM and Carto’s light basemap for overseas coverage, plus Esri satellite — a complete set of “domestic + foreign, standard + satellite”.
- A layer dropdown. Once the basemaps went from two to five, the old “cycle” button became idiotic — getting to Esri satellite meant clicking four times. So it became a dropdown.
- Enter to select in search. While chasing down the occluded-dropdown problem, I noticed I kept instinctively hitting Enter to confirm a search, so I asked the AI to add it.
5. Privacy: A Decision Deferred
Before launch, something occurred to me: this page needs no login. Convenient for me — but equally convenient for anyone else to see all of my footprints: where I’ve been, when I went, and what I wrote.
I was torn between two options: put a password lock on the page, or let the page load normally but require verification before the data loads. After discussing it with the AI, though, we couldn’t come up with a good answer right away: static hosting means no backend, so any “front-end password check” is just a fig leaf — bypass the page, hit the data.json URL directly, and the plaintext downloads just the same.
The genuinely effective answer is a third one: encrypt data.json itself with AES-256-GCM, with the password living only in my head. The browser decrypts it to render and encrypts on export; anyone who downloads the file just gets noise. But I also wanted to keep the map’s ability to be shared and shown off. And for the moment I hadn’t figured out how to do both.
6. Launch & Deployment
Deployment was simple: copy the whole source/map/ folder (index.html + data.json + README.md, three files in total) into the production repo, git push, and CNB’s cloud build takes care of everything that follows.
Once live, it’s reachable at https://misaka10013.cn/map/.
The whole tech stack, summed up in one sentence: Leaflet.js handles the map, marked.js handles Markdown rendering, localStorage handles persistence, vanilla JS + inlined CSS handle every interaction — zero frameworks, zero backend, zero build step.
7. Open Questions and To-Dos (Notes to Future Me)
Recording what’s unfinished here, so that when I stumble back onto this post later I’ll know where to pick up:
| Item | Notes | Priority |
|---|---|---|
| Encrypt data.json | AES-256-GCM + PBKDF2, decrypted and rendered client-side; exports encrypted the same way | High |
| Entry point to the map | Nothing in the navigation or in any post links to /map/ yet; reachable only by typing the URL (which is a layer of obscurity, arguably) | Low |
Afterword
Looking back over the whole project, my process was: Yuanbao converged the requirements and produced the documents, WorkBuddy did the coding, and I did the real-world testing and made the directional calls. Of the three roles, AI took two; pointing the direction and judging the quality stayed with me.
Update Log (2026-09-23): v7 → v20 After Launch
The post above wrapped up when v6 shipped. But once I actually used it, I realised shipping was only the beginning. Over the following month or so I kept iterating with AI, a dozen-plus versions, sanding down a set of detail problems. This update catches the rest up.
v7 ~ v9: Real Data Arrives, Three Pitfalls in a Row
The first move after launch was moving my real footprint data in. The pipeline: export KML from Google Maps Takeout → script it into Excel (with me going through the categories by hand) → have AI write a script to batch-convert it into data.json → import it into the footprint page. 276 markers, spanning 2018 to 2019.
The moment data came in, three problems blew up at once:
| Version | Problem | Root cause |
|---|---|---|
| v7 | Every marker name showed undefined, edit boxes blank |
The imported data used a name field for marker names while the page code read m.title; had AI fix it |
| v7 | After saving an edit, the popup’s close button stopped working | Saving rebuilt the marker, but the new popup never had its events rebound. AI’s bug |
| v8 | Changed a marker’s time; the route numbering didn’t budge | Date format problem |
| v9 | The same point sat hundreds of metres apart on Amap vs Esri | Coordinate system mismatch |
That v7 field mismatch: the data and the page code each used their own field names. I hadn’t expected AI to be capable of a slip like that. It can write code, but sometimes it makes elementary mistakes — it smells like a context-memory problem. With a little test data you’d never notice; with a lot of data, bugs detonate.
Pitfall 4: The “half a format” date (v8)
Symptom: I changed a marker’s date, saved, and the route numbering didn’t move a millimetre. Export the JSON, re-import it — the order was still exactly as before. The reasons:
- The time exported from KML is
"2018-01-01"— date only, no hours or minutes - Meanwhile the page’s
<input type="datetime-local">only accepts the completeYYYY-MM-DDTHH:MMform. Given"2018-01-01"it doesn’t error, doesn’t warn — it just quietly shows blank - I opened the edit popup, saw an empty time field, assumed “it was never filled in”, changed some other field and hit save
- The save logic wrote the empty value back —
m.time = ''— I had wiped that time out with my own hands - The route feature has a filter condition,
m.time && m.time !== '', so a marker with an empty time is excluded from the route list entirely — meaning “the numbering didn’t change” was really “this marker doesn’t take part in sorting any more”
Fix: sweep all the marker data and backfill the times uniformly in YYYY-MM-DD 00:00 format.
Pitfall 5: Coordinate systems — the hurdle no map project escapes (v9)
Symptom: domestic map clarity is genuinely mediocre, so I added OSM, Carto and Esri as foreign basemaps — and switching over, I ran straight into the same problem Google’s POIs had given me years earlier. Every marker was off — by a few hundred metres inside China. It’s something I see constantly in the government systems we build, too: pins that won’t line up because the coordinate systems differ.
Root cause: domestic map services (Amap, Baidu, Tencent) use GCJ-02 — the “Mars coordinate system”, a non-linear offset added under legal requirement — while foreign services (OSM, Esri, Carto) use WGS-84. The same pair of numbers does not point at the same place in the two systems. Fortunately, who uses what and by how much is a known quantity, so the problem had a solution.
Store everything in GCJ-02 (natively matching the domestic basemaps I use most), and check the current basemap before rendering — if it’s a WGS-84 international basemap, back-convert the coordinates to WGS-84 and place them there; conversely, when I add or locate a point on the page, convert the displayed WGS-84 coordinates back to GCJ-02 before saving.
v10 ~ v13: Breaking Up the 277KB Single File
I noticed the code for my footprint map was getting bigger and bigger, yet the AI kept everything in a single file — styles and JS included. Every version iteration meant the AI had to read the entire file, the context window would quickly fill up, and the AI would go noticeably dumber. That meant the project was no longer small; the complexity had gone up. It clearly needed to be split: HTML, JS and CSS all separated.
| Version | Requirement | Result |
|---|---|---|
| v10 | Filter markers by time range (trim markers and tracks together) | Added All / Last 1 year / Last 3 years / Custom, defaulting to the last 1 year |
| v11 | The AI could no longer handle the single file | Split 277KB into index.html (4.7KB) + app.js + style.css |
| v12 | Keep splitting and optimizing the 200KB+ app.js | 78% of it was third-party libraries — pulled them out into vendor/, so the AI no longer has to read library JS every time it looks at its own logic; business code down to 56.6KB / 1634 lines |
| v13 | Got a Tianditu key, added a cleaner domestic satellite source | Added the Tianditu satellite basemap (imagery + Chinese label overlay) |
The v12 situation is worth noting: once a project keeps going and grows in scale, it will easily exceed what an AI can handle. Splitting and decoupling are the way to go.
v14 ~ v20: From “Fig Leaf” to Real Encryption
This is the main thread of the most recent iteration, and the eventual landing of that “deferred decision” from section 5. The approach changed three times:
| Version | Approach | Why it was rejected |
|---|---|---|
| v14 | Whole-page lock: password required to enter the page | It can’t actually stop anyone from downloading data.json directly — it only deters computer novices. Under this approach, some semi-private markers that were worth sharing with others also became invisible |
| v15~v16 | Marker-level privacy: a “private” flag on each marker, hidden until unlocked | Only hides what should be hidden. But the data remained plaintext — the interface simply didn’t show it; anyone who understands the web can explore the page’s architecture and download data.json to get every private marker |
| v19 | Real encryption: public markers in plaintext + private ones as a single ciphertext block | The approach we finally settled on after brainstorming |
v19: Encryption Ships
I finally finished the real-encryption approach. In one sentence: download data.json and all you get is noise.
- Hybrid single file: public markers stay plaintext (the page opens normally, public points display normally), while private markers are encrypted as one whole array into a single ciphertext block — you can’t even tell how many private footprint markers there are
- Algorithm: PBKDF2-SHA256 with 600,000 iterations to derive the key → AES-256-GCM (12-byte random IV, 128-bit auth tag)
Updated To-Dos (Notes to Future Me)
| Item | Status |
|---|---|
| ✅ Done (v19) | |
| ✅ Back button added (v17); a link from a post still to come |
Some Closing Thoughts
The idea of building a map of my own took shape about half a year ago, back when I was working on government dashboard systems.
When I started, the map was up within a few versions. What I never expected was that, through continued use, it would spawn close to twenty versions. Though that’s normal enough — no single version can ever describe your requirements completely. Use always brings requirement refinements and bug fixes. Still, I’m fairly amazed by what AI can do. The whole way through I was probing the edges of its capability; it stumbled plenty, but in the end it delivered everything.




