22 Commits

Author SHA1 Message Date
clawd fac53a3605 chore: add dist and build artifacts to .gitignore
- Exclude frontend/dist/ (build output)
- Exclude .py files (script templates)
- Exclude PY temp files
2026-03-02 09:25:10 +01:00
clawd 994f406050 fix: make backend listen on 0.0.0.0 instead of localhost
This allows Traefik and other containers on the docker network to reach the backend API.
2026-03-02 09:25:10 +01:00
clawd f941011130 chore: remove stray EOF and PLANEOF files 2026-03-02 09:25:10 +01:00
clawd fa95e880b2 docs: add CLAUDE.md — agent development guidelines
- Core principles for autonomous agents with verification
- Checkpoint-based self-monitoring patterns
- Generalized agent workflow (no project-specific agents)
- Single source of truth in ~/clawd/claude-agents-skills/
- PM autonomy and cron job configuration
- Verification protocol to prevent hallucinations
- Together with CODING-CONVENTIONS.md, foundation for agent development
2026-03-02 09:25:10 +01:00
clawd f63f4c0420 04-06-02: Save error handling & retry logic
- Added specific error type differentiation:
  * Network errors → 'Anslutning misslyckades'
  * Validation (400) → 'Ogiltiga ändringar'
  * Auth (401/403) → 'Saknar behörighet'
  * Server (500+) → 'Serverfel'
  * Generic fallback messages

- Implemented retry tracking:
  * retryCount state for monitoring attempts
  * lastSavePayload storage for potential retry (future feature)
  * Console logging with context for debugging

- Enhanced error handling:
  * getErrorMessage() function for error classification
  * Comprehensive error logging with workout/exercise context
  * Draft preserved on all error types (no data loss)

- Improved UI/UX:
  * Error banner with specific, actionable messages
  * 'Försök igen' button with retry tracking
  * Sync status feedback (idle/saving/saved/error)
  * Success checkmark animation (2s duration)
  * Spinner animation during save

- CSS Enhancements:
  * @keyframes spin for loading spinner
  * @keyframes slideInCheckmark for success feedback
  * Mobile-responsive error banner (flex column on <480px)
  * Smooth animations for state transitions

Tests: npm run build ✓ (no syntax errors)
Files modified:
  - frontend/src/pages/WorkoutEditPage.jsx
  - frontend/src/pages/WorkoutEditPage.css
2026-03-02 09:25:10 +01:00
clawd 475cf10b17 04-06: Plan persistence improvements and implement draft persistence
- Created 04-06-PLAN.md outlining persistence improvements phases
- Phase 04-06-01: Draft persistence via localStorage
  - Added useDraftWorkout hook for auto-saving/loading drafts
  - Integrated hook into WorkoutEditPage
  - Added draft recovery prompt UI
  - Drafts cleared after successful save
- Phase 04-06-02: Save error handling & retry (scaffolding)
  - Added error state and syncStatus tracking
  - Added handleRetry() for failed saves
  - Error banner with retry button
- Phase 04-06-03: Sync status UI (scaffolding)
  - Added visual feedback for save progress
  - Status indicators: saving, saved, error
  - Disabled UI during save to prevent conflicts
- Created comprehensive styles for new UI components

Status: 04-06-01 complete and integrated. Ready for testing.
2026-03-02 09:25:10 +01:00
clawd cf85e9e314 04-05: Reset to Original feature - custom workouts can be reverted to program versions
- Added reset button (refresh icon) to custom workout cards
- Implemented confirmation dialog to prevent accidental resets
- Integrated with DELETE /api/custom-workouts/:id endpoint
- Added CSS styling: reset button, success message, modal dialog
- Added refresh icon to SVG library
- Frontend build successful

Changes:
- frontend/src/pages/WorkoutSelectPage.jsx (reset flow logic)
- frontend/src/App.css (170 new lines for reset/modal styling)
- frontend/src/components/Icons.jsx (refresh icon)
- Checkpoint updated with task completion metadata
2026-03-02 09:25:10 +01:00
clawd b5c9250a10 feat(04-04-visual-distinction): Add custom vs program workout badges on WorkoutSelectPage
- Fetch custom workouts for authenticated user
- Display 'Anpassad' (custom) or 'Program' badge on each workout card
- Add badge component with orange accent for custom, muted color for program
- Badge positioned bottom-right of workout icon
- Responsive styling consistent with Gravl dark theme
- All build checks pass
2026-03-02 09:25:10 +01:00
clawd a24199e56c feat(04-03-partial): ExercisePicker and WorkoutEditPage components - swap/add/remove exercises with sets/reps editing 2026-03-02 09:25:10 +01:00
clawd 5fd21719d0 test(e2e): add Playwright with browser tests for login, logo, dashboard 2026-03-02 09:25:10 +01:00
clawd 4bd2c9607d feat(phase-4): Backend API for custom workouts
- Add custom_workouts and custom_workout_exercises tables (schema)
- New endpoints:
  - GET /api/exercises - List all exercises for picker
  - POST /api/custom-workouts - Fork program workout
  - GET /api/custom-workouts - List user's custom workouts
  - GET /api/custom-workouts/:id - Get workout with exercises
  - PUT /api/custom-workouts/:id - Update workout exercises
  - DELETE /api/custom-workouts/:id - Delete custom workout
