Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: CI

on:
pull_request:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- run: npm ci

- run: |
cp src/lib/config.example.js src/lib/config.js
npm run build
96 changes: 96 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Release & Deploy

on:
push:
tags:
- 'v*'

permissions:
contents: write
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Set version from tag
run: |
VERSION="${GITHUB_REF_NAME#v}"
npm version "$VERSION" --no-git-tag-version

- name: Generate release notes
id: notes
run: |
PREV_TAG=$(git tag --sort=-v:refname | grep '^v' | sed -n '2p')
if [ -z "$PREV_TAG" ]; then
RANGE="HEAD"
else
RANGE="${PREV_TAG}..HEAD"
fi

FEATURES=$(git log "$RANGE" --pretty=format:'%s' | grep -E '^feat' | sed 's/^/- /' || true)
FIXES=$(git log "$RANGE" --pretty=format:'%s' | grep -E '^fix' | sed 's/^/- /' || true)
OTHERS=$(git log "$RANGE" --pretty=format:'%s' | grep -vE '^(feat|fix|Merge)' | sed 's/^/- /' || true)

{
echo "notes<<NOTES_EOF"
if [ -n "$FEATURES" ]; then
echo "## Features"
echo "$FEATURES"
echo ""
fi
if [ -n "$FIXES" ]; then
echo "## Bug Fixes"
echo "$FIXES"
echo ""
fi
if [ -n "$OTHERS" ]; then
echo "## Other Changes"
echo "$OTHERS"
echo ""
fi
echo "NOTES_EOF"
} >> "$GITHUB_OUTPUT"

- name: Create GitHub release
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--notes "${{ steps.notes.outputs.notes }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- run: npm ci

- name: Build
run: |
cp src/lib/config.example.js src/lib/config.js
npm run build

- uses: actions/upload-pages-artifact@v3
with:
path: dist

deploy:
needs: release
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
51 changes: 51 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Create Release

on:
workflow_dispatch:
inputs:
bump:
description: 'Version bump type'
required: true
type: choice
options:
- patch
- minor
- major

permissions:
contents: write

jobs:
tag:
if: github.actor == github.repository_owner
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Compute next version
id: version
run: |
LATEST=$(git tag --sort=-v:refname | grep '^v' | head -1 || echo "v0.0.0")
MAJOR=$(echo "$LATEST" | sed 's/^v//' | cut -d. -f1)
MINOR=$(echo "$LATEST" | sed 's/^v//' | cut -d. -f2)
PATCH=$(echo "$LATEST" | sed 's/^v//' | cut -d. -f3)

case "${{ inputs.bump }}" in
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
patch) PATCH=$((PATCH + 1)) ;;
esac

echo "tag=v${MAJOR}.${MINOR}.${PATCH}" >> "$GITHUB_OUTPUT"

- name: Create and push tag
run: |
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git tag "${{ steps.version.outputs.tag }}"
git push origin "${{ steps.version.outputs.tag }}"

- name: Summary
run: echo "Created tag **${{ steps.version.outputs.tag }}** — deploy workflow will run next." >> "$GITHUB_STEP_SUMMARY"
26 changes: 26 additions & 0 deletions .hooks/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh

# Enforce conventional commit format: type(scope): description
# Types: feat, fix, chore, refactor, docs, test, style, perf, ci, build

msg=$(head -1 "$1")

# Allow merge commits
if echo "$msg" | grep -qE '^Merge '; then
exit 0
fi

if ! echo "$msg" | grep -qE '^(feat|fix|chore|refactor|docs|test|style|perf|ci|build)(\(.+\))?: .+'; then
echo ""
echo "Invalid commit message format:"
echo " $msg"
echo ""
echo "Expected: type(scope): description"
echo "Types: feat, fix, chore, refactor, docs, test, style, perf, ci, build"
echo "Examples:"
echo " feat(map): add walking route visualization"
echo " fix(api): handle Overpass 429 errors"
echo " docs(readme): update feature list"
echo ""
exit 1
fi
25 changes: 25 additions & 0 deletions .hooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/sh

echo "Running pre-commit checks..."

# Ensure config.js exists for build (use example if missing)
if [ ! -f src/lib/config.js ]; then
cp src/lib/config.example.js src/lib/config.js
CREATED_CONFIG=1
fi

npm run build --silent
BUILD_EXIT=$?

# Clean up temp config
if [ "$CREATED_CONFIG" = "1" ]; then
rm src/lib/config.js
fi

if [ $BUILD_EXIT -ne 0 ]; then
echo ""
echo "Build failed. Fix errors before committing."
exit 1
fi

echo "Pre-commit checks passed."
41 changes: 27 additions & 14 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Rendez-vous

A Svelte 5 web app that helps friends find a fair meeting spot (restaurant or bar) based on everyone's location. Users add friends by address, the app geocodes them, computes the centroid, and queries the Overpass API for nearby venues ranked by fairness (equal distance to all participants).
A Svelte 5 web app that helps friends find a fair meeting spot (restaurant or bar) based on everyone's location. Users add friends by name and address, the app geocodes them, computes the centroid, and queries the Overpass API for nearby venues ranked by fairness (equal distance or walking time).

## Tech Stack

