Skip to content

Commit c14d165

Browse files
committed
address search, changelog system, update AGENTS + README
Search: server-proxied Nominatim geocoding with rate limiting and 24h cache. Search bar moved inside the info panel with inline spinner and attribution. (#11) Changelog: CHANGELOG.md with project history, cli.py subcommand to generate an HTML fragment, changelog modal with localStorage-based new-update notification dot, Dockerfile build-time generation. AGENTS.md: changelog guidelines (user-facing language, when to update, agent behavior) and README sync convention. README: added /search endpoint to API table.
1 parent b91da19 commit c14d165

12 files changed

Lines changed: 1130 additions & 97 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ __pycache__/
66
*.egg-info/
77
dist/
88
.ruff_cache/
9+
src/where_the_plow/static/changelog.html

AGENTS.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,32 @@ See the existing migrations in `db.init()` for examples (e.g. `geom` on
5151
`ip` + `user_agent` for fingerprinting.
5252
- `_client_ip(request)` helper in `routes.py` extracts IP from
5353
`X-Forwarded-For` with fallback to `request.client.host`.
54+
- `README.md` documents the API endpoint table, environment variables,
55+
database schema, and stack. When adding or removing endpoints, update
56+
the API table in the README to match.
57+
58+
## Changelog
59+
60+
`CHANGELOG.md` at the project root is for **end users**, not developers.
61+
Write in plain English -- no file paths, no code snippets, no technical
62+
jargon. Each entry links to a GitHub commit range via
63+
`[View changes](compare URL)` so technical users can drill in.
64+
65+
**Format:** Each entry is `## YYYY-MM-DD — Title` followed by a few
66+
sentences, an optional `**Contributors:**` line (only for non-Jack
67+
contributors), and a `[View changes]()` link. Increment the
68+
`<!-- changelog-id: N -->` integer at the top whenever a new entry is added.
69+
70+
**When to update:** User-facing features, significant bug fixes, and new
71+
data sources warrant a changelog entry. Internal refactors, code cleanup,
72+
CI changes, and documentation do not.
73+
74+
**Agent behavior:** If a change looks changelog-worthy, mention it to the
75+
user ("This might warrant a changelog entry") but do NOT add it
76+
automatically. The user decides when to batch changes into an entry,
77+
typically before finishing a branch. Reference GitHub issues with `(#N)`
78+
when relevant.
79+
80+
**Generating HTML:** After editing `CHANGELOG.md`, run
81+
`uv run python cli.py changelog` to regenerate the HTML fragment. The
82+
Dockerfile does this automatically at build time.

CHANGELOG.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!-- changelog-id: 5 -->
2+
# Changelog
3+
4+
## 2026-02-22 - Address Search
5+
You can now search for a street address and jump directly to it on the map,
6+
instead of scrolling and zooming manually.
7+
8+
**Contributors:** [@blossom2016](https://github.com/blossom2016)
9+
[View changes](https://github.com/jackharrhy/where-the-plow/compare/c5672ad...42ddc1e)
10+
11+
## 2026-02-22 - Email Signups & About Modal
12+
New welcome modal with information about the project and an email signup form.
13+
Leave your email to get notified when street-level plow alerts are ready.
14+
Social sharing images and SEO metadata added.
15+
16+
[View changes](https://github.com/jackharrhy/where-the-plow/compare/ae41e5f...c5672ad)
17+
18+
## 2026-02-21 - Coverage Playback Controls
19+
Play back coverage data as a time-lapse animation. Filter by vehicle type
20+
using the legend checkboxes. Follow a specific vehicle during playback.
21+
Improved time range slider and mobile layout.
22+
23+
[View changes](https://github.com/jackharrhy/where-the-plow/compare/5036734...ae41e5f)
24+
25+
## 2026-02-19 - Map Legend & Geolocate
26+
Collapsible legend showing vehicle types with color coding. "Locate me" button
27+
to center the map on your position.
28+
29+
**Contributors:** [@AminTaheri23](https://github.com/AminTaheri23)
30+
[View changes](https://github.com/jackharrhy/where-the-plow/compare/5036734...7f4db6c)
31+
32+
## 2026-02-19 - Coverage History & Heatmap
33+
View which streets have been plowed over the last 6, 12, or 24 hours. Switch
34+
between route lines and a heatmap view. Pick a specific date to review past
35+
coverage. Time slider to scrub through the window.
36+
37+
[View changes](https://github.com/jackharrhy/where-the-plow/compare/4402d55...54f335f)
38+
39+
## 2026-02-19 - Launch
40+
Live map of St. John's snowplow fleet. Vehicles update every 6 seconds from
41+
the City of St. John's AVL system. Click any vehicle to see its recent trail.
42+
Data is stored for historical playback.
43+
44+
[View changes](https://github.com/jackharrhy/where-the-plow/compare/2a08888...4402d55)

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ COPY pyproject.toml uv.lock ./
88
RUN uv sync --frozen --no-dev
99

1010
COPY src/ src/
11+
COPY CHANGELOG.md cli.py ./
12+
RUN uv run python cli.py changelog
1113

1214
EXPOSE 8000
1315

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ All geo endpoints return GeoJSON. Full OpenAPI docs at [`/docs`](https://plow.ja
4949
| `GET /vehicles/nearby?lat=&lng=&radius=` | Vehicles within radius (meters) |
5050
| `GET /vehicles/{id}/history?since=&until=` | Position history for one vehicle |
5151
| `GET /coverage?since=&until=` | Per-vehicle LineString trails with timestamps |
52+
| `GET /search?q=` | Geocode an address via Nominatim (cached proxy) |
5253
| `GET /stats` | Collection statistics |
5354
| `GET /health` | Health check |
5455
| `POST /track` | Record anonymous viewport focus event |

cli.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"""Dev CLI for where-the-plow."""
22

3+
import re
34
import subprocess
45
import sys
6+
from pathlib import Path
57

68
COMMANDS = {
79
"dev": "Run uvicorn in development mode with auto-reload",
810
"start": "Run uvicorn in production mode",
11+
"changelog": "Convert CHANGELOG.md to an HTML fragment",
912
}
1013

1114
APP = "where_the_plow.main:app"
@@ -43,6 +46,87 @@ def start():
4346
)
4447

4548

49+
def _md_inline(text: str) -> str:
50+
"""Convert inline markdown to HTML: bold, links, and issue references."""
51+
# Convert **text** to <strong>text</strong>
52+
text = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", text)
53+
# Convert [text](url) to <a> tags
54+
text = re.sub(
55+
r"\[(.+?)\]\((.+?)\)",
56+
r'<a href="\2" target="_blank" rel="noopener">\1</a>',
57+
text,
58+
)
59+
# Convert standalone (#N) to issue links
60+
text = re.sub(
61+
r"\(#(\d+)\)",
62+
r'(<a href="https://github.com/jackharrhy/where-the-plow/issues/\1" target="_blank" rel="noopener">#\1</a>)',
63+
text,
64+
)
65+
return text
66+
67+
68+
def changelog():
69+
root = Path(__file__).parent
70+
md_path = root / "CHANGELOG.md"
71+
out_path = root / "src" / "where_the_plow" / "static" / "changelog.html"
72+
73+
content = md_path.read_text()
74+
75+
# Extract changelog-id
76+
id_match = re.search(r"<!--\s*changelog-id:\s*(\d+)\s*-->", content)
77+
changelog_id = id_match.group(1) if id_match else "0"
78+
79+
# Split on ## headings; first chunk is the header/preamble, skip it
80+
sections = re.split(r"^## ", content, flags=re.MULTILINE)
81+
82+
articles = []
83+
for section in sections[1:]:
84+
lines = section.strip()
85+
# First line is the title
86+
title, _, body = lines.partition("\n")
87+
title = title.strip()
88+
body = body.strip()
89+
90+
# Split body into paragraphs on double newlines
91+
paragraphs = re.split(r"\n\n+", body)
92+
p_html = []
93+
for para in paragraphs:
94+
if not para.strip():
95+
continue
96+
# Within a paragraph block, lines starting with [ (a link)
97+
# are separate paragraphs (e.g. "View changes" links)
98+
sub_parts: list[list[str]] = [[]]
99+
for line in para.strip().splitlines():
100+
stripped = line.strip()
101+
if not stripped:
102+
continue
103+
if stripped.startswith("[") and sub_parts[-1]:
104+
sub_parts.append([stripped])
105+
else:
106+
sub_parts[-1].append(stripped)
107+
for part in sub_parts:
108+
if not part:
109+
continue
110+
text = " ".join(part)
111+
text = _md_inline(text)
112+
p_html.append(f"<p>{text}</p>")
113+
114+
article = (
115+
f"<article>\n<h2>{_md_inline(title)}</h2>\n"
116+
+ "\n".join(p_html)
117+
+ "\n</article>"
118+
)
119+
articles.append(article)
120+
121+
html = f'<div class="changelog" data-changelog-id="{changelog_id}">\n'
122+
html += "\n".join(articles)
123+
html += "\n</div>\n"
124+
125+
out_path.parent.mkdir(parents=True, exist_ok=True)
126+
out_path.write_text(html)
127+
print(f"Wrote changelog.html (changelog-id: {changelog_id})")
128+
129+
46130
def usage():
47131
print("Usage: uv run cli.py <command>\n")
48132
print("Commands:")
@@ -56,7 +140,7 @@ def main():
56140
usage()
57141

58142
cmd = sys.argv[1]
59-
{"dev": dev, "start": start}[cmd]()
143+
{"dev": dev, "start": start, "changelog": changelog}[cmd]()
60144

61145

62146
if __name__ == "__main__":
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Changelog System Design
2+
3+
## Summary
4+
5+
Add a user-facing changelog to the site. CHANGELOG.md at project root, a CLI
6+
subcommand to convert it to an HTML fragment, baked into the Docker image at
7+
build time, a new Changelog dialog in the frontend, and a "new updates"
8+
notification dot using localStorage.
9+
10+
## CHANGELOG.md Format
11+
12+
```markdown
13+
<!-- changelog-id: N -->
14+
# Changelog
15+
16+
## YYYY-MM-DD — Title
17+
Plain-English description of what changed. No code paths.
18+
References issues inline like (#11).
19+
20+
**Contributors:** @username (only for non-Jack contributors)
21+
[View changes](https://github.com/jackharrhy/where-the-plow/compare/abc...def)
22+
```
23+
24+
- `changelog-id` is an integer, incremented on each new entry
25+
- Reverse-chronological (newest first)
26+
- User-facing language only, link commit ranges for technical detail
27+
- Issue references as `(#N)` — converted to GitHub links in HTML
28+
29+
## CLI Subcommand
30+
31+
`cli.py changelog` — parses CHANGELOG.md, outputs HTML fragment to
32+
`src/where_the_plow/static/changelog.html`.
33+
34+
Markdown parsing is minimal, using `re` — no external dependency:
35+
- Split on `## ` to get entries
36+
- Convert `**text**``<strong>`
37+
- Convert `[text](url)``<a>`
38+
- Convert `(#N)` → GitHub issue link
39+
- Extract `changelog-id` from HTML comment
40+
- Output: `<div data-changelog-id="N">` with `<article>` per entry
41+
42+
## Dockerfile Integration
43+
44+
After `COPY src/ src/`, add:
45+
```dockerfile
46+
COPY CHANGELOG.md cli.py ./
47+
RUN uv run python cli.py changelog
48+
```
49+
50+
HTML fragment is baked into image at `/app/src/where_the_plow/static/changelog.html`.
51+
52+
## Frontend
53+
54+
### New HTML elements (index.html)
55+
- `#panel-footer`: add `<a href="#" id="btn-view-changelog">Changelog</a>`
56+
- About section in welcome modal: add `<button id="btn-about-changelog">`
57+
- New `#changelog-overlay` / `#changelog-modal` pair (same pattern as welcome)
58+
59+
### JavaScript (app.js)
60+
- `CHANGELOG_KEY = "wtp-changelog-seen"` — stores last-seen changelog-id
61+
- On init: fetch `/static/changelog.html`, parse `data-changelog-id`, compare
62+
with localStorage, add `has-update` class to footer link if newer
63+
- `showChangelog()`: inject HTML, show overlay, update localStorage, remove dot
64+
- `hideChangelog()`: hide overlay
65+
- Wired to: footer link, about-dialog button, close button, overlay click
66+
67+
### CSS (style.css)
68+
- Reuse modal overlay/card pattern from welcome modal
69+
- `.has-update::after` — notification dot (accent color circle)
70+
- Changelog article styling: entry spacing, date headers
71+
72+
## AGENTS.md Addition
73+
74+
New `## Changelog` section with guidelines on when to update, what to write,
75+
and how to suggest updates without auto-adding.

0 commit comments

Comments
 (0)