From b8d85888a447daab6acbcd20d986019c5f3c3f2c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 5 May 2026 16:46:17 +0000 Subject: [PATCH 1/2] Add Feel slider to Rest Day section and include F: in rest markdown The spec requires the Feel (W/P/N/G/S) slider to appear in the Rest Day section for rest entries, and for the selected feel value to be output as `F: ` in the rest markdown alongside recovery metrics and weight. Updates utils.test.ts to match: adds two new feel tests for rest output and removes the stale `expect(md).not.toContain('F:')` assertion that treated feel as cycling-only. https://claude.ai/code/session_01FXEJfqBTt5cUkoQkFmrzEo --- CLAUDE.md | 6 +++--- client/src/pages/home.tsx | 34 ++++++++++++++++++++++++++++++++++ client/src/test/utils.test.ts | 13 +++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4152c1b..119af71 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -99,7 +99,7 @@ Three entry types supported via `entryType` field (default: `cycling`): - **`rest`** — rest day logging (Recovery Metrics + Rest Day sections only); auto-expands Recovery Metrics on switch - **`other`** — non-cycling activities like MFR/yoga/strength (Activity section only) -`goal`, `rpe`, `feel` are only required when `entryType === "cycling"` (enforced by `superRefine`). Switching entry types does not reset other fields — hidden fields retain their values but don't appear in markdown output. +`goal`, `rpe`, `feel` are only required when `entryType === "cycling"` (enforced by `superRefine`). `feel` is also optional for rest entries and appears in rest markdown as `F:`. Switching entry types does not reset other fields — hidden fields retain their values but don't appear in markdown output. Old drafts (pre-entryType) are migrated to `cycling` on restore in `use-form-persistence.ts`. @@ -119,7 +119,7 @@ Old drafts (pre-entryType) are migrated to `cycling` on restore in `use-form-per Always required: `entryType` (`cycling`/`rest`/`other`), `workoutDate` Cycling-required (via `superRefine`): `goal`, `rpe` (1-10), `feel` (W/P/N/G/S) Cycling-optional: `choIntakePre`, `choIntake`, `choIntakePost`, `normalizedPower`, `tss`, `avgHeartRate`, `hrv`, `rMSSD`, `rhr`, `trainerRoadRpe`, `trainerRoadLgt`, `whatWentWell`, `whatCouldBeImproved`, `description` -Rest-only: `weight` (positive number, kg), `restNotes` (free-form, rendered as bullets). Rest entries also reuse `hrv`/`rMSSD`/`rhr`/`trainerRoadLgt`. +Rest-only: `weight` (positive number, kg), `restNotes` (free-form, rendered as bullets). Rest entries also reuse `hrv`/`rMSSD`/`rhr`/`trainerRoadLgt`/`feel`. Other-only: `activityGoal` (e.g. "MFR", "Yoga"), `activityNotes` (free-form, rendered as bullets). ### Markdown Abbreviations @@ -127,7 +127,7 @@ G=Goal (cycling) or Activity (other), R=RPE, F=Feel, Ci-Pre=Carbohydrate Intake ### Markdown Output Per Entry Type - **cycling**: `G` / `R` / `F` + optional metrics + WWW/WCBI/Planned blocks (current format, unchanged) -- **rest**: `Rest Day` marker + present-only recovery metrics and `Weight` + bulleted `restNotes` +- **rest**: `Rest Day` marker + present-only recovery metrics, `Weight`, `F` (feel if set) + bulleted `restNotes` - **other**: optional `G: ` + bulleted `activityNotes`; no metrics ## Environment Variables (Deployment Only) diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx index 949ca90..a887d0e 100644 --- a/client/src/pages/home.tsx +++ b/client/src/pages/home.tsx @@ -241,6 +241,7 @@ function generateRestMarkdown(data: InsertWorkout): string { markdown += `TR-LGT: ${data.trainerRoadLgt}\n`; } if (data.weight) markdown += `Weight: ${data.weight}\n`; + if (data.feel) markdown += `F: ${data.feel}\n`; if (data.restNotes) { markdown += '\n' + formatBulletPoints(data.restNotes) + '\n'; @@ -1297,6 +1298,39 @@ export default function Home() { )} /> + ( + + + + F (Feel): {feelOptions.find(opt => opt.value === field.value)?.label || 'Normal (N)'} + + +
+ opt.value === field.value) + 1]} + onValueChange={(value) => field.onChange(feelOptions[value[0] - 1]?.value || 'N')} + max={5} + min={1} + step={1} + className="w-full" + /> +
+ Weak + Poor + Normal + Good + Strong +
+
+
+ +
+ )} + /> + { ...baseRest, goal: 'leaked goal', rpe: 7, - feel: 'S', normalizedPower: 200, tss: 50, choIntake: 'gel', @@ -258,7 +258,6 @@ describe('generateMarkdown — rest output', () => { }); expect(md).not.toContain('G:'); expect(md).not.toContain('R:'); - expect(md).not.toContain('F:'); expect(md).not.toContain('NP:'); expect(md).not.toContain('TSS:'); expect(md).not.toContain('Ci:'); @@ -266,6 +265,16 @@ describe('generateMarkdown — rest output', () => { expect(md).not.toContain('WCBI'); }); + it('includes feel when provided', () => { + const md = generateMarkdown({ ...baseRest, feel: 'N' }); + expect(md).toContain('F: N'); + }); + + it('omits feel when not set', () => { + const md = generateMarkdown(baseRest); + expect(md).not.toContain('F:'); + }); + it('omits empty optional fields', () => { const md = generateMarkdown(baseRest); expect(md).not.toContain('HRV:'); From 1ec3e2878f065c049e03c482eb5eff3445bcdd70 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 5 May 2026 16:46:39 +0000 Subject: [PATCH 2/2] Remove stale lockfile entries from npm install https://claude.ai/code/session_01FXEJfqBTt5cUkoQkFmrzEo --- package-lock.json | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 86aad54..b81cb91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -152,7 +152,6 @@ "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", @@ -1778,7 +1777,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1802,7 +1800,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -3210,7 +3207,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -3376,7 +3372,6 @@ "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.19.2" } @@ -3387,7 +3382,6 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3398,7 +3392,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3615,7 +3608,6 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3961,7 +3953,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -5870,7 +5861,6 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -6702,7 +6692,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", @@ -6930,7 +6919,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6940,7 +6928,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -6952,7 +6939,6 @@ "version": "7.55.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.55.0.tgz", "integrity": "sha512-XRnjsH3GVMQz1moZTW53MxfoWN7aDpUg/GpVNc4A3eXRVNdGXfbzJ4vM4aLQ8g6XCUh1nIbx70aaNCl7kxnjog==", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -7238,7 +7224,6 @@ "integrity": "sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.6" }, @@ -8015,7 +8000,6 @@ "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -8211,7 +8195,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8621,7 +8604,6 @@ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -9166,7 +9148,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -9727,7 +9708,6 @@ "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" },