Expand All @@ -10,21 +10,26 @@ A Svelte 5 web app that helps friends find a fair meeting spot (restaurant or ba
- **Map:** Leaflet
- **Geocoding:** Nominatim (OpenStreetMap)
- **Venue data:** Overpass API
- **Walking directions & time matrix:** OpenRouteService (requires API key)

## Project Structure

```
src/
App.svelte — root layout (header, sidebar, map)
main.js — mount point
app.css — global styles / Tailwind
App.svelte — root layout (header, sidebar, map)
main.js — mount point
app.css — global styles / Tailwind
lib/
stores.svelte.js — shared state (friends, venues, centroid) and business logic
AddressInput.svelte — geocoded address input
FriendList.svelte — list of added friends
ModeToggle.svelte — restaurant / bar toggle
VenueList.svelte — ranked venue results
MapView.svelte — Leaflet map with markers
config.js — local config with API key and limits (gitignored)
config.example.js — config template (committed)
stores.svelte.js — shared state (friends, venues, centroid, ranking) and business logic
groups.svelte.js — saved groups CRUD with localStorage persistence
AddressInput.svelte — geocoded address input with optional name
FriendList.svelte — list of added friends
GroupManager.svelte — save / load / edit / delete groups
ModeToggle.svelte — restaurant / bar toggle
VenueList.svelte — ranked venue results with per-friend distance breakdown
MapView.svelte — Leaflet map with markers and walking route polylines
```

## Commands
Expand All @@ -37,15 +42,23 @@ npm run preview # preview production build

## External APIs

This app calls these public APIs from the browser (no backend):
This app calls these APIs from the browser (no backend):

- **Nominatim** — address geocoding
- **Overpass API** — OpenStreetMap venue queries
- **Nominatim** — address geocoding (free, no key)
- **Overpass API** — OpenStreetMap venue queries (free, no key, rate-limited)
- **OpenRouteService** — walking directions and duration matrix (free tier, requires API key in `config.js`)

No API keys required. Both are rate-limited; be mindful of request frequency.
## Workflow Rules

- Before every commit or push, check that `README.md` and `CLAUDE.md` are up-to-date with any features, config changes, or structural changes introduced. Update them if needed before committing.
- Commit messages must follow conventional commit format: `type(scope): description`. Enforced by the `commit-msg` hook.
- To release: push a tag `v*` (e.g. `git tag v1.0.0 && git push origin v1.0.0`). The workflow bumps `package.json`, generates release notes from conventional commits, creates a GitHub release, and deploys to Pages.

## Notes

- No test framework is set up yet.
- No backend — everything runs client-side.
- State management uses Svelte 5 runes (`$state`) in `stores.svelte.js`, exported as shared reactive objects.
- Saved groups are persisted in `localStorage` (no database).
- `config.js` is gitignored — copy `config.example.js` and adjust values as needed.
- ORS API key is optional in config — users can enter their own key in the app UI (settings icon). Key is stored in `sessionStorage` only (cleared on browser close).
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,43 @@ Built with Svelte 5, Leaflet, and OpenStreetMap data. Runs entirely in the brows

```sh
npm install
cp src/lib/config.example.js src/lib/config.js
```

Then:

```sh
npm run dev
```

## Features

- Add friends by name (optional) and address
- Two ranking modes: **equidistant** (straight-line distance) or **walking time** (via OpenRouteService)
- ORS API key can be entered in the app UI (click the settings icon) — stored in session only, never persisted
- Walking route visualization on the map with a different color per friend
- Per-friend distance breakdown when selecting a venue
- Save and load groups of addresses (persisted in localStorage)
- km / miles toggle
- Configurable max distance between friends and max number of addresses

## How It Works

1. Each person enters their address (geocoded via Nominatim)
2. The app computes the geographic centroid of all participants
3. Nearby restaurants or bars are fetched from the Overpass API
4. Venues are ranked by fairness — lowest variance in distance to all friends comes first
4. Venues are ranked by fairness — lowest variance in distance (or walking time) to all friends
5. Click a venue to see walking routes from each friend's address

## Configuration

Edit `src/lib/config.js`:

| Variable | Default | Description |
|----------|---------|-------------|
| `ORS_API_KEY` | `''` | OpenRouteService API key (optional — users can enter their own in the UI) |
| `MAX_DISTANCE` | `10` | Max allowed distance between any two friends (in km) |
| `MAX_ADDRESSES` | `10` | Max number of friends/addresses |

## Tech Stack

Expand All @@ -25,6 +53,7 @@ npm run dev
- **Leaflet** for the interactive map
- **Nominatim** for geocoding
- **Overpass API** for venue search
- **OpenRouteService** for walking directions and time matrix

## Scripts

Expand All @@ -33,3 +62,20 @@ npm run dev
| `npm run dev` | Start dev server |
| `npm run build` | Production build |
| `npm run preview` | Preview production build |

## Deployment

Push a version tag to trigger a release and deploy to GitHub Pages:

```sh
git tag v1.0.0
git push origin v1.0.0
```

This automatically:
1. Bumps `package.json` version
2. Generates release notes from conventional commits
3. Creates a GitHub release
4. Deploys to GitHub Pages

Live at: https://bendns.github.io/rendez-vous/
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
"preview": "vite preview",
"prepare": "git config core.hooksPath .hooks"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.0.0",
Expand Down
Loading
Loading