A wayfinder inspired map plugin for obisidian
at main 164 lines 7.8 kB view raw view rendered
1# obs-map-viewer 2 3An Obsidian sidebar plugin that reads places from bullet lists in the active note and displays them as markers on an interactive map. Geocodes place names via OpenStreetMap Nominatim, caches coordinates as `geo:` sub-bullets in the note, and provides bidirectional cursor sync between the editor and map. 4 5## Project Context 6 7This plugin is modeled on [obs-calendar-viewer](../obs-calendar-viewer), which has a calendar + map split view. `obs-map-viewer` strips out the calendar and focuses entirely on the map. Key architectural decisions are carried over: Leaflet for mapping, Nominatim for geocoding (no API keys), document-as-cache for geo data, Obsidian CSS variables for theming. 8 9### Expected Note Format 10 11```markdown 12* Sagrada Familia 13 * Amazing architecture, book tickets in advance 14 * category: Architecture 15 * geo: 41.403600,2.174400 16* [The Louvre](https://en.wikipedia.org/wiki/Louvre) 17 * Must see the Mona Lisa 18 * category: Art 19 * geo: 48.860600,2.337600 20* Blue Bottle Coffee, Tokyo 21``` 22 23- Top-level bullets (`*` or `-`) define places 24- Sub-bullets matching `<key>: <value>` are parsed as structured fields 25- The `geo:` field is special: valid coordinates are extracted and used for map markers 26- Sub-bullets not matching key-value format are stored as freeform notes 27- Place names can be plain text, markdown links `[name](url)`, or wiki-links `[[Page Name]]` 28 29## Development Methodology: VSDD 30 31This project follows **Verified Spec-Driven Development (VSDD)**, a methodology that fuses Spec-Driven Development, Test-Driven Development, and Verification-Driven Development into a single pipeline. See the [full VSDD spec](https://gist.github.com/dollspace-gay/d8d3bc3ecf4188df049d7a4726bb2a00). 32 33This includes having red gated tests. We must start with the tests, show that they all fail, and only then proceed to implementation. 34 35### Roles 36 37| Role | Entity | Function | 38|------|--------|----------| 39| **Architect** | Human developer | Strategic vision, domain expertise, acceptance authority | 40| **Builder** | Claude (OpenCode) | Spec authorship, test generation, code implementation, refactoring | 41| **Adversary** | @adversary agent | Hyper-critical reviewer, fresh context on every pass, zero tolerance | 42| **Tracker** | Chainlink CLI | Hierarchical issue tracking with milestones, blocking relationships, and sub-issues | 43 44### VSDD Pipeline (Adapted) 45 46The full VSDD ceremony is adapted for this project's scope (Obsidian plugin, TypeScript, no formal verification toolchain): 47 48#### Phase 2: TDD Implementation 49Test-first development for each module in dependency order: 501. `parser.ts` (pure, no deps) 512. `geocoder.ts` (needs Place type) 523. `mapRenderer.ts` (needs Place type) 534. `mapView.ts` (needs all above) 545. `main.ts` (needs mapView) 55 56For each: write failing tests -> flag that the red gate exists (all tests fail) -> implement minimum to pass -> adversarial review -> refactor 57 58#### Adversarial Review 59Each module reviewed by the Adversary in a fresh context after implementation. Plus a final full-codebase review looking at cross-module interactions. 60 61We will only move on to the next module once adversarial review is passed. 62 63#### Phase 4: Feedback Integration 64Adversary findings feed back: spec fixes -> test fixes -> implementation fixes. 65 66#### Phase 5: Hardening 67- Property-based tests for the parser via `fast-check` 68- Edge case stress tests for the geocoder 69- Final adversarial pass 70 71#### Phase 6: Convergence 72Done when the adversary is nitpicking style, not finding real bugs. Four dimensions must converge: specs, tests, implementation, and hardening. 73 74## Chainlink Issue Tracking 75 76All work is tracked via `chainlink`, a local CLI issue tracker. Issues are organized into milestones (one per VSDD phase) with blocking relationships enforcing dependency order. 77 78### Milestones 79 80| ID | Phase | Issues | 81|----|-------|--------| 82| M1 | Phase 2: TDD Implementation | #1-#22 (scaffolding + 5 modules x (parent + 3 sub-issues) + styles) | 83| M2 | Phase 3: Adversarial Review | #23-#28 (per-module reviews + full codebase) | 84| M3 | Phase 4: Feedback Integration | #29-#31 (spec/test/impl fixes) | 85| M4 | Phase 5: Hardening | #32-#34 (property tests, stress tests, final adversary) | 86| M5 | Phase 6: Convergence | #35-#36 (convergence check, smoke test) | 87 88### Commands 89 90```bash 91chainlink tree # Full issue hierarchy 92chainlink list # All open issues 93chainlink ready # Issues with no open blockers (what to work on next) 94chainlink show <id> # Full issue details with spec 95chainlink milestone show <id> # Milestone progress 96chainlink blocked # What's waiting on what 97``` 98 99### Labels 100 101- `spec` — specification work 102- `test` — test writing 103- `impl` — implementation 104- `review` — adversarial review 105- `fix` — feedback integration 106- `infra` — scaffolding/build config 107 108## Module Architecture 109 110``` 111obs-map-viewer/ 112├── main.ts # Plugin entry — view registration, commands, events 113├── mapView.ts # ItemView subclass — sidebar, refresh, geo write-back, cursor sync 114├── parser.ts # Pure parser — markdown bullet lists → Place[] 115├── mapRenderer.ts # Leaflet map — markers, popups, selection, highlight 116├── geocoder.ts # Nominatim geocoding — rate limiting, dedup, cancellation 117├── styles.css # CSS using Obsidian variables for theme compat 118├── manifest.json # Obsidian plugin manifest 119├── package.json # Dependencies and build scripts 120├── tsconfig.json # TypeScript config 121├── esbuild.config.mjs # esbuild bundler config (CJS output, Leaflet bundled) 122├── vitest.config.ts # Vitest test config 123└── tests/ 124 ├── parser.test.ts 125 ├── geocoder.test.ts 126 ├── mapRenderer.test.ts 127 ├── mapView.test.ts 128 └── main.test.ts 129``` 130 131### Dependency Graph 132 133``` 134main.ts → mapView.ts → parser.ts (pure) 135 → geocoder.ts (effectful, fetch) 136 → mapRenderer.ts (effectful, DOM/Leaflet) 137``` 138 139`parser.ts` is the only pure module. All types (`Place`, etc.) are exported from it. 140 141### Key Design Decisions 142 1431. **Leaflet bundled via esbuild** — no CDN dependency, ~10K lines but tree-shaken 1442. **Leaflet CSS inlined as string literal** — avoids needing a CSS loader 1453. **CJS output, ES2018 target** — Obsidian compatibility 1464. **No API keys** — Nominatim + Stadia Watercolor + CartoDB labels are all free 1475. **Document as cache**`geo:` coordinates stored in the note itself (portable, no external DB) 1486. **Structured field parsing**`<key>: <value>` sub-bullets parsed into `fields: Record<string, string>` for extensibility 1497. **Write-back safety** — re-parse inside `vault.process()`, match by name, never use stale line numbers 1508. **Geocoding mutex** — single in-flight operation via AbortController, prevents concurrent writes 1519. **Theme integration** — all colors from Obsidian CSS variables 152 153## For AI Agents 154 155When working on this project: 156 1571. **Check `chainlink ready`** to see what's unblocked and ready to work on 1582. **Check `chainlink show <id>`** before starting any issue — the description IS the spec 1593. **Follow strict TDD**: write tests first, verify they fail, then implement minimum to pass 1604. **Never write implementation without a failing test demanding it** 1615. **Run the adversary review** after completing each module (Phase 3 issues) 1626. **Mark issues done** via `chainlink close <id>` when complete 1637. **Use `chainlink start <id>`** to track time on issues 1648. **The spec in the issue is the source of truth** — if the code contradicts the issue description, the code is wrong