Imported from Joseph5610/departs-app (
AGENTS.md). Install upstream withnpx skills add Joseph5610/departs-app. Copyright stays with the author.
CRITICAL CONSTRAINTS: departs-app
Real-time multi-city public transport tracking PWA (Prague PID, Brno IDS JMK). Vite + React 19 + TypeScript + Tailwind v4 + shadcn/ui. Backend: Cloudflare Pages Functions.
1. ARCHITECTURAL INVARIANTS (MANDATORY)
Non-negotiable. Any violation is a system-level bug.
State Model & Zustand Stores
- Single Source of Truth: Zustand stores (
selectionStore,viewportStore,preferencesStore,geolocationStore,mapMetadataStore,pwaStore,uiStore) manage global state. - Minimal State: Stores MUST ONLY store minimal IDs or primitive settings. Full objects/computed data MUST NEVER be stored in state; derive in hooks or use selectors.
- Zero-Context Architecture: React Context providers are avoided. Components access global state and actions directly from modular stores using granular selectors (e.g.,
s => s.value). - Pure Transformations:
useMemo,select, and data transforms MUST be pure.
| Store | Key File | Purpose |
|---|---|---|
SelectionStore |
selectionStore.ts |
State: isFollowing, selectedLine (URL is source of truth for IDs) |
ViewportStore |
viewportStore.ts |
Map bounds, debounced bounds, selected places |
PreferencesStore |
preferencesStore.ts |
User settings, favorites, search history (Persisted) |
GeolocationStore |
geolocationStore.ts |
User location, speed, geo-pending status |
MapMetadataStore |
mapMetadataStore.ts |
Map loaded state, label layer IDs, MapRef |
PWAStore |
pwaStore.ts |
PWA installation and update status |
UiStore |
uiStore.ts |
Which app-level modals are open (not persisted) |
Hook Data Flow (Strict Hierarchy)
- Layer 1:
data/(React Query): Talk to API, own cache. (e.g.,useVehicles,useDepartures) - Layer 2:
derived/(Logic): Merge multiple data sources into single objects. (e.g.,useSelectedVehicle,useMapFilters) - Layer 3:
features/(UI Glue): Side-effects, URL sync, camera, animations. (e.g.,useMapInterface) - STRICT RULE: Imports MUST flow one-way (1 -> 2 -> 3). NEVER import upward.
Vehicle Data Priority
useSelectedVehicle MUST merge sources with this priority:
- Detail API (Metadata via
useVehicleDetail) - Live Stream (Positions via
useVehicles) - Selection State (IDs)
If
is_static_fallback: truein Detail API, preserve live position/delay from stream.
2. PERFORMANCE & MAP CONSTRAINTS
Map MUST run at 60fps. React renders too slow for high-frequency updates.
- Bypass React: Visual updates to map layers (like the selected vehicle pulse) MUST bypass React state via
requestAnimationFrameand direct map mutations. - Direct Mutations: ONLY use
map.setPaintProperty()ormap.setLayoutProperty()for high-frequency animations. - Cleanup: All
requestAnimationFrameloops and map event listeners MUST have robust cleanup. - Memoization: Wrap map layer components in
React.memowith primitive props only. - React Query: Use
TRANSIT_REFRESH_MS(10s) for refreshes andkeepPreviousDatafor positions to prevent flicker.
3. UI & DOMAIN RULES
- DetailPanel Abstraction: Mobile (Vaul drawer) and Desktop (Sheet sidebar) MUST be managed by
DetailPanel. DO NOT break responsive switch logic. - GTFS Types:
0Tram,1Metro,2Rail,3Bus,4Ferry,7Funicular,11or800Trolleybus. - Metro Logic: Metro departures MUST be grouped by
(line + direction)— lines A/B/C have distinct directional identities. - Branding Authority: Transit colors and icons originate from backend-provided branding or centralized frontend config (
src/utils/mapIcons.ts). - Safe Areas: Use
env(safe-area-inset-*)for all layouts. - i18n: Czech (
cs) and English (en) locales viareact-i18next. Translation files insrc/i18n/locales/. - Normalization: Backend handlers in
functions/MUST follow: Validate -> Fetch -> Normalize -> Cache. Normalization MUST perform structural grouping server-side for frontend map layer performance.
4. OPERATIONAL RULES (AGENT WORKFLOW)
Forbidden Patterns (STRICT NEGATIVES)
- NEVER use raw
fetch(). All internal API calls MUST useapiFetch(from@/lib/api-client). - NEVER use manual
localStorage/sessionStorageor rawJSON.parsefor app state persistence; use Zustand stores withpersistmiddleware. - NEVER use repetitive emojis, icons, or visual filler.
- NEVER construct ad-hoc
border-dashedor custom empty containers; use established shadcnEmptyprimitives. - NEVER modify visual design during architectural refactors unless explicitly requested.
- NEVER use ad-hoc utility classes for core layout; use established design system tokens.
- NEVER store UI state (like drawer height) in global selection context.
- NEVER use
eslint-disable. All TypeScript and ESLint errors MUST be solved architecturally or typing-wise. Disabling the linter is strictly forbidden. - NEVER use
Array.prototype.find()or.filter()inside loops or.map()callbacks. O(N) nested searches (O(N^2) complexity) are strictly forbidden. Always build an O(1) indexMaporRecordbeforehand. - NEVER write comments that restate the code, narrate why a change was made, or record findings and measurements. Comment ONLY where the next reader would otherwise make a wrong edit, and then in ONE line stating the constraint — not the evidence for it. JSDoc on exported symbols is fine; explanatory paragraphs inside function bodies are not.
- NEVER introduce a loose
constfor a tunable value. Config belongs in a config object:_core/config.tsfor cross-adapter values,<adapter>/core/config.tsfor adapter-specific ones.
Mandatory Protocol
- Tool-First: Execute tools immediately, then report.
- Response Length (HARD LIMIT): Final responses MUST be under 6 sentences. No exceptions for "complex" work — complexity is a reason to write less, not more.
- Forbidden: recapping what you just did step by step, restating the user's question, tables or headings unless explicitly requested, listing what you did NOT do, previewing what you are about to do, apologising or self-critiquing at length.
- Required: lead with the answer or result. State caveats in one clause, not a section. If evidence is needed, show the command output, not prose about it.
- Offer detail instead of dumping it: "want the breakdown?" beats three paragraphs.
- Build & Quality Integrity: Run
npm run buildand ensuretscandlintpass for BOTH frontend and backend (functions/) before confirming any architectural change or concluding task.npm run buildis ONLY authority for final type validation. - Versioning: Increment
package.jsonexactly ONCE per conversational session (or logical commit), NOT repeatedly on every prompt. Group all incremental changes made during the session under a single version bump. NOTE: This rule applies ONLY to the maindeparts-apprepository. - Changelog: Maintain a single version block in
CHANGELOG.mdfor the entire session. NOTE: This rule applies ONLY to the maindeparts-apprepository.- Scope: Record ONLY new features (short 1-line description), important bug fixes, and important architectural changes.
- Forbidden: NEVER log internal code refactors, minor typing/lint fixes, variable renames, dev tool scripts, or transient bugs introduced and resolved within the same session.
- Format: Keep descriptions concise (max 1 short sentence per entry). DO NOT use nested sub-bullet lists detailing individual files or internal functions. Group all incremental session work under a single clean version header.
- Scratch & Testing: All scratch files, testing scripts, and temporary data MUST live in the
/scratchfolder at the root of the repository. This folder is git-ignored, ensuring the repository is not cluttered.
5. DATA PIPELINE & NORMALIZATION (BACKEND)
- Parallel Fetching: Fetch large independent datasets in parallel via
Promise.allwhere possible. - Two-Phase Grouping: Stop processing MUST follow two phases:
- Structural: Identify and create Parent Stations (e.g. location_type 1).
- Logical: Merge Regular Stops (e.g. location_type 0) into Structural Parent Stations when present.
- Centroid Authority: Centroids MUST be generated for every logical stop node. They must have
is_centroid: trueand an ID prefixed withcentroid-. - O(1) Lookups: Use
MaporRecordfor transit metadata lookups. Sequential array search (O(N)) is FORBIDDEN. - Strict Typing: All internal mapping methods must return strictly typed objects adhering to internal generic types (e.g.,
AppStopFeature,AppVehicleFeature). - Zod Validation Boundaries: Untrusted external inputs (user form payloads, KV storage reads, external API JSON responses) MUST be validated using Zod schemas (
safeParseorparse) before casting or processing.
6. LOCAL ENVIRONMENT
- Port: Dev server always runs on
http://localhost:8788(Cloudflare Pages proxy). Do NOT use5173.
API Endpoints & Logic Authority
- USER IS THE SOLE AUTHORITY ON ENDPOINTS AND LOGIC. NEVER alter, "fix", format, or restructure existing API endpoints, URL paths, or core business logic during refactors. If an endpoint looks weird (like using a semicolon
;gtfsTripId=), ASSUME IT IS CORRECT. Your job is ONLY to refactor architectural wrappers (like OOP classes, DI, typing), NOT to touch the underlying data flow or remote endpoints.