Completed Tasks

Archive of completed backlog items.

Rename (2025-12-28)

  • Rename TimeTracker to Minuta (2025-12-28)
    • Renamed folders, filenames, texts throughout codebase
    • Verified build works after rename

UI/UX Improvements (2025-12-17/18)

  • Toast notification for record deletion with undo (2025-12-17)

    • Created ToastView.swift with DeletionToastView and DeletionToastContainer
    • Shows at bottom of screen for 60 seconds with countdown
    • Undo button restores deleted record
    • Animated transitions
  • Fix row tap area in History table (2025-12-17)

    • Changed Grid cells to HStack with fixed widths and .contentShape(Rectangle())
    • Entire row is now tappable including empty space
  • Unify timer editing form (2025-12-18)

    • Created unified RecordEditor component in RecordEditorViews.swift
    • Replaced RunningTimerEditor and EditableRecordRow with single component
    • Running timer: ticking duration (TimelineView), blue circle stop button, auto-save on edit
    • Completed record: Start/End date pickers with duration display, Cancel/Save buttons
    • Added time range validation (end must be after start)
    • Close button (X) on top of edit form (except running timers)
    • Tags created only when timer is stopped (not during auto-save)
    • Running timers hidden from History section
    • History refreshes automatically when records change
  • Improve tag button styling (2025-12-18)

    • Tags now have colored borders matching tag color
    • Fill with tag color when selected, contrasting text (black/white based on luminance)
    • Removed colored dot indicators (redundant with border colors)
    • Updated TagComboBox.swift, TagFilterView.swift, Color+Hex.swift
  • Add background to record editor in History view (2025-12-18)

    • Added gray background with rounded corners to inline editor
    • Visually distinguishes the expanded editor from other rows
  • Make “Add photo” and “Browse files” look like links (2025-12-18)

    • Changed from bordered buttons to plain link-style buttons
    • Uses accent color, dims when disabled
  • Stacked bar chart by tag (2025-12-18)

    • Each day shows stacked segments colored by tag
    • Duration text inside segments (with contrasting black/white)
    • Total duration on top of combined bar
    • Removed Y axis scale (labels inside bars instead)
    • Added ReportDayTagSegment model, updated ReportService and SVGReportRenderer
  • Tag context menu in History filter (2025-12-18)

    • Long press on tag filter buttons shows context menu
    • Archive/Unarchive option (toggles based on current state)
    • Delete option to remove tag
  • Harmony color palette for tags (2025-12-18)

    • Replaced custom colors with Evil Martians’ Harmony palette (500 shade)
    • 17 accessible, consistent colors: red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose
    • Native Swift enum with CaseIterable for type-safe access
    • Source: https://github.com/evilmartians/harmony

Foundation (Phase 0)

  • Improve debugability and testability (2025-12-17)
    • Added TimeTrackingServiceProtocol for dependency injection
    • Added StorageLocationManagerProtocol (@MainActor)
    • Created AppLogger with OSLog (storage, tracking, app, intents categories)
    • Created MockTimeTrackingService actor for testing
    • Added DebugStateView for runtime state inspection (DEBUG builds)
    • Replaced all print() statements with structured logging
    • Files: Logger.swift, MockTimeTrackingService.swift, DebugStateView.swift (new)

Phase 1: Critical Fixes (2025-12-17)

  • Export popup closes itself on first open

    • Fixed with snapshot pattern to prevent re-renders from dismissing sheet
    • HistorySection captures ExportSnapshot when button pressed
    • Sheet uses snapshot data instead of live bindings
  • Fix CSV export escaping

    • Added carriage return (\r) handling per RFC 4180
    • CSVParser.escapeField now handles \r, \n, quotes, and commas
  • Add tagsByID dictionary to AppState

    • Added tagsByID: [UUID: Tag] dictionary for O(1) lookups
    • Added tag(for:) helper method
    • Updated all views to use new lookup pattern
  • Document StorageLocationManager singleton pattern

    • Pattern: singleton for production, protocol for testing
    • Added StorageLocationManagerProtocol (@MainActor)
  • Terminal build commands documentation

    • Added comprehensive commands to CLAUDE.md
    • iOS Simulator, Mac Catalyst, iPad targets documented
  • Use ExportService instead of inline CSV

    • HistorySection now uses DefaultExportService
    • Removed duplicate CSV generation code

