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
2 changes: 1 addition & 1 deletion .cursor/rules/00-product.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Build a free, ad-free, mobile-first PWA. Working name: Delhi Metro Simple. It is

The home task is: choose origin, choose destination, show one readable route. Answer which train, where to change, and the fare when the payload includes one.

Primary destinations are Plan, Map, and Help. Navigation is a compact top bar with an icon and a text label, so it does not cover the page. Wide screens use two panes: controls on the left, the route or the line on the right.
Primary destinations are Plan, Map, City, Saved, and Help. Navigation is a compact top bar with an icon and a text label, so it does not cover the page. Wide screens use two panes: controls on the left, the route or the line on the right.

Out of v1: accounts, payments, ticket sales, ads, analytics, chatbots, social feeds, and mandatory install, onboarding, or location permission.

Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/60-deployment.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ When the user says to deploy:
2. Commit on that branch. `type(scope): description`. One commit is one change.
3. Push the branch and open a pull request in this repository.
4. Wait for the `check` job in the CI workflow. It runs `npm ci`, `npm test`, and `npm run build`, rejects `dist` above 20,000 files or any file over 25 MiB, then uploads a preview. `npm run smoke` is not in the job.
5. Open the preview at `https://<branch>.detro.pages.dev`. A slash in the branch name becomes a hyphen. Confirm `/`, `/map`, `/city`, and `/help` load. If the check fails, stop and report the check and the URL.
5. Open the preview at `https://<branch>.detro.pages.dev`. A slash in the branch name becomes a hyphen. Confirm `/`, `/map`, `/city`, `/saved`, and `/help` load. If the check fails, stop and report the check and the URL.
6. Merge into `main` only after that job is green. Branch protection requires the `check` job.
7. The merge publishes production at `https://detro.pages.dev`.

Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/70-phone.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Check 320, 390, and 430 CSS pixels. The page does not scroll sideways.
- The route strip keeps one colored line. Station names wrap underneath it and do not overlap.
- A station name stays on one line. A title wraps at the arrow between the two stations.
- A pair of actions may sit side by side. If a label needs three lines, stack that row.
- The header is the mark plus four tabs. It stays on screen, clears the safe area, and does not cover the first line of the page.
- The header is the mark plus five tabs. It stays on screen, clears the safe area, and does not cover the first line of the page.
- On City, the map is visible without scrolling. The trip sheet is the part that scrolls.
- Do not add a banner, a tutorial, or extra chrome to fix spacing.
- Default type on a phone is compact. Help has a text-size slider. Do not leave a layout that only becomes readable after the user drags that slider.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The network, the fares, the platforms, and the first and last trains come from a
| Map | One line at a time, with the stations on that line. Start here and Go here set the trip. Station details list the facilities saved for that station. |
| City | 154 stations with a saved latitude and longitude, on street tiles kept in this repo. The ride is drawn between the ends that have positions. |
| Offline | After this browser has loaded the app once, the snapshot still plans the trip with the network off. |
| Saved trips | Stored on this device. A shared link carries the two station codes. |
| Saved trips | Own tab. Stored on this device. A shared link carries the two station codes. |
| Help | Usage, text size, and the snapshot date. |

254 stations are in the snapshot. The city map omits the ones with no saved position. The plan and the line map still include them.
Expand All @@ -31,7 +31,7 @@ The network, the fares, the platforms, and the first and last trains come from a

| Piece | Where | Job |
| --- | --- | --- |
| App | `src/` | Plan, map, city, and help. |
| App | `src/` | Plan, map, city, saved, and help. |
| Snapshot | `data/en/` | Lines, stations, briefs, coordinates, and one journey file per origin. |
| Street tiles | `public/map-tiles/` | Metro area, zoom 9 through 14. |

Expand Down
6 changes: 3 additions & 3 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Every later change uses a branch and a pull request. Do not commit or push direc

1. Branch from `main` and commit there.
2. Open a pull request. The `check` job in the CI workflow tests the build, enforces the file limits, and uploads a preview at `https://<branch>.detro.pages.dev`. A slash in the branch name becomes a hyphen.
3. Confirm `/`, `/map`, `/city`, and `/help` on that preview.
3. Confirm `/`, `/map`, `/city`, `/saved`, and `/help` on that preview.
4. Merge only after the `check` job is green. Branch protection requires that job.
5. The merge publishes production at `https://detro.pages.dev`.

