Skip to content

Commit 61e21b8

Browse files
author
aligneddev
committed
plan
1 parent 97a671d commit 61e21b8

9 files changed

Lines changed: 675 additions & 0 deletions

File tree

.github/agents/copilot-instructions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Auto-generated from all feature plans. Last updated: 2026-03-13
55
## Active Technologies
66
- TypeScript 5.x (React 19 + Vite); .NET 10 C# / F# backend (unchanged) + `react-router-dom` v7 (new); React 19, Vite, ASP.NET Core Minimal API (existing) (003-user-login)
77
- `sessionStorage` (client-side auth session only); SQLite via EF Core (existing, unchanged) (003-user-login)
8+
- C# (.NET 10), F# (domain project present), TypeScript (React 19 + Vite) + ASP.NET Core Minimal API, EF Core, React, MSAL auth flow, existing outbox publisher services (004-create-the-record-ride-mvp)
9+
- SQLite local-file profile via EF Core (with existing SQL-compatible patterns) and outbox events table (004-create-the-record-ride-mvp)
810

911
- .NET 10 (C#), F# (domain project), TypeScript 5.x (Aurelia 2 frontend) + ASP.NET Core Minimal API, Microsoft Aspire AppHost, Entity Framework Core + SQLite provider, Aurelia 2 + `@aurelia/router`, .NET `System.Security.Cryptography` (PBKDF2), background worker for outbox retry (001-user-signup-pin)
1012

@@ -25,6 +27,7 @@ npm test; npm run lint
2527
.NET 10 (C#), F# (domain project), TypeScript 5.x (Aurelia 2 frontend): Follow standard conventions
2628

2729
## Recent Changes
30+
- 004-create-the-record-ride-mvp: Added C# (.NET 10), F# (domain project present), TypeScript (React 19 + Vite) + ASP.NET Core Minimal API, EF Core, React, MSAL auth flow, existing outbox publisher services
2831
- 003-user-login: Added TypeScript 5.x (React 19 + Vite); .NET 10 C# / F# backend (unchanged) + `react-router-dom` v7 (new); React 19, Vite, ASP.NET Core Minimal API (existing)
2932

3033
- 001-user-signup-pin: Added .NET 10 (C#), F# (domain project), TypeScript 5.x (Aurelia 2 frontend) + ASP.NET Core Minimal API, Microsoft Aspire AppHost, Entity Framework Core + SQLite provider, Aurelia 2 + `@aurelia/router`, .NET `System.Security.Cryptography` (PBKDF2), background worker for outbox retry
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Specification Quality Checklist: Record Ride Page
2+
3+
**Purpose**: Validate specification completeness and quality before proceeding to planning
4+
**Created**: 2026-03-20
5+
**Feature**: [spec.md](../spec.md)
6+
7+
## Content Quality
8+
9+
- [x] No implementation details (languages, frameworks, APIs)
10+
- [x] Focused on user value and business needs
11+
- [x] Written for non-technical stakeholders
12+
- [x] All mandatory sections completed
13+
14+
## Requirement Completeness
15+
16+
- [x] No [NEEDS CLARIFICATION] markers remain
17+
- [x] Requirements are testable and unambiguous
18+
- [x] Success criteria are measurable
19+
- [x] Success criteria are technology-agnostic (no implementation details)
20+
- [x] All acceptance scenarios are defined
21+
- [x] Edge cases are identified
22+
- [x] Scope is clearly bounded
23+
- [x] Dependencies and assumptions identified
24+
25+
## Feature Readiness
26+
27+
- [x] All functional requirements have clear acceptance criteria
28+
- [x] User scenarios cover primary flows
29+
- [x] Feature meets measurable outcomes defined in Success Criteria
30+
- [x] No implementation details leak into specification
31+
32+
## Notes
33+
34+
- All checklist items pass after initial validation.
35+
- No open clarification markers remain.
36+
- Spec is ready for `/speckit.clarify` or `/speckit.plan`.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
openapi: 3.0.3
2+
info:
3+
title: BikeTracking Ride Recording API
4+
version: 1.0.0
5+
description: Authenticated ride recording and rider default value retrieval.
6+
7+
paths:
8+
/api/rides:
9+
post:
10+
summary: Record a ride event for the authenticated rider
11+
operationId: recordRide
12+
requestBody:
13+
required: true
14+
content:
15+
application/json:
16+
schema:
17+
$ref: '#/components/schemas/RecordRideRequest'
18+
responses:
19+
'201':
20+
description: Ride recorded
21+
content:
22+
application/json:
23+
schema:
24+
$ref: '#/components/schemas/RecordRideSuccessResponse'
25+
'400':
26+
description: Validation failed
27+
content:
28+
application/json:
29+
schema:
30+
$ref: '#/components/schemas/ErrorResponse'
31+
'401':
32+
description: Unauthorized
33+
content:
34+
application/json:
35+
schema:
36+
$ref: '#/components/schemas/ErrorResponse'
37+
38+
/api/rides/defaults:
39+
get:
40+
summary: Get record-ride form defaults for the authenticated rider
41+
operationId: getRideDefaults
42+
responses:
43+
'200':
44+
description: Defaults resolved
45+
content:
46+
application/json:
47+
schema:
48+
$ref: '#/components/schemas/RideDefaultsResponse'
49+
'401':
50+
description: Unauthorized
51+
content:
52+
application/json:
53+
schema:
54+
$ref: '#/components/schemas/ErrorResponse'
55+
56+
components:
57+
schemas:
58+
RecordRideRequest:
59+
type: object
60+
required:
61+
- rideDateTimeLocal
62+
- miles
63+
properties:
64+
rideDateTimeLocal:
65+
type: string
66+
format: date-time
67+
description: Date/time entered by the rider.
68+
miles:
69+
type: number
70+
exclusiveMinimum: 0
71+
rideMinutes:
72+
type: integer
73+
nullable: true
74+
minimum: 1
75+
description: Optional duration in minutes.
76+
temperature:
77+
type: number
78+
nullable: true
79+
description: Optional ambient temperature in app-configured unit.
80+
81+
RecordRideSuccessResponse:
82+
type: object
83+
required:
84+
- rideId
85+
- riderId
86+
- savedAtUtc
87+
- eventStatus
88+
properties:
89+
rideId:
90+
type: integer
91+
format: int64
92+
riderId:
93+
type: integer
94+
format: int64
95+
savedAtUtc:
96+
type: string
97+
format: date-time
98+
eventStatus:
99+
type: string
100+
enum:
101+
- queued
102+
- published
103+
104+
RideDefaultsResponse:
105+
type: object
106+
required:
107+
- hasPreviousRide
108+
- defaultRideDateTimeLocal
109+
properties:
110+
hasPreviousRide:
111+
type: boolean
112+
defaultRideDateTimeLocal:
113+
type: string
114+
format: date-time
115+
defaultMiles:
116+
type: number
117+
nullable: true
118+
defaultRideMinutes:
119+
type: integer
120+
nullable: true
121+
defaultTemperature:
122+
type: number
123+
nullable: true
124+
125+
ErrorResponse:
126+
type: object
127+
required:
128+
- code
129+
- message
130+
properties:
131+
code:
132+
type: string
133+
message:
134+
type: string
135+
details:
136+
type: array
137+
items:
138+
type: string
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://biketracking.local/contracts/ride-recorded-event.schema.json",
4+
"title": "RideRecordedEvent",
5+
"type": "object",
6+
"additionalProperties": false,
7+
"required": [
8+
"eventId",
9+
"eventType",
10+
"occurredAtUtc",
11+
"riderId",
12+
"rideDateTimeLocal",
13+
"miles",
14+
"source"
15+
],
16+
"properties": {
17+
"eventId": {
18+
"type": "string",
19+
"description": "Unique event identifier."
20+
},
21+
"eventType": {
22+
"type": "string",
23+
"const": "RideRecorded"
24+
},
25+
"occurredAtUtc": {
26+
"type": "string",
27+
"format": "date-time"
28+
},
29+
"riderId": {
30+
"type": "integer",
31+
"minimum": 1
32+
},
33+
"rideDateTimeLocal": {
34+
"type": "string",
35+
"format": "date-time"
36+
},
37+
"miles": {
38+
"type": "number",
39+
"exclusiveMinimum": 0
40+
},
41+
"rideMinutes": {
42+
"type": "integer",
43+
"minimum": 1
44+
},
45+
"temperature": {
46+
"type": "number"
47+
},
48+
"source": {
49+
"type": "string",
50+
"enum": ["BikeTracking.Api"]
51+
}
52+
}
53+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Data Model: Record Ride Page MVP
2+
3+
**Branch**: `004-create-the-record-ride-mvp` | **Date**: 2026-03-20
4+
5+
## Overview
6+
7+
This feature introduces a ride recording command/query flow and a `RideRecorded` event payload persisted via the existing outbox event mechanism. It also introduces a per-rider defaults query model for pre-filling the form.
8+
9+
## Entities
10+
11+
### RecordRideCommand
12+
13+
Represents user-submitted form data sent to the API.
14+
15+
| Field | Type | Required | Validation | Notes |
16+
|-------|------|----------|------------|-------|
17+
| riderId | integer | Yes | >= 1 | Derived from authenticated session context |
18+
| rideDateTimeLocal | string (date-time) | Yes | Valid date-time | Exact user-entered value |
19+
| miles | number | Yes | > 0 | Decimal precision up to 2 places |
20+
| rideMinutes | integer | No | > 0 when provided | Optional duration |
21+
| temperature | number | No | none | Optional ambient temperature in existing app unit |
22+
23+
### RideRecordedEventPayload
24+
25+
Immutable event contract stored in outbox payload JSON and used for downstream publishing.
26+
27+
| Field | Type | Required | Description |
28+
|-------|------|----------|-------------|
29+
| eventId | string (guid) | Yes | Unique event identifier |
30+
| eventType | string | Yes | Constant `RideRecorded` |
31+
| occurredAtUtc | string (date-time) | Yes | Server timestamp for event creation |
32+
| riderId | integer | Yes | Rider identity |
33+
| rideDateTimeLocal | string (date-time) | Yes | User-entered ride date/time |
34+
| miles | number | Yes | Recorded miles |
35+
| rideMinutes | integer | No | Optional duration in minutes |
36+
| temperature | number | No | Optional temperature |
37+
| source | string | Yes | Constant `BikeTracking.Api` |
38+
39+
### RiderRideDefaults
40+
41+
Read model used by `GET /api/rides/defaults`.
42+
43+
| Field | Type | Required | Description |
44+
|-------|------|----------|-------------|
45+
| hasPreviousRide | boolean | Yes | Indicates if defaults can be populated from prior ride |
46+
| defaultMiles | number | No | Last saved miles value |
47+
| defaultRideMinutes | integer | No | Last saved optional minutes |
48+
| defaultTemperature | number | No | Last saved optional temperature |
49+
| defaultRideDateTimeLocal | string (date-time) | Yes | Always current local date/time at response generation |
50+
51+
## Relationships
52+
53+
- One rider can produce many `RideRecorded` events.
54+
- `RiderRideDefaults` is derived from the latest `RideRecorded` event for that rider.
55+
- `RecordRideCommand` transforms into exactly one `RideRecordedEventPayload` when validation passes.
56+
57+
## State Transitions
58+
59+
1. Authenticated rider opens `/rides/record`.
60+
2. Frontend requests `GET /api/rides/defaults`.
61+
3. User submits `RecordRideCommand` via `POST /api/rides`.
62+
4. API validates request.
63+
5. API writes ride row and outbox `RideRecorded` payload in one transaction.
64+
6. API returns success response.
65+
7. Outbox publisher asynchronously publishes the payload.
66+
67+
## Validation Rules
68+
69+
### Frontend
70+
71+
- `rideDateTimeLocal` is required.
72+
- `miles` is required and must be > 0.
73+
- `rideMinutes` is optional; when provided, must be > 0.
74+
- Failed submissions preserve user-entered values and show clear error feedback.
75+
76+
### API
77+
78+
- Repeat frontend numeric/date validation using request DTO validation and endpoint guards.
79+
- Reject malformed date-time or non-positive numeric values with `400`.
80+
- Ensure rider identity comes from authenticated context, not request body.
81+
82+
### Database
83+
84+
- Non-null constraints on required ride fields.
85+
- Check constraints for `miles > 0` and `rideMinutes > 0` when not null.
86+
- Required outbox metadata (`EventType`, payload JSON, occurrence timestamp).

0 commit comments

Comments
 (0)