Phase 2: Records Layout Enhancement (2025-12-17)

  • Tap to edit records

    • Removed context menu from EditableRecordRow
    • Added tap gesture to open edit mode
    • Tapping anywhere on record row opens editor
    • Swipe-to-delete still works (handled by ForEach .onDelete)
  • History table layout (compact mode)

    • Added isCompact parameter to EditableRecordRow
    • Table layout: tag | date | duration | start — end | comment | chevron
    • Horizontal layout with proper spacing (8pt between elements)
    • Used in HistorySection and TodaySectionView (isCompact: true)
    • Visual affordance: chevron.right icon for tap-to-edit
    • Better use of horizontal space, more scannable format

Phase 3: UI Quick Fixes (2025-12-17)

  • Remove app title from window

    • Removed .navigationTitle("TimeTracker") from ContentView
  • Fix element positions

    • Reviewed layout - well-structured with proper spacing
    • No issues found requiring fixes
  • A4 horizontal for reports

    • Changed to 842x595 points (A4 landscape)
    • Adjusted layout for wider format
    • Updated chart and record sections for horizontal orientation

Phase 3: Testing (2025-12-17)

  • Add report-generator JS tests

    • 53 tests for buildReport(), div(), truncate(), chart, records
    • Location: report-generator/tests/
    • Note: Superseded - JS replaced with pure Swift SVGReportRenderer
  • Add AppState integration tests

    • 15 tests covering workflows, data flow, error propagation
    • Location: Shared/Tests/TimeTrackerSharedTests/Integration/
  • Add Swift-JS integration tests

    • Documented manual testing procedures in 601-testing-guide.md
    • Note: Superseded - JS replaced with pure Swift SVGReportRenderer

Phase 4: Performance (2025-12-17)

  • Cache filtered records

    • Views already use computed properties (acceptable pattern)
    • No additional caching needed
  • Implement record caching

    • Added in-memory cache to LocalFileStorageService
    • Cache invalidated on save/update/delete
    • Reduces disk I/O for repeated queries

Phase 5: Features (2025-12-17)

  • Archive tags

    • Added isArchived property to Tag model
    • Backward-compatible decoding (defaults to false)
    • Archived tags hidden from selection UI
    • Archived tags greyed out in History filter
    • Swipe to archive/unarchive in Settings
  • Delete records from Today and History

    • Swipe-to-delete already existed
    • Added context menu for Mac Catalyst (right-click delete)
  • Keyboard shortcuts (Mac)

    • Cmd+N: Start new timer
    • Cmd+.: Stop all running timers
    • Implemented via FocusedValue and SwiftUI Commands
  • Avoid font bundling in reports

    • Superseded by pure Swift SVG renderer (no external fonts needed)
  • Horizontal export is not horizontal (2025-12-17)

    • JS had correct dimensions (842x595), Swift had portrait (595x842)
    • Fixed WebKitReportService.swift: webview frame, PDF rect, PNG snapshot
  • Replace JS report rendering with pure Swift (2025-12-17)

    • Location: Shared/Sources/TimeTrackerShared/Services/SVGReportRenderer.swift
    • Stack: Core Text (text measurement) + manual layout + SVG string generation
    • Benefits: eliminated JS bundle entirely, simpler architecture
    • Deleted: report.js, report-generator/, pre-build script
    • See 501-yoga-satori-investigation for analysis

Test Coverage (2025-12-19)

  • Add UI tests for report generation (2025-12-19)
    • Created ReportFlowTests.swift with 7 comprehensive tests
    • Tests all export formats: PDF, PNG, SVG, CSV preview
    • Captures 15 baseline screenshots for visual regression
    • Added accessibility identifiers: format-pdf/svg/png/csv, previewButton, shareExportButton, copyAsTextButton
    • Ran xcodegen to include new test file in project

