This file tracks significant changes and work sessions on the project.
Implemented lap marker support for KML generation and comprehensive bikelog/PDF improvements including description parsing, weight extraction, and improved formatting.
-
Added lap marker support to KML generation:
- New
--lapscommand-line flag to enable lap marker output - Fetches detailed activity data to access lap information
- Optimization: Only fetches detailed data when
--lapsis enabled and laps array not already present - Creates circular marker placemarks at lap button press locations
- Labels show "Lap 1", "Lap 2", etc. when clicked in Google Earth
- Labels hidden by default (scale=0) to avoid clutter
- New
-
KML styling for lap markers:
- Added
_addLapMarkerStyle()method to create lap marker icon style - Uses Google's placemark_circle.png icon (scale 0.6)
- Integrated lap marker style into KML header when
--lapsenabled
- Added
-
Implementation details:
_outputLapMarkers(): Iterates through laps array and outputs point placemarks_outputLapPoint(): Creates individual KML Point placemark for each lap- Uses
lap.start_indexto find coordinate in activity's coordinate array - Coordinates output as
lng,lat,0per KML spec
-
Weight extraction from descriptions:
- Added
extractWeight()method with case-insensitive parsing - Supports formats: "165", "165 kg", "165kg", "Weight=165", etc.
- Automatically populates
<wt>field in bikelog XML output - Weight excluded from note text (appears only in dedicated XML field)
- Added
-
Description and private_note merging:
- Created
parseActivityText()method to merge description + private_note - Private note content prefixed with "Private: " before merging
- Combined text parsed for key=value pairs
- Remaining non-key/value lines become the description
- Created
-
Custom properties parsing:
- Extracts key=value pairs from merged description/private_note text
- Keys converted to Title Case for consistent output
- Blank lines filtered out from final description
- Special handling for "weight" key (extracted to XML field)
-
Bikelog formatting improvements:
- Double newline separator between multiple activities on same day
- Moving/Elapsed times now appear BEFORE description text
- Custom property key/value pairs appear AFTER description
- All keys displayed in Title Case (e.g., "Biker:", "Motor:")
-
PDF generation enhancement:
- Now fetches detailed activity data to access description and private_note fields
- Consistent with KML lap marker approach for detailed data fetching
-
Added types:
laps?: booleantoKml.Optstype- Added to option definitions in
cmd/options/definitions.ts - Added to KML command configuration
-
Files modified:
src/app/app.ts: Detailed activity fetching for laps and PDFsrc/bikelog/bikelog.ts: Description parsing, weight extraction, formattingsrc/kml/kml.ts: Lap marker style and output methodssrc/kml/types.ts: Added laps option typesrc/cmd/kml/cmd.ts: Added laps to command configsrc/cmd/options/definitions.ts: Defined --laps option
- Lap markers ready for testing in Google Earth
- Weight extraction tested with various formats
- Description parsing handles edge cases (blank lines, empty fields)
Implemented the complete getKml() method in app.ts to fetch activities from Strava and generate KML files. Fixed type errors throughout the KML module.
- Implemented complete workflow:
- Initialize KML generator with options and line styles
- Validate that activities or segments requested
- Fetch activities for each date range using
Api.getActivities() - Convert returned
Dict[]toActivity.Base[]objects - Filter activities based on commute option ('yes', 'no', or 'all')
- Fetch coordinates for each activity using
Api.getStreamCoords() - Generate KML file with
kml.outputData()
- API Usage:
- Used correct
Api.ActivityOptstype with athleteId and query parameters - Properly convert epoch timestamps from Date objects
- Handle FileSpec and string output paths correctly
- Used correct
- Fixed imports: Added
compare,escapeHtml,fieldCapitalize,Fmtfromfmt.ts - Removed unused Main reference: Deleted
private main: Main;field - Fixed _dateString(): Properly handle
DateRangeDef[]with Date objects, format to ISO strings - Simplified _buildActivityDescription(): Basic implementation with distance and elevation (full implementation marked as TODO)
- Fixed type annotations: Added proper types to all parameters and return values
- Removed verbose option: No longer referencing removed option
- Changed dates type: From
DateRanges[]toDateRangeDef[](correct type from @epdoc/daterange) - Added Coord type:
[number, number]for lat/lng pairs - Updated PlacemarkParams: Changed coordinates from
unknown[]toCoord[]
- Fixed compare function: Made generic with proper type guards for string and number comparison
- Added type annotations: All parameters now properly typed (name, unsafe, $1)
- Fixed Fmt class: All calls to
precision()now properly scoped asFmt.precision()
- segment/dep.ts: Fixed import paths to use correct relative paths to strava-api
- segment/base.ts:
- Fixed Schema import path
- Added default initializers for all properties
- segment/data.ts:
- Fixed Coord import
- Added default initializers for all properties
- Used localeCompare for string sorting instead of generic compare
- ✅ getKml() method fully implemented
- ✅ Type checking passes for app.ts and kml.ts
- ✅ All imports corrected
- ⏳ Segment fetching not yet implemented (marked as TODO)
- ⏳ Full activity description not implemented (marked as TODO)
- ⏳ End-to-end testing needed
- Test KML generation with real Strava data
- Implement segment fetching if needed
- Enhance activity descriptions with full details
- Address remaining type errors in other parts of codebase
Migrated KML generation from old stream-based writing to new FileSpecWriter with async/await patterns.
Repository: /Users/jpravetz/dev/@epdoc/std/fs
src/fs.ts: AddedFileSpecWriter as Writerto exports for convenient access viaFS.Writerdeno.json:- Bumped version to 1.1.2
- Updated description to: "Type-safe file system operations with FileSpec, FolderSpec, and streaming support for Node.js and Deno"
1126e93- Export FileSpecWriter as Writer from fs moduleec9401c- Improve package description for @epdoc/fs
- All tests passed (12 tests, 140 steps)
- Published to JSR as @epdoc/[email protected]
Repository: /Users/jpravetz/dev/@epdoc/strava
packages/strava/src/kml/kml.ts: Complete refactor of KmlMain class
-
Replaced stream with writer:
- Changed
private stream: fs.WriteStreamtoprivate writer?: FS.Writer
- Changed
-
Refactored
outputData()method:- Removed event-based stream handling (
stream.once('open'), etc.) - Replaced promise chain with clean async/await
- Added proper try/catch with resource cleanup
- Removed event-based stream handling (
-
Refactored
addActivities()method:- Removed
.reduce()promise chain - Replaced with
for...ofloop with await - Simplified control flow
- Removed
-
Refactored
addSegments()method:- Replaced
.forEach()withfor...ofloops - Made properly async
- Replaced
-
Updated
header()andfooter()methods:- Converted to async/await
- Fixed to write to buffer (not directly to writer parameter)
-
Updated
flush()and_flush()methods:- Now properly writes buffered content to FileSpecWriter
- Uses
await this.writer.write(content)
- Updated
@epdoc/fsfrom 1.1.1 to 1.1.2 in both:packages/strava/deno.jsonpackages/strava-api/deno.json
- Changes complete, code compiles
- Not yet committed
- Pre-existing type errors in other parts of codebase (unrelated to these changes)
- Cleaner, more maintainable code with modern async/await patterns
- Eliminates complex event handling and promise chains
- Proper resource management with try/catch cleanup
- Consistent with project code style guidelines
- Test KML generation end-to-end
- Complete
app.getKml()implementation - Address remaining TODOs in the codebase
Created project documentation files (CLAUDE.md, WORKLOG.md) and completed KML command options to match legacy implementation requirements.
-
@epdoc/strava/CLAUDE.md - Project guide with:
- Repository structure and relationships
- Key dependencies (@epdoc/logger, @epdoc/cliapp, @epdoc/std)
- Architecture patterns (command structure, options system)
- Development workflow
- Code style guidelines
-
@epdoc/strava/WORKLOG.md - Work log for tracking changes
-
@epdoc/logger/CLAUDE.md - Guide referencing GEMINI.md for detailed docs
-
@epdoc/std/CLAUDE.md - Monorepo guide with package overview
segmentsFlatFolder- Flat folder structure option for segmentsimperial- Imperial units option (was missing from definitions)
Added missing options to enable:
- ✅
segmentsFlatFolder- Flat vs hierarchical segment folders - ✅
imperial- Imperial units support - ✅
refresh- Refresh starred segments list
- Added
output- Output filename (string | FileSpec) - Added
commute- Commute filter ('yes' | 'no' | 'all') - Added
dryRun- Dry run mode - Added
segmentsFlatFolder- Flat folder option - Added
imperial- Imperial units - Added
refresh- Refresh segments - Removed
verbose- No longer needed (using @epdoc/logger) - Added detailed comments for all fields
KML command now supports all options from legacy implementation:
- ✅ output (filename)
- ✅ dates (date ranges)
- ✅ more (additional details)
- ✅ commute (filter)
- ✅ dryRun
- ✅ activities (with optional filter)
- ✅ segments
- ✅ segmentsFlatFolder
- ✅ imperial
- ✅ refresh
- All KML options defined and configured
- Types updated to match
- Ready for implementation testing
Corrected option handling for global vs command-specific options, and created new segments command structure.
Discovery: imperial is a global option defined at the root command level, not a command-specific option.
How Global Options Work:
- Global options are defined in
src/cmd/root/cmd.tsviaaddOptions() - Commander.js automatically merges parent (global) options into child command options
- Command action handlers receive both global and command-specific options in the options parameter
- Currently defined global options:
--id <athleteId>- Athlete ID--imperial- Use imperial units--offline- Offline mode
Changes Made:
- Removed
imperialfromsrc/cmd/options/definitions.ts(was incorrectly added as command-specific) - Removed
imperialfromsrc/cmd/kml/cmd.tscmdConfig (now uses global option) - Added comment in kml cmdConfig explaining that imperial is global
- Kept
imperialinKml.Optstype since it will be available via global option merging
Purpose: Analyze starred segments with effort times (separate from KML generation)
Files Created:
src/cmd/segments/cmd.ts- Command implementation withrefreshoptionsrc/cmd/segments/mod.ts- Module exports
Registration:
- Added import in
src/cmd/root/cmd.ts - Registered command in root command initialization
Options for Segments Command:
dates- Date range filteringrefresh- Refresh starred segments list from StravadryRun- Dry run mode
Note: refresh option belongs to segments command, not KML command. Segments are fetched/refreshed via segments command, then can be output to KML via kml command.
KML Command Options (command-specific):
- output, dates, more, commute, dryRun, activities, segments, segmentsFlatFolder
- Uses global
imperialoption
Segments Command Options (command-specific):
- dates, refresh, dryRun
- Uses global
imperialoption
- Segments command structure complete (implementation TODO)
- Global vs command-specific options properly separated
- All commands registered and ready for implementation
Fixed lint errors and runtime error when running kml command.
Files Modified:
src/cmd/segments/cmd.ts- Prefixed unusedoptsparameter with underscoresrc/kml/kml.ts- Prefixed unusedsegmentparameter with underscore inbuildSegmentDescription()
Issue: Cannot use 'in' operator to search for 'Commute' in undefined
Root Causes:
isValidActivityType()guard was checkingApi.ActivityNamewhich doesn't exist- Correct path is
Api.Schema.ActivityName LineStyleDefstype was too restrictive - only allowedApi.ActivityType | 'Default'- Custom style names like 'Commute', 'Moto', 'Segment' were not supported
Changes Made:
-
src/kml/types.ts:
- Changed
LineStyleDefsfromRecord<Api.ActivityType | 'Default', LineStyle>toRecord<string, LineStyle> - Removed unused
Apiimport - Added comment explaining that it supports ActivityTypes plus custom names
- Changed
-
src/kml/guards.ts:
- Fixed path from
Api.ActivityNametoApi.Schema.ActivityName - Changed return type from
name is Api.ActivityTypetoboolean(since we now allow any string) - Added explicit checks for custom style names: 'Default', 'Commute', 'Moto', 'Segment'
- Added null check for
Api.Schema.ActivityNamebefore usinginoperator
- Fixed path from
Note: While 'Commute' is now a separate option (not treated as an activity type), it remains a valid line style name for backwards compatibility and styling purposes.
- All lint errors in modified files resolved
- Runtime error fixed
- Code ready for testing