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
67 changes: 67 additions & 0 deletions .copilot/skills/caveman/SKILLS.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
name: caveman
description: >
Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman
while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra,
wenyan-lite, wenyan-full, wenyan-ultra.
Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens",
"be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested.
---

Respond terse like smart caveman. All technical substance stay. Only fluff die.

## Persistence

ACTIVE EVERY RESPONSE. No revert after many turns. No filler drift. Still active if unsure. Off only: "stop caveman" / "normal mode".

Default: **full**. Switch: `/caveman lite|full|ultra`.

## Rules

Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Fragments OK. Short synonyms (big not extensive, fix not "implement a solution for"). Technical terms exact. Code blocks unchanged. Errors quoted exact.

Pattern: `[thing] [action] [reason]. [next step].`

Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
Yes: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:"

## Intensity

| Level | What change |
|-------|------------|
| **lite** | No filler/hedging. Keep articles + full sentences. Professional but tight |
| **full** | Drop articles, fragments OK, short synonyms. Classic caveman |
| **ultra** | Abbreviate (DB/auth/config/req/res/fn/impl), strip conjunctions, arrows for causality (X → Y), one word when one word enough |
| **wenyan-lite** | Semi-classical. Drop filler/hedging but keep grammar structure, classical register |
| **wenyan-full** | Maximum classical terseness. Fully 文言文. 80-90% character reduction. Classical sentence patterns, verbs precede objects, subjects often omitted, classical particles (之/乃/為/其) |
| **wenyan-ultra** | Extreme abbreviation while keeping classical Chinese feel. Maximum compression, ultra terse |

Example — "Why React component re-render?"
- lite: "Your component re-renders because you create a new object reference each render. Wrap it in `useMemo`."
- full: "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`."
- ultra: "Inline obj prop → new ref → re-render. `useMemo`."
- wenyan-lite: "組件頻重繪,以每繪新生對象參照故。以 useMemo 包之。"
- wenyan-full: "物出新參照,致重繪。useMemo .Wrap之。"
- wenyan-ultra: "新參照→重繪。useMemo Wrap。"

Example — "Explain database connection pooling."
- lite: "Connection pooling reuses open connections instead of creating new ones per request. Avoids repeated handshake overhead."
- full: "Pool reuse open DB connections. No new connection per request. Skip handshake overhead."
- ultra: "Pool = reuse DB conn. Skip handshake → fast under load."
- wenyan-full: "池reuse open connection。不每req新開。skip handshake overhead。"
- wenyan-ultra: "池reuse conn。skip handshake → fast。"

## Auto-Clarity

Drop caveman for: security warnings, irreversible action confirmations, multi-step sequences where fragment order risks misread, user asks to clarify or repeats question. Resume caveman after clear part done.

Example — destructive op:
> **Warning:** This will permanently delete all rows in the `users` table and cannot be undone.
> ```sql
> DROP TABLE users;
> ```
> Caveman resume. Verify backup exist first.

## Boundaries

Code/commits/PRs: write normal. "stop caveman" or "normal mode": revert. Level persist until changed or session end.
2 changes: 1 addition & 1 deletion .specify/feature.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"feature_directory": "specs/019-ride-difficulty-wind"
"feature_directory": "specs/020-improve-ride-preset-options"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Specification Quality Checklist: Improve Ride Entry Preset Options

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-04-29
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- Validation pass 1: all checklist items satisfied.
- This specification explicitly supersedes previous ride-entry autofill behavior from earlier quick-entry spec.
- Clarified decisions (2026-04-29): preset stores exact start time; ride-entry preset list ordered by most recently used; legacy quick-entry UI deleted for all users.
143 changes: 143 additions & 0 deletions specs/020-improve-ride-preset-options/contracts/api-contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# API Contracts: Ride Preset Options

**Feature**: 020-improve-ride-preset-options
**Date**: 2026-04-29

## Contract Summary

This feature introduces rider-scoped ride preset CRUD and removes legacy quick-options API usage.

- Added: preset CRUD endpoints
- Added: `selectedPresetId` in ride create request
- Removed: legacy quick-options endpoint from ride-entry behavior