Critical Bugs (Fixed)

  • Fix share popup closing unexpectedly (2025-12-19)

    • Share/export sheet would close immediately after opening
    • Root cause: @State variables in HistorySection were being reset when parent view updated
    • Fix: Moved showingShareSheet and exportSnapshot states to ContentView, added guards to skip data reloads while sheet is open
    • Also guards 60-second auto-refresh timer when share sheet is open
    • Files: ContentView.swift, HistorySection.swift
    • Added accessibility identifier shareButton and UI test testShareSheetStaysOpen
  • Fix endlessly reopening form in history (2025-12-19)

    • Form kept reopening after dismissal due to race condition
    • Root cause: onChange(of: appState.todayRecords) triggered loadHistoryRecords() which rebuilt the ForEach while editingRecordId was still set
    • Fix: Moved editingRecordId to ContentView as binding, skip reload when editingHistoryRecordId != nil
    • Files: ContentView.swift, HistorySection.swift
    • Added UI test: testHistoryRecordEditAndDismiss in RecordEditingTests
  • Fix Svelte SSR + Satori browser incompatibility (2025-12-17)

    • Note: Superseded - entire JS stack replaced with pure Swift SVGReportRenderer
    • Original fix replaced Svelte SSR with pure Satori object structure
  • Fix Save as file not working (2025-12-16)

    • Location: HistorySection.swift (fileExporter)
    • Issue: Save button does not export file
    • Fix: Changed csvDocument from optional to non-optional, added onChange handler for state timing
  • Fix filtered list delete index mismatch (2025-12-16)

    • Location: TodayView.swift (both platforms)
    • Issue: .onDelete uses index from filtered array but array is re-filtered
    • Fix: Added completedRecords computed property to ensure consistent filtering
  • Fix force unwrap on date calculation (2025-12-16)

    • Location: HistoryView.swift (both platforms)
    • Issue: calendar.date(byAdding:)! could crash on edge cases
    • Fix: Changed to guard let with early return