Expand All @@ -25,7 +25,7 @@ The numbered procedure below is the first-time setup. It is already done. Do not
5. Create the Cloudflare Pages project `detro` as Direct Upload, not a Git-connected build. The production branch is `main`. The public URL is `https://detro.pages.dev`.
6. Create a Cloudflare API token that can edit that Pages project only. Store it as the GitHub secret `CLOUDFLARE_API_TOKEN`, and store the account id as `CLOUDFLARE_ACCOUNT_ID`. Do not print the token and do not commit it.
7. Add `.github/workflows/ci.yml` as specified below. Require that check on `main` before a merge.
8. Open a pull request and confirm the preview URL loads `/`, `/map`, `/city`, and `/help`.
8. Open a pull request and confirm the preview URL loads `/`, `/map`, `/city`, `/saved`, and `/help`.
9. Merge to `main` and open `https://detro.pages.dev` on a phone. Turn the network off and reload. Search, a saved fare, a station brief, the line map, and the city map must still open.

Stop at the first failed step and report it. Do not retry a deploy by connecting Pages to Git.
Expand Down Expand Up @@ -149,7 +149,7 @@ No app environment variables. No install of private packages.

## Addresses inside the app

The app uses `/`, `/map`, `/city`, and `/help`. Pages must serve `index.html` for those paths when a file of that name does not exist. `public/_redirects` contains:
The app uses `/`, `/map`, `/city`, `/saved`, and `/help`. Pages must serve `index.html` for those paths when a file of that name does not exist. `public/_redirects` contains:

