# dpg-map Rebuild Steps This file replaces the old long step list. There is no Step 0. Initial setup is listed separately, then implementation starts at Step 1. ## Workflow rules 1. Use `uv` for all Python package and dependency management. 2. Always read `FEATURES.md`, `ARCHITECTURE.md`, and `AGENTS.md` before making code changes. 3. Keep `AGENTS.md` as a rolling log of what has been done, what is broken, and what comes next. 4. Update `README.md` whenever public behaviour or examples change. 5. After every step: - update `AGENTS.md` - run relevant checks - commit to git 6. Do not casually change public API once introduced. 7. Public runtime APIs must be safe to call from background threads unless explicitly documented otherwise. 8. Dear PyGui calls must happen on the GUI thread only. 9. Prefer stable, boring implementation over clever rendering tricks. ## Initial setup instructions Run these before Step 1 when starting from a clean repository: ```bash mkdir dpg-map cd dpg-map git init uv init --package dpg-map uv add dearpygui pillow platformdirs requests uv add --dev pytest ruff pyright ``` Create this structure: ```text src/dpg_map/ __init__.py api.py widget.py state.py commands.py renderer.py draw_layers.py interaction.py overlays.py providers.py projection.py tiles.py cache.py sizing.py diagnostics.py types.py exceptions.py examples/ tests/ FEATURES.md ARCHITECTURE.md STEPS.md AGENTS.md README.md ``` Recommended `pyproject.toml` additions: ```toml [tool.ruff] line-length = 100 target-version = "py311" [tool.ruff.lint] select = ["E", "F", "I", "UP", "B", "SIM"] [tool.pyright] typeCheckingMode = "basic" ``` Create `AGENTS.md` with: ```markdown # AGENTS.md ## Current status Rebuild initialized. ## Completed steps None yet. ## Current step Step 1 — Public API contract and pure core. ## Design decisions - Package is managed with uv. - Public import is `import dpg_map as dpgm`. - Dear PyGui calls are GUI-thread-only. - Runtime public calls enqueue commands or update logical state. - Overlay updates must not reset center/zoom. - The widget uses child_window + measured-size drawlist. - Tiles use a memory cache and persistent disk cache. - Tile providers are interchangeable. ## Known issues None yet. ## Commands used None yet. ## Next action Implement Step 1. ``` Initial commit: ```bash uv run python -c "import dpg_map; print(dpg_map.__name__)" uv run ruff check . uv run ruff format . git add . git commit -m "initial uv project setup" ``` ## Step 1 — Public API contract and pure core Goal: lock the public API surface and implement pure non-DPG components first. Tasks: 1. Define public exports in `__init__.py`. Required API names: ```python configure TileProvider register_provider unregister_provider get_provider list_providers map_widget set_center get_center set_zoom get_zoom set_view fit_bounds screen_to_latlon latlon_to_screen add_marker add_polyline add_trajectory update_marker update_polyline update_trajectory set_marker_position set_marker_label set_polyline_points set_overlay_show delete_overlay add_layer show_layer hide_layer clear_layer clear_map set_provider clear_memory_cache clear_disk_cache get_cache_stats get_map_debug_state ``` 2. Add exceptions in `exceptions.py`. 3. Add common types in `types.py`. 4. Implement `TileProvider` and provider registry in `providers.py`. 5. Implement Web Mercator projection in `projection.py`. 6. Implement initial cache dataclasses and `CacheStats`. 7. Stub GUI-dependent public functions so imports succeed. 8. Add tests for: - package imports - provider registration - provider URL building - invalid provider templates - projection roundtrips - cache stats dataclass construction Checks: ```bash uv run pytest uv run ruff check . uv run ruff format --check . uv run pyright ``` Acceptance criteria: - public API imports cleanly - provider system works - projection tests pass - no Dear PyGui context is required for tests - API names are fixed before renderer work begins Commit: ```bash git add . git commit -m "step 1: lock public api and pure core" ``` ## Step 2 — Thread-safe state, commands, overlays, and cache model Goal: build the logical state model before rendering anything. Tasks: 1. Implement `DpgMapConfig` and `configure(...)`. 2. Implement `MapState`. 3. Implement global map registry and current map context stack. 4. Implement `DirtyFlags`. 5. Implement `MapCommand`, `CommandKind`, and `MapCommandQueue`. 6. Implement coalescing rules: - latest overlay update per overlay wins - latest view update wins - structural commands preserve order 7. Implement logical overlay dataclasses: - `Overlay` - `MarkerOverlay` - `PolylineOverlay` - `TrajectoryOverlay` - `LayerState` 8. Implement public overlay creation/update functions so they: - resolve map context or `map_tag` - validate inputs - copy coordinate sequences - enqueue commands or mutate logical model under lock - never call Dear PyGui 9. Implement cache model: - memory cache metadata - disk path generation - disk metadata read/write - disk size scanning - prune planning without deleting active files yet 10. Add tests for: - command coalescing - overlay update does not alter center/zoom - trajectory input lists are copied - mismatched lat/lon lengths raise - layer state - disk cache path generation - disk cache prune ordering Checks: ```bash uv run pytest uv run ruff check . uv run ruff format --check . uv run pyright ``` Acceptance criteria: - runtime overlay update functions are thread-safe at the logical level - no public overlay function calls Dear PyGui - center/zoom cannot be changed by overlay commands - cache has separate memory and disk configuration - persistent disk cache layout is decided Commit: ```bash git add . git commit -m "step 2: add thread safe state commands and cache model" ``` ## Step 3 — Widget shell, sizing system, and GUI-thread frame pump Goal: create a stable Dear PyGui widget shell before loading real tiles. Tasks: 1. Implement `map_widget(...)`. 2. Internally create: ```text child_window drawlist ``` 3. Child window receives requested sizing values. 4. Drawlist receives concrete measured size only. 5. Implement `sizing.py`: - measure child content region - track last non-zero size - resize drawlist - detect hidden/show-later transitions - avoid permanent zero-size collapse 6. Implement the renderer frame pump: - schedules frame callbacks - drains commands on GUI thread - updates size - draws background and attribution placeholder - exposes debug state 7. Implement map interaction hit-test rectangle calculation, but not full panning yet. 8. Add examples: - `examples/basic_map.py` - `examples/sizing_window.py` - `examples/sizing_child.py` - `examples/sizing_table.py` - `examples/hidden_tab.py` 9. Add tests where possible without opening a GUI: - sizing state transitions - zero-size does not overwrite last non-zero size - dirty flags from resize - command drain ordering Manual checks: ```bash uv run python examples/basic_map.py uv run python examples/sizing_window.py uv run python examples/sizing_child.py uv run python examples/sizing_table.py uv run python examples/hidden_tab.py ``` Automated checks: ```bash uv run pytest uv run ruff check . uv run ruff format --check . uv run pyright ``` Acceptance criteria: - widget appears - map area fills its child container correctly - drawlist size matches measured child content size - hidden tab example loads/render placeholder after becoming visible - no map gets stuck at zero size - public API remains unchanged Commit: ```bash git add . git commit -m "step 3: add widget shell sizing and frame pump" ``` ## Step 4 — Tile manager, persistent cache, and asynchronous loading Goal: implement stable tile loading and both cache limits before advanced overlays. Tasks: 1. Implement `TileID`, `TileStatus`, `Tile`, and `TileManager`. 2. Implement visible tile calculation. 3. Implement persistent disk cache: - provider-namespaced paths - metadata files - `last_accessed_at` - disk size scanning - LRU pruning to `disk_cache_max_bytes` 4. Implement memory cache: - LRU or approximate LRU - `memory_cache_max_tiles` - visible-tile protection - deferred GUI-thread texture deletion 5. Implement worker thread/tile queue: - read disk cache - fetch HTTP if missing - decode tile image - return tile result with map generation and provider name - never call Dear PyGui 6. Renderer GUI thread: - processes tile results - ignores stale generation/provider results - creates textures - deletes evicted textures - draws tile layer 7. Ensure OpenStreetMap provider uses configured User-Agent or warns/raises clearly. 8. Add `examples/cache_stress.py`. Manual checks: ```bash uv run python examples/basic_map.py uv run python examples/cache_stress.py ``` Automated checks: ```bash uv run pytest uv run ruff check . uv run ruff format --check . uv run pyright ``` Acceptance criteria: - OSM tiles display - GUI does not freeze while tiles load - only visible tiles plus margin are requested - returning to recently viewed area uses memory cache - restarting app uses disk cache - disk cache prunes to configured limit - stale tile results are ignored after provider/generation change - texture deletion happens on GUI thread Commit: ```bash git add . git commit -m "step 4: add async tiles and persistent cache" ``` ## Step 5 — Interaction: pan, zoom, and view commands Goal: make interaction stable before adding live overlays. Tasks: 1. Implement left mouse drag panning. 2. Implement mouse wheel zoom. 3. Zoom around cursor where practical. 4. Implement programmatic view commands: - `set_center` - `set_zoom` - `set_view` - `fit_bounds` - `screen_to_latlon` - `latlon_to_screen` 5. Interaction must: - use concrete map rectangle - work in nested containers - not depend only on child-window hover state - not reset when overlay commands are queued - not recenter from tile results 6. Add debug fields for: - active drag - last mouse position - current center - current zoom - dirty flags Manual checks: ```bash uv run python examples/basic_map.py uv run python examples/sizing_child.py uv run python examples/sizing_table.py ``` Automated checks: ```bash uv run pytest uv run ruff check . uv run ruff format --check . uv run pyright ``` Acceptance criteria: - dragging pans smoothly - mouse wheel zooms - tile grid remains aligned - map works in nested containers - view commands work - no unexpected recenter occurs from tile loading Commit: ```bash git add . git commit -m "step 5: add stable pan zoom and view commands" ``` ## Step 6 — Overlay rendering and runtime update stress tests Goal: implement overlays in a way that cannot break interaction. Tasks: 1. Implement marker rendering. 2. Implement polyline rendering. 3. Implement trajectory rendering. 4. Implement overlay layer rendering separate from tile layer. 5. Overlay updates must set only `OVERLAYS` dirty unless a structural map operation requires more. 6. Overlay draw rebuild must not: - clear tile textures - clear tile draw state - reset center - reset zoom - reset active drag state - rebuild the whole map unnecessarily 7. Implement: - `add_marker` - `add_polyline` - `add_trajectory` - `update_marker` - `update_polyline` - `update_trajectory` - `set_marker_position` - `set_marker_label` - `set_polyline_points` - `set_overlay_show` - `delete_overlay` 8. Add examples: - `examples/markers_live_thread.py` - `examples/trajectory_live_thread.py` The live examples must update overlays from a background thread while the user drags and zooms. Manual stress checks: ```bash uv run python examples/markers_live_thread.py uv run python examples/trajectory_live_thread.py ``` While examples run: - drag continuously for at least 30 seconds - zoom in and out - verify no flicker through blank map - verify no random recentering - verify overlay remains geographically attached - verify update thread does not block badly Automated checks: ```bash uv run pytest uv run ruff check . uv run ruff format --check . uv run pyright ``` Acceptance criteria: - marker updates at 20–60 Hz do not break dragging - trajectory updates at 10–30 Hz do not break dragging - overlay updates are coalesced - center/zoom remain stable unless explicitly changed - overlay update APIs are safe from background threads - tile layer is not destroyed by overlay updates Commit: ```bash git add . git commit -m "step 6: add stable overlays and live update stress tests" ``` ## Step 7 — Layers, provider switching, and clearing APIs Goal: add structural map operations after rendering stability is proven. Tasks: 1. Implement layer operations: - `add_layer` - `show_layer` - `hide_layer` - `clear_layer` - `clear_map` 2. Implement provider switching: - `set_provider` 3. Provider switch must: - keep overlays - keep center - clamp zoom if needed - increment map generation - clear old provider tile draw state - ignore stale tile results - redraw attribution - not perform Dear PyGui work from caller thread 4. Implement cache control: - `clear_memory_cache` - `clear_disk_cache` - `get_cache_stats` 5. Add examples: - `examples/custom_provider.py` - extend `examples/cache_stress.py` Manual checks: ```bash uv run python examples/custom_provider.py uv run python examples/cache_stress.py ``` Automated checks: ```bash uv run pytest uv run ruff check . uv run ruff format --check . uv run pyright ``` Acceptance criteria: - layers can be shown/hidden/cleared - provider switch keeps overlays - provider switch does not render stale old-provider tiles - cache clear functions do not call Dear PyGui from non-GUI threads - memory/disk cache stats are usable Commit: ```bash git add . git commit -m "step 7: add layers provider switching and cache controls" ``` ## Step 8 — Documentation, hardening, and internal release Goal: make the package usable as a dependency in DataFlux or another project. Tasks: 1. Update `README.md` with: - uv install - local editable dependency - basic map - sizing examples - live marker update - live trajectory update - custom provider - memory/disk cache config - OpenStreetMap attribution/User-Agent note - thread-safety contract 2. Add docstrings for all public functions. 3. Review public API exports. 4. Add tests for failure cases: - unknown map - unknown overlay - unknown provider - invalid coordinates - mismatched lat/lon lengths - empty trajectory - clear deleted overlay - provider switch while tiles loading - overlay update while dragging model state 5. Run all examples manually. 6. Test local editable install from another project: ```bash cd ../some-test-project uv add -e ../dpg-map uv run python -c "import dpg_map as dpgm; print(dpgm.list_providers())" ``` 7. Bump version. Suggested initial rebuilt beta: ```text 0.3.0b1 ``` 8. Tag release: ```bash git tag v0.3.0b1 ``` Final checks: ```bash uv sync uv run pytest uv run ruff check . uv run ruff format --check . uv run pyright ``` Acceptance criteria: - all examples run - local editable dependency works - live overlay stress examples are stable - cache survives restart - disk cache limit works - sizing examples work - README accurately documents thread-safety and cache behaviour - AGENTS.md accurately describes status Commit: ```bash git add . git commit -m "step 8: harden docs and prepare rebuilt beta" git tag v0.3.0b1 ```