Code Review 2025-12-16

  • Add LocalFileStorageService tests (2025-12-16)

    • Location: Shared/Tests/TimeTrackerSharedTests/Services/LocalFileStorageServiceTests.swift
    • Coverage: 49 tests covering all persistence operations
    • Tasks:
      • Directory creation tests (2 tests)
      • Tags persistence tests (8 tests - save/load/missing file/corrupt JSON)
      • Record persistence tests (6 tests - save/load/update/delete)
      • Error handling tests (7 tests - all FileStorageError cases)
      • Edge cases (6 tests - special chars, unicode, long comments)
      • Date range queries (3 tests)
      • Running records (2 tests)
      • Update/delete operations (7 tests)
      • Concurrent access tests (2 tests)
      • Full workflow integration test (1 test)
  • Refactor ContentView God Object (2025-12-16)

    • Result: 722 lines -> 105 lines, 10 new files created
    • New structure:
      • Views/Components/FlowLayout.swift (43 lines)
      • Views/Components/CSVDocument.swift (25 lines)
      • Views/Sections/RunningTimersSection.swift (16 lines)
      • Views/Sections/TodaySectionView.swift (48 lines)
      • Views/History/DateRangePickerView.swift (68 lines)
      • Views/History/TagFilterView.swift (45 lines)
      • Views/History/HistorySection.swift (173 lines)
      • Views/Settings/SettingsSheet.swift (220 lines)
      • Shared/Services/ImportService.swift (118 lines)
  • Fix AppState concurrency issues (2025-12-16)

    • Analysis: @MainActor already provides sufficient thread safety - no race conditions
    • Tasks:
      • Analyzed array mutations - @MainActor serializes all access, no additional sync needed
      • Fix AppIntents creating separate service instances (bypasses AppState)
        • Added Notification.Name.timeTrackerDataChanged extension
        • AppIntents post notification after modifying data
        • AppState observes notification and reloads data
        • RootView reloads data on scenePhase becoming active
      • Dictionary conversion not needed - arrays work fine with @MainActor isolation
  • Add deinit to AppState (2025-12-16)

    • Location: TimeTracker/Sources/TimeTrackerApp.swift
    • Issue: NotificationCenter observer not cleaned up (cleanupTimer was already handled)
    • Fix: Added dataChangedObserver property and cleanup in deinit
  • Fix race condition in stopTimer (2025-12-16)

    • Status: CLOSED - NOT AN ISSUE
    • Analysis: @MainActor ensures sequential execution, actor-isolated storage serializes file I/O
    • Safe patterns used (firstIndex with ID matching, optional binding)
    • Design is “eventually consistent” - appropriate for local-first app
  • Extract duplicated code (2025-12-16)

    • New utilities in Shared/Sources/TimeTrackerShared/Utilities/:
      • DurationFormatter.swift - 4 format styles (timer, compact, csv, paddedMinutes)
      • ColorPalette.swift - presetColors array + randomColor()
      • CSVParser.swift - parseLine() + escapeField()
    • Added resolveTagId(from:) method to AppState
    • Removed: 8 formatDuration, 5 randomColor, 3 resolveTagId, 3 presetColors, 2 CSV parsing instances
  • Rename misleading view files (2025-12-16)

    • Renamed:
      • TodayView.swift -> NewTimerSheet.swift
      • SettingsView.swift -> ExportView.swift
      • TagsView.swift -> AddTagSheet.swift
  • Remove unused code (2025-12-16)

    • Tasks:
      • Removed import Combine from ContentView.swift
      • StorageLocationManager methods documented as public API (for future use)
  • Update docs (2025-12-16)

    • Tasks:
      • Fixed 904-2024-12-12-catalyst.md - removed TabView reference
      • Updated 402-ios.md - new file structure, components
      • Updated 000-index.md - added utilities, services, views
      • Updated 101-overview.md - added StorageLocationManager, view hierarchy
      • Created 305-storage-location.md - full StorageLocationManager docs
      • Updated 302-export.md - added ImportService documentation

Validation & Error Handling

  • Add time validation (2025-12-16)

    • Location: TimeRecord.swift, TimeTrackingService.swift, FileStorageService.swift
    • Added TimeRecordError enum with endTimeBeforeStartTime case
    • Added validateTimeRange() and hasValidTimeRange to TimeRecord
    • Added validation in updateRecord() and createManualRecord()
    • Tests: 4 TimeRecord tests + 5 TimeTrackingService tests
  • Add tag name validation (2025-12-16)

    • Location: Tag.swift, TimeTrackingService.swift, FileStorageService.swift
    • Added TagError enum with emptyName case
    • Added validateName() and hasValidName to Tag model
    • Added validation in createTag() and updateTag()
    • Tests: 4 tests (empty name, whitespace-only name, update validation, valid name)

Code Duplication

  • Move AppState to Shared package (2025-12-16)

  • Move Color+Hex to Shared package (2025-12-16)

    • Resolved: Single implementation in multiplatform project