- Updated endpoints for source_type support:
  - GET /api/logs - Filter by source_type and custom_workout_id
  - POST /api/logs - Save with source_type and custom_workout_id
  - DELETE /api/logs - Support custom workout log deletion
- Adds Phase 4 planning overview

Completes: 04-01-schema-migration, 04-02-backend-api
Next: 04-03-frontend-workout-edit
2026-03-02 09:25:10 +01:00
clawd 22750bfa06 fix(staging): fix Traefik service linking with explicit service labels 2026-03-02 09:25:10 +01:00
clawd 4b39f39e3e feat(staging): add Traefik-based staging with automatic subdomains 2026-03-02 09:25:10 +01:00
clawd 7694ca6313 feat(infra): add staging environment setup with docker-compose and scripts 2026-03-02 09:25:10 +01:00
sphinxen 15d7aff096 Merge pull request 'feature/03-design-polish' (#1) from feature/03-design-polish into main
Reviewed-on: https://gitea.homelab.local/clawd/gravl/pulls/1
2026-03-02 09:08:10 +01:00
clawd 362f4eed49 checkpoint: mark phase 3 complete (03-01, 03-02, 03-03) 2026-03-01 00:03:48 +01:00
clawd 6d1da03fec 03-03: Workout Experience Polish - enhanced exercise cards, progress badges, rest timer, KLART button, warmup styling 2026-02-28 23:47:36 +01:00
clawd 5d0e0e3952 feat(dashboard): polish header logo, stat cards, calendar and animations
- Replace gravl icon text with Logo component in dashboard header
- Stat cards: gradient depth + per-card colour accent (orange/green/amber)
- Calendar today: pulsing glow animation; workout days get subtle brand tint
- Arrow nudge animation on today-workout-card hover
- Section stagger fade-in on page load (calendar → coach → stats)
- Larger stat-value font (3xl) with tighter letter-spacing
- Consistent gap spacing in dashboard-main (space-6)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 23:22:34 +01:00
clawd be4a149a47 feat(auth): polish login/register with logo, gradients and animations 2026-02-28 22:59:08 +01:00
clawd 0cd6cd0269 checkpoint: mark 03-01-login-onboarding-polish as completed 2026-02-28 22:58:24 +01:00
clawd e40b486ae5 feat(onboarding): add conversational ChatOnboarding component 2026-02-28 22:06:15 +01:00
clawd 04bab32e26 design: WorkoutPage Hevy-style redesign + AlternativeModal + backend API
- Add GET /api/exercises/:id/alternatives endpoint
- Add GET /api/exercises/:id/last-workout endpoint
- New AlternativeModal component for swapping exercises
- WorkoutPage: single-tap logging, +/- buttons, rest timer
- Updated Icons with new workout icons
- Polish: card shadows, borders, micro-interactions
- Tasks directory for project management
2026-02-28 21:25:23 +01:00
13 changed files with 294 additions and 647 deletions
+9
View File
@@ -52,3 +52,12 @@ TODO.md
./frontend/tasks/ ./frontend/tasks/
./docs/plans/ ./docs/plans/
.claude/settings.local.json .claude/settings.local.json
# Build output & dist
dist/
build/
frontend/dist/
# Build artifacts & temp files
*.py
PY
+16 -16
View File
@@ -1,20 +1,20 @@
{ {
"lastRun": "2026-03-02T03:55:00Z", "lastRun": "2026-03-01T20:42:00+01:00",
"status": "blocked", "status": "completed",
"phase": "04-workout-modification", "phase": "04-workout-modification",
"milestone": "PHASE_04_COMPLETE", "activeTask": "04-05-reset-to-original",
"completedTasks": [ "tasksCompleted": ["01-input-ux", "02-flexible-sets", "03-design-polish", "04-01-schema-migration", "04-02-backend-api", "04-03-frontend-workout-edit", "04-04-visual-distinction", "04-05-reset-to-original"],
"04-01-database-schema", "nextTask": "04-06-persistence-improvements",
"04-02-backend-api", "agentSession": "local-exec",
"04-03-frontend-edit-mode", "agentType": "gravl-pm-cron",
"04-04-visual-distinction", "spawnTime": "2026-03-01T20:42:00+01:00",
"04-05-reset-option", "result": "Phase 04-05 complete. Reset to Original feature fully implemented. Changes: 1) Added refresh button to custom workout cards (visible only on 'Anpassad' workouts). 2) Implemented handleResetClick + handleConfirmReset flow with confirmation dialog ('Är du säker? Dina ändringar kommer att försvinna...'). 3) DELETE /api/custom-workouts/:id endpoint verified (exists in backend). 4) Added CSS styling: .reset-btn (orange icon button with hover effects), .success-message (green slide-down animation), .modal-overlay/.modal-dialog/.modal-btn (reusable confirmation dialog). 5) Added refresh icon to Icons.jsx SVG library. Frontend build successful with no errors.",
"04-06-01-draft-persistence", "notes": "Task 04-05 complete. Custom workouts can now be reset to original program versions. User gets confirmation dialog before deletion. UI updates show badge change from 'Anpassad' to 'Program' after reset. Next: 04-06 (persistence improvements or advanced features like workout export/backup).",
"04-06-02-error-recovery", "filesModified": [
"04-06-03-sync-status-ui" "frontend/src/pages/WorkoutSelectPage.jsx",
"frontend/src/App.css",
"frontend/src/components/Icons.jsx"
], ],
"result": "Phase 04 (Workout Modification) complete. Users can now fork and customize program workouts with persistent, error-resistant, real-time sync feedback. Next phase awaits definition.", "buildStatus": "success",
"blockReason": "No 04-06-04 spec or 05-* phase defined. Awaiting human direction for next feature.", "buildTime": "3.59s"
"recommendation": "Options: (1) Define and execute 04-06-04 performance optimization, (2) Start phase 05 (new feature), (3) User reviews completeness and prioritizes next work",
"nextAction": "Await phase definition in workspace planning docs or manual prompt"
} }
+85 -413
View File
@@ -1,12 +1,11 @@
# CLAUDE.md — Agent Development Foundation # CLAUDE.md — Agent Development Guidelines
This document is **THE** foundation for developing Claude agents and autonomous systems in Gravl. This is the foundation for developing Claude agents and autonomous systems in the Gravl ecosystem.
Together with the actual codebase, this is your north star.
## Part 1: Agent Development Principles ## Core Principles
### 1. Autonomy with Verification ### 1. Autonomy with Verification
- Agents execute tasks independently - Agents execute tasks independently (autonomy)
- **Always verify results** after delegation (no hallucinations) - **Always verify results** after delegation (no hallucinations)
- Verification pattern: `git status`, `git log`, `ls`, diff before checkpoint update - Verification pattern: `git status`, `git log`, `ls`, diff before checkpoint update
- Never report completion without checking actual work - Never report completion without checking actual work
@@ -32,215 +31,45 @@ All long-running tasks use checkpoint files:
The Gravl PM agent: The Gravl PM agent:
- Plans sprints/phases autonomously - Plans sprints/phases autonomously
- Spawns specialized agents (frontend-dev, backend-dev, etc.) - Spawns specialized agents (frontend-dev, backend-dev, etc.)
- Verifies their work **before** checkpoint completion - Verifies their work before checkpoint completion
- Reports progress to Telegram (not silent failures) - Reports progress to Telegram (not silent failures)
- Timeout: 15 minutes (900s) per cron cycle - Timeout: 15 minutes (900s) per cron cycle
### 4. Generalized Agents (Reusable) ### 4. Generalized Agents (Reusable)
**NEVER create project-specific agents.** **Never create project-specific agents.**
Use generalized agents instead: Use generalized agents instead:
- `frontend-dev` — React/CSS specialist - `frontend-dev` — React/CSS specialist
- `backend-dev` — Node.js/PostgreSQL specialist - `backend-dev` — Node.js/PostgreSQL specialist
- `architect` — System design - `architect` — System design
- `reviewer` — Code review + quality gates - `reviewer` — Code review
- `browser-tester` — E2E testing + QA - `browser-tester` — E2E testing + QA
These live in `~/clawd/claude-agents-skills/agents/` and symlink to `~/clawd/agents/`. These are in `~/clawd/claude-agents-skills/agents/` and symlinked to `~/clawd/agents/`.
### 5. Single Source of Truth ### 5. Single Source of Truth
All skills and agents in ONE central repo: All skills and agents live in ONE central repo:
- **Hub location:** `~/clawd/claude-agents-skills/` - **Hub location:** `~/clawd/claude-agents-skills/`
- **Symlinks from:** `~/clawd/skills/` and `~/clawd/agents/` - **Symlinks from:** `~/clawd/skills/` and `~/clawd/agents/`
- Commit all changes to hub repo - **Commit everything to hub repo**
- Enables sharing, versioning, and collaboration - This enables sharing, versioning, and collaboration
### 6. Communication Pattern ## Development Workflow
- PM drives autonomously
- Silence = approval (no blocking)
- Report **only** at milestones or blocking issues
- Use Telegram for delivery (explicit `"channel: telegram"`)
---
## Part 2: Coding Conventions (MANDATORY)
### Red/Green/Refactor TDD (OBLIGATORY)
All new code follows the TDD cycle:
```
🔴 RED → 🟢 GREEN → 🔄 REFACTOR
```
#### Step 1: 🔴 RED - Write Failing Test First
```javascript
// test/feature.test.js
describe('Feature', () => {
it('should do expected behavior', async () => {
const result = await feature.doSomething();
expect(result).toBe(expected);
});
});
```
**Run the test - it MUST fail!**
```bash
npm test -- --grep "Feature"
# ❌ FAIL (this is correct!)
```
#### Step 2: 🟢 GREEN - Minimal Implementation
Write just enough code to pass the test:
```javascript
// src/feature.js
export function doSomething() {
return expected; // Minimal solution
}
```
**Run the test again:**
```bash
npm test -- --grep "Feature"
# ✅ PASS
```
#### Step 3: 🔄 REFACTOR - Improve
Now you can:
- Refactor for clean code
- Extract functions
- Improve naming
- Remove duplication
**Run tests continuously:**
```bash
npm test
# ✅ All tests must still pass
```
### Test Structure
```
/workspace/gravl/
├── src/
│ └── components/
├── server/
│ └── routes/
└── test/
├── unit/ # Unit tests
├── integration/ # API tests
└── e2e/ # End-to-end (Playwright)
```
### Naming Conventions
#### Test Files
- `[feature].test.js` — Unit tests
- `[feature].integration.test.js` — Integration tests
- Describe block: Noun (what is tested)
- It block: "should [verb] [expected outcome]"
#### Commits
```
test: add failing test for [feature]
feat: implement [feature] to pass tests
refactor: clean up [feature] implementation
```
### Agent Workflow (Step-by-Step)
When spawned with a coding task:
1. **Read the spec** → Check docs/current-task.md
2. **Write failing test** → Show to PM that you understand the requirement
3. **Implement code** → Make the test pass (minimal solution)
4. **Refactor** → Clean code if needed
5. **Run full test suite** → Ensure nothing broke
6. **Commit with proper prefix**`test:`, `feat:`, `refactor:`
7. **Report to PM** → Include git log, test results
8. **Verification** → PM checks `git status`, `git log`, diffs
---
## Part 3: Operations
### Cron Jobs (3 Active)
| Job | Schedule | Timeout | Checkpoint | Status |
|-----|----------|---------|-----------|--------|
| Gravl PM | Every 30m | 15 min | `/workspace/gravl/.pm-checkpoint.json` | Active |
| Vietnam Flights | Daily 09:00 | 2 min | `~/.checkpoint-vietnam-flights.json` | Active |
| System Updates | Daily 10:00 | 5 min | `~/.checkpoint-system-updates.json` | Active |
All use explicit `"channel: telegram"` for Telegram delivery.
### Repository Structure
```
/workspace/gravl/
├── frontend/ # React app
├── backend/ # Express API
├── db/ # Database setup + migrations
├── scripts/ # Automation scripts
├── docker/ # Compose files
├── test/ # Test suites
├── docs/
│ └── CODING-CONVENTIONS.md # (Deprecated, see CLAUDE.md)
├── README.md # Project overview
├── CLAUDE.md # THIS FILE — Agent & coding foundation
└── .gitignore # Excludes node_modules, planning docs
```
### Local-Only Files (Not in Git)
These stay on disk but excluded via `.gitignore`:
- `.planning/` — research, requirements, roadmap
- `TODO.md` — task tracking
- `frontend/tasks/` — feature tasks
- `docs/plans/` — planning notes
This keeps the repo clean while preserving planning work locally.
---
## Part 4: Agent Development Workflow
### Adding a New Agent ### Adding a New Agent
1. Create in hub: `~/clawd/claude-agents-skills/agents/my-agent/` 1. Create in hub: `~/clawd/claude-agents-skills/agents/my-agent/`
2. Write `SOUL.md` (agent definition, personality, expertise) 2. Write `SOUL.md` (agent definition + personality)
3. Optional: Add `README.md`, scripts, config files 3. Optional: Add `README.md`, scripts, config
4. Symlink automatically created: `~/clawd/agents/my-agent` 4. Symlink automatically created: `~/clawd/agents/my-agent → hub/agents/my-agent`
5. Commit to hub repo 5. Commit to hub repo
Example SOUL.md:
```markdown
# My Agent SOUL
## Core Identity
- Name: [Agent Name]
- Expertise: [Domain]
- Personality: [Vibe]
## Instructions
1. [Guideline 1]
2. [Guideline 2]
## Communication
- Report at milestones
- Verify before completion
```
### Adding a New Skill ### Adding a New Skill
1. Create in hub: `~/clawd/claude-agents-skills/skills/my-skill/` 1. Create in hub: `~/clawd/claude-agents-skills/skills/my-skill/`
2. Write `SKILL.md` (documentation, usage, examples) 2. Write `SKILL.md` (how to use it)
3. Add code/scripts 3. Add code/scripts as needed
4. Symlink automatically created: `~/clawd/skills/my-skill` 4. Symlink automatically created: `~/clawd/skills/my-skill → hub/skills/my-skill`
5. Commit to hub repo 5. Commit to hub repo
### Verification Pattern (CRITICAL) ### Verification Pattern (CRITICAL)
@@ -257,243 +86,86 @@ git log --oneline -3
# 3. Inspect actual changes # 3. Inspect actual changes
git diff HEAD~1 git diff HEAD~1
# 4. ONLY THEN update checkpoint # 4. THEN update checkpoint
echo '{ echo '{"status":"completed",...}' > checkpoint.json
"lastRun": "'$(date -Iseconds)'",
"status": "completed",
"result": "Summary..."
}' > checkpoint.json
``` ```
**This prevents hallucination bugs** where agents claim work they didn't do. **This prevents hallucination bugs** where agents claim work they didn't do.
--- ## Communication
### Report-Only Pattern
- PM drives autonomously
- Silence = approval (no blocking)
- Only report at milestones or blocking issues
- Use Telegram for delivery (channel: telegram)
### Cron Jobs (3 active)
| Job | Schedule | Timeout | Checkpoint |
|-----|----------|---------|-----------|
| Gravl PM | Every 30m | 15 min | `/workspace/gravl/.pm-checkpoint.json` |
| Vietnam Flights | Daily 09:00 | 2 min | `~/.checkpoint-vietnam-flights.json` |
| System Updates | Daily 10:00 | 5 min | `~/.checkpoint-system-updates.json` |
All use explicit `"channel: telegram"` for Telegram delivery.
## Code Conventions
See `CODING-CONVENTIONS.md` for:
- Frontend (React, CSS)
- Backend (Express, PostgreSQL)
- Database (schema, migrations)
- Testing (Playwright, E2E)
## Repository Structure
```
/workspace/gravl/
├── frontend/ # React app
├── backend/ # Node.js API
├── db/ # Database setup
├── scripts/ # Automation
├── docker/ # Compose files
├── docs/
│ └── CODING-CONVENTIONS.md # Technical standards
├── README.md # Project overview
├── CLAUDE.md # This file (agent guidelines)
└── .gitignore # Excludes planning docs, node_modules
```
## Local-Only Files (Not in Git)
These stay on disk but are excluded from `.git` via `.gitignore`:
- `.planning/` — research, requirements, roadmap
- `TODO.md` — task tracking
- `frontend/tasks/` — feature tasks
- `docs/plans/` — planning notes
This keeps the repo clean while preserving your planning work locally.
## Key Decisions ## Key Decisions
1. **Generalized agents**Reusable, maintainable, shareable 1. **Generalized agents over project-specific**More reusable, easier to maintain
2. **Single hub repo** — Centralized versioning 2. **Single hub repo** — Centralized versioning + easy sharing
3. **Symlinks for discovery** — OpenClaw finds everything automatically 3. **Symlinks for discovery** — OpenClaw finds skills/agents automatically
4. **TDD mandatory**Red → Green → Refactor 4. **Verification protocol**Prevents hallucination bugs
5. **Verification protocol** — No hallucinations allowed 5. **Checkpoint-based recovery** — Self-healing cron jobs
6. **Checkpoint-based recovery** — Self-healing cron jobs 6. **Telegram for delivery** — Explicit channel to avoid missed messages
7. **Telegram delivery** — Explicit channel routing
--- ## For the PM Agent
## PM Agent Playbook (30-minute cycles) The Gravl PM uses this playbook:
1. **Plan** → Identify phase tasks, delegate to agents 1. **Plan phase** → Identify tasks, delegate to specialized agents
2. **Execute** → Spawn agents with tasks, monitor progress 2. **Execute phase** → Spawn agents, monitor progress
3. **Verify** → Check `git status`, diffs, test results (NO ASSUMPTIONS) 3. **Verify phase** → Check git status, diffs, logs (NO HALLUCINATIONS)
4. **Report** → Send Telegram update (success or blocking issue) 4. **Report phase** → Send Telegram update with result or blocking issue
5. **Checkpoint** → Update `.pm-checkpoint.json` with status + nextCheck 5. **Checkpoint phase** → Update checkpoint.json with status + nextCheck
PM runs autonomously every 30 minutes. **No human approval needed unless blocked.** PM runs every 30 minutes autonomously. No human approval needed unless blocked.
---
## References
- **Agent Symlink Hub:** `~/clawd/claude-agents-skills/`
- **Frontend Stack:** React + Vite + Tailwind
- **Backend Stack:** Express + PostgreSQL
- **Testing:** Jest (unit), Playwright (E2E)
- **Database:** PostgreSQL with migrations
- **Deployment:** Docker Compose (local), staging via Traefik
--- ---
**Last Updated:** 2026-03-02 **Last Updated:** 2026-03-02
**Version:** 1.0 **Version:** 1.0
**Audience:** All Claude agents, PM, developers **For questions:** Check specific agent SOUL.md or skill SKILL.md files
> **Remember:** This document is your north star. Follow it. Extend it. Improve it.
---
## Part 5: Git Hygiene - Keep the Repo Clean
### No "Fix" Commits
**NEVER** commit fixes for typos, syntax errors, or accidentally added files:
```bash
# BAD - Creates noise in history:
commit 1a2b3c: feat: add feature
commit 1a2b3d: fix: typo in feature ← NO!
commit 1a2b3e: chore: remove extra file ← NO!
```
**INSTEAD:** Use interactive rebase to clean up BEFORE pushing:
```bash
# GOOD - Clean, single commit:
commit 1a2b3c: feat: add feature (with typo fixed)
```
### Interactive Rebase Workflow
**1. Check recent commits:**
```bash
git log --oneline -5
# abc1234 feat: add feature
# def5678 fix: typo in feature ← Oops!
# ghi9012 chore: remove extra file ← Oops!
```
**2. Rebase the last 3 commits:**
```bash
git rebase -i HEAD~3
```
**3. Edit in your editor:**
```
pick abc1234 feat: add feature
squash def5678 fix: typo in feature
squash ghi9012 chore: remove extra file
```
Change `pick` to `squash` (or `s`) for commits you want to merge into the previous one.
**4. Save and edit the commit message:**
The editor will ask for a final commit message. Edit it to be clean:
```
feat: add feature
- Added feature X
- Removed extra file
```
**5. Force-push (only if not yet pushed):**
```bash
git push -f origin branch-name
```
### When to Rebase
-**Before first push** — Fix typos, add forgotten files
-**Before review** — Squash "work in progress" commits
-**After pushed to shared branch** — Don't force-push to main/develop
### Common Rebase Scenarios
**Fix a typo in last commit:**
```bash
git commit --amend
# Edit the file, save
# Git updates the commit without a new one
```
**Reorder commits:**
```bash
git rebase -i HEAD~3
# Reorder the lines in the editor
```
**Combine last 2 commits:**
```bash
git rebase -i HEAD~2
# Change second line from "pick" to "squash"
```
### Rule of Thumb
**One logical change = One commit**
If you're writing a commit message that says "fixed typo from previous commit", you should have rebased instead.
---
## Part 6: Branching Strategy - One Feature Per Branch
### The Workflow
**Each phase/feature gets its own branch:**
```
main
└─ feature/03-design-polish (Phase 03)
└─ feature/04-workout-modification (Phase 04, branched from 03)
└─ feature/05-exercise-encyclopedia (Phase 05, branched from 04)
```
### Step-by-Step
#### 1. Work on Phase 03
```bash
git checkout -b feature/03-design-polish
# ... do all Phase 03 work ...
# Multiple commits, testing, refinement
git push origin feature/03-design-polish
```
#### 2. When Phase 03 is Done → Create PR
```bash
# Create PR: feature/03-design-polish → main
# Get code review, merge when approved
# This becomes the baseline for Phase 04
```
#### 3. Start Phase 04 (NEW BRANCH from Phase 03)
```bash
# First, make sure local is up-to-date
git checkout feature/03-design-polish
git pull origin feature/03-design-polish
# Create new branch FROM the current feature
git checkout -b feature/04-workout-modification
# Now do all Phase 04 work on this new branch
# ... commits for 04-01, 04-02, 04-03, etc ...
git push origin feature/04-workout-modification
```
#### 4. When Phase 04 is Done → Rebase Before PR
```bash
# Make sure feature/03 is merged to main
git fetch origin
# Rebase Phase 04 onto main (to get clean history)
git rebase origin/main feature/04-workout-modification
# Force-push (since we're rebasing)
git push -f origin feature/04-workout-modification
# Create PR: feature/04-workout-modification → main
```
### Why This Matters
-**Cleaner history** — Each phase is separate
-**Easier reviews** — PRs are focused on one phase
-**Better testing** — Each phase tested independently
-**Easy rollback** — Remove one phase without touching others
-**Work in parallel** — Different agents can work on different phases
### Current Situation
We've mixed Phase 03 and 04 in `feature/03-design-polish`. Going forward:
1. **Merge feature/03-design-polish → main** (code review, then merge)
2. **Create feature/04-workout-modification** from main
3. Move/cherry-pick Phase 04 commits to new branch (or just continue from here)
4. **Create feature/05-exercise-encyclopedia** from feature/04 when 04 is done
### Rebase Chain Example
```bash
# After 03 is merged and 04 starts:
git checkout feature/04-workout-modification
git rebase origin/main # Rebase 04 onto latest main
# After 04 is merged and 05 starts:
git checkout feature/05-exercise-encyclopedia
git rebase origin/main # Always rebase new features onto main
```
### Rule of Thumb
- **One feature per branch**
- **Each branch is independent**
- **Rebase onto main before PR**
- **No "feature/03-design-polish" commits after it's merged**
+1 -1
View File
@@ -394,7 +394,7 @@ app.get('/api/today/:programId', async (req, res) => {
} }
}); });
app.listen(PORT, () => { app.listen(PORT, '0.0.0.0', () => {
console.log(`Gravl API running on port ${PORT}`); console.log(`Gravl API running on port ${PORT}`);
}); });
+103
View File
@@ -0,0 +1,103 @@
# Gravl Coding Conventions
## Utvecklingsmetodik
### Red/Green TDD (OBLIGATORISKT)
All ny kod måste följa TDD-cykeln:
```
🔴 RED → 🟢 GREEN → 🔄 REFACTOR
```
#### 1. 🔴 RED - Skriv test först
```javascript
// test/feature.test.js
describe('Feature', () => {
it('should do expected behavior', async () => {
const result = await feature.doSomething();
expect(result).toBe(expected);
});
});
```
**Kör testet - det MÅSTE faila!**
```bash
npm test -- --grep "Feature"
# ❌ FAIL (detta är rätt!)
```
#### 2. 🟢 GREEN - Minimal implementation
Skriv bara tillräckligt med kod för att testet passerar:
```javascript
// src/feature.js
export function doSomething() {
return expected; // Minimal lösning
}
```
**Kör testet igen:**
```bash
npm test -- --grep "Feature"
# ✅ PASS
```
#### 3. 🔄 REFACTOR - Förbättra
Nu kan du:
- Refaktorera för clean code
- Extrahera funktioner
- Förbättra namngivning
- Ta bort duplicering
**Kör testerna kontinuerligt:**
```bash
npm test
# ✅ Alla test måste fortfarande passa
```
---
## Teststruktur
```
/workspace/gravl/
├── src/
│ └── components/
├── server/
│ └── routes/
└── test/
├── unit/ # Enhetstester
├── integration/ # API-tester
└── e2e/ # End-to-end
```
## Namnkonventioner
### Tester
- `[feature].test.js` - Unit tests
- `[feature].integration.test.js` - Integration tests
- Describe-block: Noun (vad testas)
- It-block: "should [verb] [expected outcome]"
### Commits
```
test: add failing test for [feature]
feat: implement [feature] to pass tests
refactor: clean up [feature] implementation
```
---
## Workflow för kodningsagenter
1. **Få uppgift** från Gravl PM
2. **Läs spec** i docs/current-task.md
3. **Skriv failing test** - visa PM
4. **Implementera** tills test passerar
5. **Refaktorera** om nödvändigt
6. **Commit** med rätt prefix
7. **Rapportera** till PM
---
*Uppdaterad: 2026-02-28*
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -11,8 +11,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<title>Gravl - Träning</title> <title>Gravl - Träning</title>
<script type="module" crossorigin src="/assets/index-DIGPkDnZ.js"></script> <script type="module" crossorigin src="/assets/index-hhKetRGz.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D0xrERyI.css"> <link rel="stylesheet" crossorigin href="/assets/index-my_lGtI5.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
-18
View File
@@ -267,24 +267,6 @@ export const Icons = {
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/> <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/>
</svg> </svg>
), ),
spinner: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10"/>
<path d="M12 2a10 10 0 0 1 10 10" opacity="0.3"/>
</svg>
),
alert: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3.05h16.94a2 2 0 0 0 1.71-3.05L13.71 3.86a2 2 0 0 0-3.42 0z"/>
<line x1="12" y1="9" x2="12" y2="13"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>
),
checkmark: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12"/>
</svg>
),
} }
// Icon component wrapper // Icon component wrapper
-19
View File
@@ -1,19 +0,0 @@
$(cat Icons.jsx | sed '/^ refresh: (/,/^ ),$/a\
\ spinner: (\
\ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">\
\ <circle cx="12" cy="12" r="10"/>\
\ <path d="M12 2a10 10 0 0 1 10 10" opacity="0.3"/>\
\ </svg>\
\ ),\
\ alert: (\
\ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">\
\ <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3.05h16.94a2 2 0 0 0 1.71-3.05L13.71 3.86a2 2 0 0 0-3.42 0z"/>\
\ <line x1="12" y1="9" x2="12" y2="13"/>\
\ <line x1="12" y1="17" x2="12.01" y2="17"/>\
\ </svg>\
\ ),\
\ checkmark: (\
\ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">\
\ <polyline points="20 6 9 17 4 12"/>\
\ </svg>\
\ ),')</EOFTEMP
-92
View File
@@ -405,98 +405,6 @@
cursor: not-allowed; cursor: not-allowed;
} }
/* Sync Status Indicator - Saving State */
.sync-status.saving {
background: #cfe8fc;
color: #004085;
animation: pulse 1s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
/* Spinner animation for saving state */
.icon-spinner {
animation: spin 1s linear infinite;
}
/* Save Timeout Warning Banner */
.timeout-banner {
background: #fff3cd;
border-bottom: 1px solid #ffeaa7;
padding: 1rem;
display: flex;
align-items: center;
gap: 0.75rem;
color: #856404;
font-size: 0.95rem;
animation: slideDown 0.3s ease-out;
}
/* Toast Notification Container */
.toast {
position: fixed;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
padding: 1rem 1.5rem;
border-radius: 0.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
font-weight: 500;
z-index: 2000;
animation: slideUp 0.3s ease-out;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
max-width: 90vw;
word-break: break-word;
}
.toast-success {
background: #d4edda;
color: #155724;
}
.toast-error {
background: #f8d7da;
color: #721c24;
}
@keyframes slideUp {
from {
transform: translateX(-50%) translateY(100%);
opacity: 0;
}
to {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
}
/* Mobile adjustments for toast */
@media (max-width: 600px) {
.toast {
bottom: 2rem;
left: 1rem;
right: 1rem;
transform: none;
max-width: none;
}
.timeout-banner {
font-size: 0.9rem;
padding: 0.75rem;
}
}
/* Mobile/Tablet Adjustments */ /* Mobile/Tablet Adjustments */
@media (max-width: 600px) { @media (max-width: 600px) {
.page-header { .page-header {
+10 -85
View File
@@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from 'react' import { useState } from 'react'
import { Icon } from '../components/Icons' import { Icon } from '../components/Icons'
import ExercisePicker from '../components/ExercisePicker' import ExercisePicker from '../components/ExercisePicker'
import { useDraftWorkout } from '../hooks/useDraftWorkout' import { useDraftWorkout } from '../hooks/useDraftWorkout'
@@ -17,37 +17,7 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
const [retryCount, setRetryCount] = useState(0) const [retryCount, setRetryCount] = useState(0)
const [lastSavePayload, setLastSavePayload] = useState(null) const [lastSavePayload, setLastSavePayload] = useState(null)
// Timeout and toast state // Show draft recovery prompt on first render
const [saveTimeout, setSaveTimeout] = useState(false)
const [toast, setToast] = useState(null) // { type, message }
const saveStartTimeRef = useRef(null)
const saveTimeoutRef = useRef(null)
const toastTimeoutRef = useRef(null)
// Cleanup timeouts on unmount
useEffect(() => {
return () => {
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current)
}
}, [])
// Show and auto-hide toast notifications
useEffect(() => {
if (!toast) return
if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current)
toastTimeoutRef.current = setTimeout(() => {
setToast(null)
}, 3000)
return () => {
if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current)
}
}, [toast])
// Show draft recovery prompt on first render
const handleRecoverDraft = () => { const handleRecoverDraft = () => {
if (hasDraft && !draftPromptShown) { if (hasDraft && !draftPromptShown) {
setDraftPromptShown(true) setDraftPromptShown(true)
@@ -137,19 +107,8 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
setSaving(true) setSaving(true)
setSyncStatus('saving') setSyncStatus('saving')
setError(null) setError(null)
setSaveTimeout(false)
setToast(null)
setRetryCount(prev => prev + 1) setRetryCount(prev => prev + 1)
// Track save start time for timeout warning
saveStartTimeRef.current = Date.now()
// Set timeout warning at 8 seconds
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
saveTimeoutRef.current = setTimeout(() => {
setSaveTimeout(true)
}, 8000)
try { try {
// Format for API // Format for API
const payload = { const payload = {
@@ -167,41 +126,28 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
// Call the save callback // Call the save callback
await onSave(workout.id, payload) await onSave(workout.id, payload)
// Clear timeout warning
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
setSaveTimeout(false)
// Success: clear draft and show confirmation // Success: clear draft and show confirmation
clearDraft() clearDraft()
setSyncStatus('saved') setSyncStatus('saved')
setRetryCount(0) // Reset retry count on success setRetryCount(0) // Reset retry count on success
// Show success toast
setToast({ type: 'success', message: 'Sparat!' })
// Log success // Log success
console.log('Workout saved successfully', { console.log('Workout saved successfully', {
workoutId: workout.id, workoutId: workout.id,
exerciseCount: exercises.length, exerciseCount: exercises.length,
retryCount, retryCount
duration: Date.now() - saveStartTimeRef.current
}) })
// Reset status after 2 seconds // Reset status after 2 seconds
setTimeout(() => setSyncStatus('idle'), 2000) setTimeout(() => setSyncStatus('idle'), 2000)
} catch (err) { } catch (err) {
// Clear timeout warning
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
setSaveTimeout(false)
// Log error with context for debugging // Log error with context for debugging
console.error('Failed to save workout:', { console.error('Failed to save workout:', {
error: err, error: err,
workoutId: workout.id, workoutId: workout.id,
exerciseCount: exercises.length, exerciseCount: exercises.length,
retryCount, retryCount,
payload: lastSavePayload, payload: lastSavePayload
duration: Date.now() - saveStartTimeRef.current
}) })
// Determine error message based on error type // Determine error message based on error type
@@ -209,9 +155,6 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
setError(errorMessage) setError(errorMessage)
setSyncStatus('error') setSyncStatus('error')
// Show error toast
setToast({ type: 'error', message: 'Fel vid sparning' })
// Keep draft on error so user doesn't lose work // Keep draft on error so user doesn't lose work
// (useDraftWorkout already auto-saves, so no action needed here) // (useDraftWorkout already auto-saves, so no action needed here)
} finally { } finally {
@@ -273,11 +216,6 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
</button> </button>
<h1>Redigera pass</h1> <h1>Redigera pass</h1>
<div className="save-header-group"> <div className="save-header-group">
{syncStatus === 'saving' && (
<span className="sync-status saving">
<Icon name="spinner" size={16} className="icon-spinner" /> Sparar...
</span>
)}
{syncStatus === 'saved' && ( {syncStatus === 'saved' && (
<span className="sync-status saved"> <span className="sync-status saved">
<Icon name="checkmark" size={16} /> Sparat <Icon name="checkmark" size={16} /> Sparat
@@ -292,9 +230,13 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
className="save-header-btn" className="save-header-btn"
onClick={handleSave} onClick={handleSave}
disabled={saving} disabled={saving}
title={saving ? 'Sparar...' : 'Spara ändringar'}
> >
{syncStatus === 'saving' ? 'Sparar...' : 'Spara'} {syncStatus === 'saving' && (
<>
<Icon name="spinner" size={16} /> Sparar...
</>
)}
{syncStatus !== 'saving' && 'Spara'}
</button> </button>
</div> </div>
</header> </header>
@@ -321,23 +263,6 @@ export default function WorkoutEditPage({ workout, onBack, onSave }) {
</div> </div>
)} )}
{/* Save Timeout Warning */}
{saveTimeout && (
<div className="timeout-banner">
<Icon name="alert" size={18} />
<span>Sparningen tar längre än vanligt. Kontrollera din anslutning.</span>
</div>
)}
{/* Toast Notifications */}
{toast && (
<div className={`toast toast-${toast.type}`}>
{toast.type === 'success' && <Icon name="checkmark" size={16} />}
{toast.type === 'error' && <Icon name="alert" size={16} />}
<span>{toast.message}</span>
</div>
)}
<main className="edit-main"> <main className="edit-main">
<div className="workout-meta-card"> <div className="workout-meta-card">
<h2>{workout.name}</h2> <h2>{workout.name}</h2>