## 1) GET /api/rides/presets

Returns rider-owned presets in MRU order.

Ordering:
1. `lastUsedAtUtc` descending (`null` values last)
2. `updatedAtUtc` descending

Response `200`:

```json
{
"presets": [
{
"presetId": 42,
"name": "Morning Commute",
"primaryDirection": "SW",
"periodTag": "morning",
"exactStartTimeLocal": "07:45",
"durationMinutes": 34,
"miles": 7.2,
"lastUsedAtUtc": "2026-04-29T13:02:31Z",
"updatedAtUtc": "2026-04-29T12:50:00Z"
}
],
"generatedAtUtc": "2026-04-29T13:05:00Z"
}
```

## 2) POST /api/rides/presets

Creates a new rider-owned preset.

Request:

```json
{
"name": "Afternoon Return",
"primaryDirection": "NE",
"periodTag": "afternoon",
"exactStartTimeLocal": "17:35",
"durationMinutes": 32,
"miles": 7.2
}
```

Response `201`:

```json
{
"presetId": 43,
"name": "Afternoon Return",
"primaryDirection": "NE",
"periodTag": "afternoon",
"exactStartTimeLocal": "17:35",
"durationMinutes": 32,
"miles": 7.2,
"lastUsedAtUtc": null,
"updatedAtUtc": "2026-04-29T13:10:00Z"
}
```

Validation errors `400` include:
- duplicate preset name for rider
- invalid `exactStartTimeLocal`
- invalid `durationMinutes`

## 3) PUT /api/rides/presets/{presetId}

Updates an existing rider-owned preset.

Request body shape matches POST.

Response `200`: updated preset DTO.

Error `404`: preset not found for rider.

## 4) DELETE /api/rides/presets/{presetId}

Deletes rider-owned preset.

Response `200`:

```json
{
"presetId": 43,
"deletedAtUtc": "2026-04-29T13:20:00Z",
"message": "Preset deleted"
}
```

## 5) POST /api/rides (extended)

Existing record-ride request is extended with optional `selectedPresetId`.

Request addition:

```json
{
"selectedPresetId": 42
}
```

Behavior:
- If `selectedPresetId` is present and belongs to rider, backend updates that preset's `lastUsedAtUtc` after successful ride save.
- If omitted, ride save behavior remains unchanged.

## 6) Removed Legacy Contract

Legacy behavior to remove:
- `GET /api/rides/quick-options`
- frontend quick-option fetch/apply flow from ride-entry page

This removal applies to all riders without a feature-flag fallback.

## Security and Isolation

- All preset endpoints require authentication.
- Presets are rider-scoped only.
- Cross-rider access attempts return `404` or `403` depending on endpoint policy.

## Frontend Type Notes

Planned TypeScript additions in ride service layer:
- `RidePreset` (now includes required `miles: number`)
- `RidePresetsResponse`
- `UpsertRidePresetRequest` (now includes required `miles: number`)

Planned request extension:
- `RecordRideRequest.selectedPresetId?: number`
127 changes: 127 additions & 0 deletions specs/020-improve-ride-preset-options/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Data Model: Improve Ride Preset Options

**Feature**: 020-improve-ride-preset-options
**Date**: 2026-04-29
**Status**: Phase 1 design complete

---

## Overview

This feature introduces explicit rider-managed presets and removes history-derived quick-entry behavior.

Primary model changes:
1. Add persisted rider-owned `RidePreset` entity.
2. Add optional preset usage linkage on ride save request (`selectedPresetId`) to maintain MRU ordering.
3. Remove legacy quick-option projection/use from ride-entry UI and endpoint surface.

---

## New Entity: RidePreset

Planned file area: `src/BikeTracking.Api/Infrastructure/Persistence/Entities/`