Test Coverage

  • Add LocalFileStorageService tests (2025-12-16)

    • Completed: 49 tests covering entire persistence layer
  • Add TimeTrackingService tests (2025-12-16)

    • Tag lifecycle (create, update, delete)
    • Record filtering
    • Timer state management
  • Add ExportService tests (2025-12-16)

    • CSV header validation
    • Escaping edge cases
    • Column selection
  • Set up GitHub Actions for CI (2025-12-16)

    • Created .github/workflows/build.yml
    • Builds Shared package and TimeTracker app
  • Add ImportService tests (2025-12-16)

    • Location: Shared/Tests/TimeTrackerSharedTests/Services/ImportServiceTests.swift
    • Coverage: 21 tests covering all HEY CSV import scenarios
    • Categories:
      • Valid CSV Parsing (5 tests - basic, multiple records, date format, empty notes, duration ignored)
      • Empty/Missing Content (4 tests - empty string, header only, empty lines, empty category)
      • Malformed CSV (4 tests - fewer fields, invalid dates, mixed valid/invalid, extra fields)
      • Tag Creation/Reuse (5 tests - new tags, existing tags, case insensitive, deduplication, color palette)
      • Edge Cases (3 tests - quoted fields with commas, unicode, whitespace-only lines)
  • Add ReportService tests (2025-12-16)

    • Location: Shared/Tests/TimeTrackerSharedTests/Services/ReportServiceTests.swift
    • Coverage: 37 tests covering ReportData.build() and model Codable conformance
    • Categories:
      • ReportData Build Tests (6 tests - empty/single/multiple/untagged/mixed records)
      • Date Range Filtering Tests (4 tests - within/outside range, boundary dates)
      • Tag Filtering Tests (6 tests - specific tags, multiple tags, include/exclude untagged)
      • Chart Data Tests (5 tests - aggregation, sorting, top 5 limit, colors)
      • Record Sorting Tests (1 test - newest first)
      • Total Hours Tests (2 tests - calculation with/without filtering)
      • Metadata Tests (2 tests - title, date range string)
      • Record Item Tests (3 tests - data correctness, nil comment, untagged)
      • Codable Tests (5 tests - all model encode/decode)
      • Edge Cases (3 tests - unknown tagId, empty tags, zero/long duration)

Phase 6: Image Attachments (2025-12-17)

  • Add images to log entries (2025-12-17)
    • Model: Added images: [String] array to TimeRecord (filenames)
    • Storage: LocalFileStorageService has saveImage, loadImage, deleteImage, imageURL methods
    • Service: TimeTrackingService has addImage, removeImage methods
    • UI: RecordImageViews.swift with gallery, full-screen viewer, image pickers
    • Integration: EditableRecordRow shows gallery and add photo buttons in edit mode
    • Features:
      • Image picker (PHPickerViewController on iOS/iPad)
      • Document picker for Mac Catalyst file selection
      • Full-screen image viewer with pinch-to-zoom and drag gestures
      • Context menu with View, Share, Delete options
      • Gallery showing thumbnails with lazy loading
      • Images stored in same directory as records: records/YYYY/MM/{record-id}_{index}.jpg
      • Automatic image deletion when record is deleted
    • Processing: Images resized to 1920px max, compressed to 80% JPEG quality
    • Backward Compatibility: Old records without images field decode with empty array
    • Tests: All 196 existing tests pass, backward compatibility verified
    • Files Modified:
      • Shared/Sources/TimeTrackerShared/Models/TimeRecord.swift - added images array + imageFilename helper
      • Shared/Sources/TimeTrackerShared/Services/FileStorageService.swift - added image protocol methods
      • Shared/Sources/TimeTrackerShared/Services/TimeTrackingService.swift - added addImage, removeImage
      • TimeTracker/Sources/Views/Components/RecordImageViews.swift (new) - UI components
      • docs/200-models/202-time-record.md - updated documentation

Phase 7: Report Redesign (2025-12-17)

  • Redesign report layout (2025-12-17)
    • Chart: Time series with days on X axis, hours on Y axis (replaced tag-based bar chart)
    • Columns: 3 text columns below chart with comments and images
    • Models: Added ReportDayItem (date, totalHours) and ReportCommentItem (recordId, date, tag info, comment, image filenames)
    • ReportData: Extended with dayItems and commentItems arrays, backward-compatible decoding
    • SVGReportRenderer: Complete redesign
      • Vertical bar chart for daily hours with grid lines
      • Round-robin comment distribution across 3 columns
      • ReportImageProvider protocol for lazy image loading
      • Max 8 images embedded as base64 thumbnails (80x80px)
      • Text wrapping utility for multi-line comments (4 line limit)
    • Tests: 62 ReportService tests (13 new for time series, comments, backwards compatibility)
    • Files Modified:
      • Shared/Sources/TimeTrackerShared/Models/ReportData.swift - new models, custom Codable
      • Shared/Sources/TimeTrackerShared/Services/ReportService.swift - buildDayItems, buildCommentItems
      • Shared/Sources/TimeTrackerShared/Services/SVGReportRenderer.swift - complete rewrite
      • Shared/Tests/TimeTrackerSharedTests/Services/ReportServiceTests.swift - updated tests