```text
/* /index.html 200
Expand Down
12 changes: 7 additions & 5 deletions docs/PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ A child with an adult, an older adult, a first-time visitor, and someone unfamil

## Destinations

1. **Plan a trip** — origin, destination, swap, and “Show my route.” On a wide screen the route sits beside the form. A saved trip can be opened or removed.
1. **Plan a trip** — origin, destination, swap, and “Show my route.” On a wide screen the route sits beside the form.
2. **Map** — one line at a time, with a sentence that says to pick a line and then a station. Start here and Go here sit with the chosen station. On a wide screen the line diagram sits beside the list.
3. **Help** — how to use the app, verified contacts, text size, offline status, privacy, and sources.
3. **City** — stations with a saved coordinate on street tiles. The ride is drawn between the ends that have positions.
4. **Saved** — trips stored on this device. Open one to see the ride, or remove it.
5. **Help** — how to use the app, verified contacts, text size, offline status, privacy, and sources.

Plan, Map, and Help live in a top bar. The bar does not cover the page.
Plan, Map, City, Saved, and Help live in a top bar. The bar does not cover the page.

Station details open from search, the map, and journey results. They are not a fourth top-level destination.
Station details open from search, the map, and journey results. They are not a top-level destination.

## Home

The first screen shows “Where do you want to go?”, From, To, swap, and one primary route button. “Use my location” is secondary. Recent or saved trips appear only when some exist.
The first screen shows “Where do you want to go?”, From, To, swap, and one primary route button. “Use my location” is secondary. Saved trips live under Saved, not on Plan.

Search supports English and Hindi names, aliases, and spelling variants. Suggestions show the line when membership is known. Similar names stay distinct. A fuzzy match is never selected silently.

Expand Down
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ <h1>DETRO — Delhi Metro Simple</h1>
<a href="/">Plan a route</a>
<a href="/map">Delhi Metro line map</a>
<a href="/city">Delhi Metro city map</a>
<a href="/saved">Saved trips</a>
<a href="/help">Help</a>
</nav>
</main>
Expand Down
3 changes: 3 additions & 0 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
<url>
<loc>https://detro.pages.dev/city</loc>
</url>
<url>
<loc>https://detro.pages.dev/saved</loc>
</url>
<url>
<loc>https://detro.pages.dev/help</loc>
</url>
Expand Down
44 changes: 29 additions & 15 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import type { Journey, Lang, Suggestion } from "./lib/transit/types"
import { HelpView } from "./ui/HelpView"
import { MapView } from "./ui/MapView"
import { PlanView } from "./ui/PlanView"
import { SavedView } from "./ui/SavedView"
import { CityMap } from "./ui/CityMap"
import { pathForTab, tabFromPath } from "./lib/nav"
import { TabBar, type Tab } from "./ui/TabBar"
import { SignalMark, TabBar, type Tab } from "./ui/TabBar"

const snapshot = loadSnapshot()
const snapshotDate = snapshot.fetchedAt.slice(0, 10)
Expand All @@ -41,6 +42,12 @@ function pageMeta(tab: Tab): { title: string; description: string } {
description: "See a Delhi Metro ride on the city map in DETRO. Choose a start and a destination and follow the line between them.",
}
}
if (tab === "saved") {
return {
title: "Saved trips · DETRO",
description: "Open a saved Delhi Metro trip in DETRO. Trips stay on this phone. Free, ad-free, and independent of DMRC.",
}
}
if (tab === "help") {
return {
title: "Help · DETRO",
Expand Down Expand Up @@ -447,7 +454,8 @@ export function App() {
<span className="mark-name">DETRO</span>
<span className="mark-tag">Delhi Metro Simple</span>
</a>
<TabBar copy={copy.en} tab={tab} online={online} onTab={openTab} />
<TabBar copy={copy.en} tab={tab} onTab={openTab} />
<SignalMark copy={copy.en} online={online} />
</header>
{tab === "plan" ? (
<PlanView
Expand Down Expand Up @@ -480,19 +488,6 @@ export function App() {
clearResult()
}}
onSubmit={onSubmit}
trips={trips.map((trip) => ({
fromCode: trip.fromCode,
toCode: trip.toCode,
label: `${namesFor(snapshot.stations, lang).get(trip.fromCode) ?? trip.fromCode} → ${namesFor(snapshot.stations, lang).get(trip.toCode) ?? trip.toCode}`,
}))}
onOpenTrip={openTrip}
onRemoveTrip={(fromCode, toCode) => {
try {
setTrips(removeTrip(localStorage, { fromCode, toCode }))
} catch {
setTrips((current) => current.filter((trip) => trip.fromCode !== fromCode || trip.toCode !== toCode))
}
}}
onSave={() => {
if (!state.from || !state.to) return
setTrips(saveTrip(localStorage, { fromCode: state.from.code, toCode: state.to.code }))
Expand All @@ -512,6 +507,25 @@ export function App() {
}}
/>
) : null}
{tab === "saved" ? (
<SavedView
copy={copy[lang]}
trips={trips.map((trip) => ({
fromCode: trip.fromCode,
toCode: trip.toCode,
fromName: namesFor(snapshot.stations, lang).get(trip.fromCode) ?? trip.fromCode,
toName: namesFor(snapshot.stations, lang).get(trip.toCode) ?? trip.toCode,
}))}
onOpenTrip={openTrip}
onRemoveTrip={(fromCode, toCode) => {
try {
setTrips(removeTrip(localStorage, { fromCode, toCode }))
} catch {
setTrips((current) => current.filter((trip) => trip.fromCode !== fromCode || trip.toCode !== toCode))
}
}}
/>
) : null}
{tab === "map" ? (
<MapView
copy={copy[lang]}
Expand Down
15 changes: 15 additions & 0 deletions src/i18n/copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export type Copy = {
plan: string
map: string
help: string
saved: string
onlineStatus: string
offlineStatus: string
lineLabel: string
findOnLine: string
zoomIn: string
Expand Down Expand Up @@ -96,6 +99,8 @@ export type Copy = {
sources: string
noBrief: string
savedTrips: string
savedLead: string
savedEmpty: string
saveTrip: string
removeTrip: string
removeSaved: (label: string) => string
Expand Down Expand Up @@ -195,6 +200,9 @@ export const copy = {
plan: "Plan",
map: "Map",
help: "Help",
saved: "Saved",
onlineStatus: "Online",
offlineStatus: "Offline",
lineLabel: "Line",
findOnLine: "Find a station on this line",
zoomIn: "Zoom in",
Expand Down Expand Up @@ -226,6 +234,8 @@ export const copy = {
sources: "Data comes from the Delhi Metro website backend. This app is independent.",
noBrief: "More detail for this station is not in the saved snapshot yet.",
savedTrips: "Saved trips",
savedLead: "Trips you save stay on this phone.",
savedEmpty: "Save a trip from Plan after you have a route.",
saveTrip: "Save trip",
removeTrip: "Remove",
removeSaved: (label) => `Remove ${label}`,
Expand Down Expand Up @@ -324,6 +334,9 @@ export const copy = {
plan: "योजना",
map: "नक्शा",
help: "सहायता",
saved: "सेव",
onlineStatus: "ऑनलाइन",
offlineStatus: "ऑफ़लाइन",
lineLabel: "लाइन",
findOnLine: "इस लाइन पर स्टेशन खोजें",
zoomIn: "बड़ा करें",
Expand Down Expand Up @@ -355,6 +368,8 @@ export const copy = {
sources: "डेटा दिल्ली मेट्रो वेबसाइट के बैकएंड से है। यह ऐप स्वतंत्र है।",
noBrief: "इस स्टेशन का और विवरण सेव की गई सूची में अभी नहीं है।",
savedTrips: "सेव यात्राएँ",
savedLead: "सेव की गई यात्राएँ इस फ़ोन पर रहती हैं।",
savedEmpty: "रास्ता दिखने के बाद योजना से यात्रा सेव करें।",
saveTrip: "यात्रा सेव करें",
removeTrip: "हटाएँ",
removeSaved: (label) => `${label} हटाएँ`,
Expand Down
Loading
Loading