| Field | Type | Required | Validation | Notes |
|-------|------|----------|------------|-------|
| `RidePresetId` | `long` | Yes | PK | Identity key |
| `RiderId` | `long` | Yes | `> 0` | Owner scope; FK-like relationship to rider |
| `Name` | `string` | Yes | 1..80 chars, unique per rider | Display label in settings + ride entry |
| `PrimaryDirection` | `string` | Yes | compass enum used by existing rides contracts | Default direction to apply |
| `PeriodTag` | `string` | Yes | `Morning` or `Afternoon` | Drives default direction suggestion only |
| `ExactStartTimeLocal` | `TimeOnly` | Yes | valid local time | Exact time persisted in preset |
| `DurationMinutes` | `int` | Yes | `> 0` and within existing ride-minute constraints | Default duration |
| `Miles` | `decimal` | Yes | `> 0`, max 999.99 | Default miles to apply; required |
| `LastUsedAtUtc` | `DateTime?` | No | UTC | MRU sort key; updated only on successful ride save using preset |
| `CreatedAtUtc` | `DateTime` | Yes | UTC | Audit |
| `UpdatedAtUtc` | `DateTime` | Yes | UTC | Audit |
| `Version` | `int` | Yes | optimistic concurrency | For update/delete conflict handling |

### Constraints

- Unique index: `(RiderId, Name)`.
- Index for ride-entry ordering: `(RiderId, LastUsedAtUtc DESC, UpdatedAtUtc DESC)`.
- `PeriodTag` limited to known values (`Morning`, `Afternoon`).

---

## API Contract Models

### RidePresetDto

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `presetId` | number | Yes | server identifier |
| `name` | string | Yes | unique per rider |
| `primaryDirection` | CompassDirection | Yes | reusable from existing ride direction type |
| `periodTag` | `morning` \| `afternoon` | Yes | lowercase in JSON |
| `exactStartTimeLocal` | string (`HH:mm`) | Yes | exact preset time |
| `durationMinutes` | number | Yes | integer minutes |
| `miles` | number | Yes | positive decimal, required |
| `lastUsedAtUtc` | string \| null | No | MRU sort metadata |
| `updatedAtUtc` | string | Yes | secondary sort/tie-breaker metadata |

### UpsertRidePresetRequest

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `name` | string | Yes | 1..80, unique per rider |
| `primaryDirection` | CompassDirection | Yes | must be valid enum |
| `periodTag` | `morning` \| `afternoon` | Yes | required |
| `exactStartTimeLocal` | string (`HH:mm`) | Yes | parseable exact time |
| `durationMinutes` | number | Yes | positive integer |
| `miles` | number | Yes | positive decimal, required |

### RecordRideRequest (extension)

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `selectedPresetId` | number | No | if present and rider-owned, backend updates `LastUsedAtUtc` after successful ride save |

---

## Relationships

- One rider has many `RidePreset` records.
- One ride save may reference zero or one preset via `selectedPresetId`.
- Presets are never shared across riders (strict rider scope).

---

## State Transitions

### Preset CRUD

1. Rider opens settings and fetches presets.
2. Rider creates or updates preset.
3. Backend validates unique name per rider and stores exact time/duration/direction.
4. Rider may delete preset; deletion removes it from future ride-entry choices only.

### Preset Use in Ride Entry (MRU)

1. Ride entry loads presets ordered by `LastUsedAtUtc DESC`, then `UpdatedAtUtc DESC`.
2. Rider selects preset; client populates direction, exact start time (time component), duration.
3. Rider can edit values manually before submit.
4. On successful `POST /api/rides` with `selectedPresetId`, backend updates that preset `LastUsedAtUtc = now`.

---

## Validation Rules

- Preset name must be unique per rider.
- `durationMinutes` must remain within existing ride validation limits.
- `exactStartTimeLocal` is mandatory and must be exact (`HH:mm`) not free-text.
- `miles` is required, must be a positive decimal (e.g., >0, max 999.99).
- Default direction suggestions:
- morning -> SW
- afternoon -> NE
- Suggestions are non-binding; persisted direction is always rider-selected value.

---

## Legacy Quick-Entry Decommission

The following behavior is removed for all riders:
- Backend `GetQuickRideOptionsService` endpoint usage path.
- Frontend "Quick Ride Options" section and associated fetch/action flow.

Historical rides remain unchanged and continue to support analytics/reporting.
Loading
Loading