Phase 8: UI Refinements (2025-12-17)

  • History Grid table layout (2025-12-17)

    • Replaced List ForEach with SwiftUI Grid for auto-sized columns
    • Removed day grouping headers (flat list sorted by date)
    • System date format (adapts to locale)
    • Inline editing: tap row to expand edit view within Grid
    • Context menu for delete (replaces swipe-to-delete in Grid)
    • onRecordUpdated callback to refresh UI after save
  • Multiline comment field (2025-12-17)

    • Changed TextField to TextEditor in EditableRecordRow edit mode
    • Height: 60-120pt (expands with content)
    • Styled with rounded border and gray background
    • Scrollable when content exceeds max height
  • EditableRecordRow callbacks (2025-12-17)

    • Added startInEditMode for external edit state control
    • Added onDismissEdit callback for edit completion
    • Added onRecordUpdated callback with updated record

Enhancements

  • Satori Migration for Reports (2025-12-16)

    • Note: Superseded - replaced with pure Swift SVGReportRenderer (2025-12-17)
    • Original implementation used Satori for HTML-to-SVG conversion
  • Unified Share Button (2025-12-16)

    • Replaced three export buttons with single Share button in History section
    • New ShareExportSheet with format selection: PDF, SVG, PNG, CSV
    • Action buttons: Preview, Save File (Mac only), Share, Copy as Text (SVG/CSV only)
    • Preview uses PDFKit for PDF, UIImage for PNG, monospaced text for SVG/CSV
    • Added generateSVG() and generatePNG() to WebKitReportService
    • Added ExportFormat.contentType for proper UTType handling
    • Location: TimeTracker/Sources/Views/Components/ShareExportSheet.swift
  • Report Generator (2025-12-16)

    • PDF report generation with chart visualization
    • Models: ReportData, ReportChartItem, ReportRecordItem, ReportOptions
    • Features: date range filtering, tag filtering, top 5 chart aggregation
    • Location: Shared/Sources/TimeTrackerShared/Services/ReportService.swift, Shared/Sources/TimeTrackerShared/Models/ReportData.swift
    • Documentation: docs/300-services/306-report.md
  • Import from HEY (2025-12-16)

    • CSV import from HEY email time tracking
    • Format: Start,End,Duration,Category,Notes
    • Auto-creates tags from Category field
    • Located in SettingsPopup
  • Shortcuts Integration (2025-12-16)

    • StartTimerIntent and StopTimerIntent
    • Works with Apple Shortcuts app
  • Unified Single-View Layout (2025-12-16)

    • Removed tabs, combined Today/History/Settings
    • Floating play button
    • Settings popup via gear icon
  • Tag Filters for History (2025-12-16)

    • FlowLayout for wrapping buttons
    • Toggle selection for multiple tags
    • Total time display
  • Export Improvements (2025-12-16)

    • Save panel for Mac (fileExporter)
    • Share sheet for iOS
    • CSV with date range in filename
  • Mac Catalyst Polish (2025-12-16)

    • Hidden title bar
    • Native save panel
  • Enhance tag filters in History (2025-12-16)

    • Added text field to filter tags by name (case-insensitive search)
    • Tags now sorted alphabetically by name
    • Existing toggle selection behavior preserved

Low-Hanging Fruit Batch 2 (2025-12-28)

  • Pull to refresh (2025-12-28)

    • Added .refreshable modifier to List in ContentView
    • Reloads app data and history records on pull
  • Cancel timer button (2025-12-28)

    • Added gray X button next to stop button for running timers
    • Deletes timer without saving (uses existing soft delete)
    • Location: RecordEditorViews.swift
  • Dependency injection for ReportService (2025-12-28)

    • ShareExportSheet now accepts reportService: ReportServiceProtocol via init
    • Updated ReportServiceProtocol to include imageProvider method overloads
    • Improves testability
  • Optimize history filtering (2025-12-28)

    • Cached filtered records in @State with onChange handlers
    • Added static DurationFormatter to avoid repeated instantiation
    • Location: HistorySection.swift
  • Fix deletion of records (2025-12-28)

    • Record deletion now works correctly

History Grid Redesign (2025-12-30)

  • Smart history layout with nested grid structure (2025-12-30)
    • Nested Grid structure: Year > Month > Day > Records
    • Vertical text for year and full month name (rotated -90 degrees, aligned to top)
    • 4x larger font (44pt) for date columns
    • 2-digit day with leading zero
    • Comment always on second line (removed inline option)
    • Tag column flexible width (fills remaining space)
    • Debug borders on cells for layout visualization
    • Smart visibility: hide year/month/day columns based on date range
    • Hide tag column when single tag filter active
    • Files:
      • HistoryGrid.swift (new) - nested grid structure with YearGroup, MonthGroup, DayGroup, RecordGridRow
      • HistoryRecordRow.swift (new) - HistoryColumnSizes constants
      • HistoryVisibility.swift (new) - visibility computation
      • HistorySection.swift - simplified, uses HistoryGrid
      • TodaySectionView.swift - uses TodayGrid with RecordGridRow

Low-Hanging Fruit (2025-12-26)

  • Add DurationFormatter tests (2025-12-26)

    • Location: Shared/Tests/TimeTrackerSharedTests/Utilities/DurationFormatterTests.swift
    • 46 tests covering all 4 format methods: timerFormat, compactFormat, csvFormat, paddedMinutesFormat
    • Edge cases: zero, negative, fractional seconds, boundaries, large values
  • Add CSVParser tests (2025-12-26)

    • Location: Shared/Tests/TimeTrackerSharedTests/Utilities/CSVParserTests.swift
    • 31 tests covering parseLine() and escapeField() per RFC 4180
    • Categories: basic parsing, quoted fields, empty fields, escaping, round-trip
  • Replace print() with AppLogger (2025-12-26)

    • Location: RecordImageViews.swift:101
    • Changed print("Failed to load image...") to AppLogger.storage.error("loadImage: ...")
    • Proper structured logging for image load failures
  • Extract tag lookup to shared utility (2025-12-26)

    • Added Array<Tag>.findByName(_:) and findByName(_:excludingId:) extensions to Tag.swift
    • Updated 4 call sites: AppIntents.swift (1), TimeTrackerApp.swift (3)
    • Eliminates duplicated case-insensitive tag lookup pattern
  • Fix timer cleanup in AppState (2025-12-26)

    • Location: TimeTrackerApp.swift:241
    • Added cleanupTimer?.invalidate() at start of startCleanupTimer()
    • Prevents multiple timers if method called more than once
  • Standardize protocol naming convention (2025-12-26)

    • Added “Protocol” suffix to all service protocols
    • Renamed: FileStorageServiceProtocol, ExportServiceProtocol, ImportServiceProtocol, ReportServiceProtocol, ReportImageProviderProtocol
    • Updated 8 source files and 1 mock file
  • Cache DateFormatter instances (2025-12-26)

    • Created DateFormatters.swift with 10 static cached formatters
    • Updated: RecordEditorViews.swift, HistorySection.swift, TimeRecord.swift, AppIntents.swift
    • Performance improvement: DateFormatter creation is expensive
  • Swipe to dismiss deletion toast (2025-12-26)

    • Added DragGesture to DeletionToastView for swipe-down dismissal
    • Added confirmDelete(_:) method to AppState
    • Swipe down >50pt confirms deletion and dismisses toast
    • Spring animation snaps back if threshold not met

Related