Backlog

For completed tasks, see 911-completed.

Priority Legend

  • [P1] - High priority: bugs, blockers, quality issues affecting UX
  • [P2] - Medium priority: improvements, polish, technical debt
  • [P3] - Low priority: future enhancements, ideas, nice-to-have

Bugs [P1]

  • Fix progressive loading not working (load more broken) (2026-01-02)

    • Location: HistoryGrid.swift:287-324 (LoadMoreTrigger)
    • Root cause: SwiftUI Grid is not lazy, so onAppear fires immediately on render, not on scroll
    • Fix: Replaced onAppear with GeometryReader viewport detection
    • Result: Grid height dropped from 3904px to 1986px (50% reduction)
    • Related: 803-load-more-bug
  • Fix cache race condition in FileStorageService (2026-01-03)

    • Location: FileStorageService.swift:384-428
    • Root cause: updateRecord() deleted old file before writing new, causing data loss on error
    • Fix: Write new file first, then delete old file, then update cache atomically
  • Fix AppState tag mutation inconsistency (2026-01-03)

    • Location: MinutaApp.swift:379-397, 447-469
    • Root cause: loadData() had optimization that skipped updates if tag count/IDs matched, missing property changes
    • Fix: Always update tags and rebuild dictionary on load; use direct assignment instead of optional chaining
  • Fix concurrent image operations (2026-01-03)

    • Location: RecordEditorViews.swift:453-509
    • Root cause: Rapid image adds could race on currentRecord
    • Fix: Added isImageOperationInProgress guard to prevent concurrent add/delete operations
  • Debug OKLCH colors

    • Investigate hue distribution across alphabets
    • Check color consistency between views
  • Fix view of tags in edit form

    • Tags display incorrectly in RecordEditor
    • Location: RecordEditorViews.swift
  • Fix play button position when keyboard is undocked

    • Floating button positioned wrong when iPad keyboard is undocked/floating
  • Fix window not hiding immediately on app close (Mac)

    • Window stays visible briefly after closing the app
    • Should hide immediately when user closes
  • Investigate empty block above running timers (2026-03-24)

    • Unexplained empty space appearing over working timers section
    • Need to identify the cause and remove

Test Coverage [P1]

Current: 361 tests passing (unit tests)

  • Add tests for HistoryGrid progressive loading

    • Location: HistoryGrid.swift:173-282
    • Test displayedRecordCount state management:
      • Initial load shows 50 records
      • loadMore() adds 50 more records
      • Count doesn’t exceed total records
    • Test reset behavior on filter change:
      • Resets to 50 when records count drops (new filter)
      • Preserves count when records increase (e.g., new record added)
    • Test LoadMoreTrigger visibility:
      • Shows when hasMoreToLoad is true
      • Hidden when all records displayed
    • Test visibleMonthGroups returns correct subset
    • Could use ViewInspector or UI tests with performance fixture
  • Add unit tests for StorageLocationManager

    • Document folder selection untested
    • Add tests with mock FileManager
  • Add UI test for image attachment

    • Test adding image to a record
    • Verify image thumbnail appears in record row
    • Screenshot to confirm visual appearance
    • Location: UITests/Flows/
  • Add App Intents/Shortcuts integration tests

    • StartTimerIntent, StopTimerIntent have no coverage
    • Location: AppIntents.swift
  • Add view tests for multiplatform project

    • Location: Minuta/ (needs test target in project.yml)
    • ViewInspector for SwiftUI testing
    • ContentView, SettingsSheet tests
  • Add SVGReportRenderer internal tests

    • Test private methods: renderTimeSeriesChart, wrapText, truncateText
    • Current tests only cover public API

Audit Findings (2026-01-03) [P1]

Dead Code:

  • Remove unused dayCellSize constant (2026-01-03)

    • Removed from CalendarDatePicker.swift
  • Remove unused UIKit import from ContentView (2026-01-03)

    • Location: ContentView.swift:2
    • No UIKit types used in file
  • Remove unused UIKit import from SettingsSheet (2026-01-03)

    • False positive: UIPasteboard is used for copy-to-clipboard functionality
  • Remove unused DateRangePickerView.swift (2026-01-03)

    • Location: Views/History/DateRangePickerView.swift
    • 114-line file never referenced - CalendarDatePicker is used instead
  • Remove unused Color(oklch:) initializer (2026-01-03)

    • Location: Color+Hex.swift:30-40
    • Never called; only Color(hex:) is used
    • Also removed unused contrastingTextColor(for: OKLCH) overload

Maintainability:

  • Extract layout constants in CalendarDatePicker (2026-01-03)

    • Location: CalendarDatePicker.swift:75-91
    • Created LayoutMetrics enum with named constants for cellSize, fonts, padding, opacity, etc.
  • Refactor duplicated date range logic in CalendarDatePicker (2026-01-03)

    • Added endOfDay() and setDateRange(from:to:) helper methods
    • Reduced duplicate “add day, subtract second” pattern from 5 occurrences to 1
  • Clean up verbose comments in CalendarDatePicker (2026-01-03)

    • Removed redundant section labels and implementation detail comments
    • Kept useful explanatory comments (corner radius logic, week selection criteria)

UI Tests (16 failures as of 2026-04-15):

  • Fix UI test failures - History section not found

    • Affected: RecordEditingTests.testViewExistingRecords, RecordEditingTests.testShareSheetStaysOpen, RecordEditingTests.testHistoryRecordEditAndDismiss
    • Root cause: app.staticTexts["History"].waitForExistence(timeout: 5) fails — History section not appearing in time
    • Location: RecordEditingTests.swift:17, RecordEditingTests.swift:146
  • Fix UI test failures - Export sheet not opening

    • Affected: all 7 ReportFlowTests (testExportSheetShowsAllFormats, testPDFReportPreview, testPNGReportPreview, testSVGReportPreview, testCSVExportPreview, testExportSheetStaysDuringGeneration, testCancelExportSheet), all 3 ReportWithImagesTests (testPDFReportWithImages, testPNGReportWithImages, testSVGReportWithImages)
    • Root cause: navigateToExportSheet() fails waiting for History section
    • Location: ReportFlowTests.swift:186, ReportWithImagesTests.swift:90
  • Fix UI test failures - Missing fixture data in settings

    • Affected: SettingsTests.testViewExistingTags
    • Root cause: “Acme Corp” tag not found — standard fixture tags not loading into settings view
    • Location: SettingsTests.swift:72
  • Fix UI test failures - Tag management flow

    • Affected: TagManagementTests.testNewTagCreatedOnTimerStop, TagManagementTests.testTimerWithNewTag
    • Root cause: unknown, needs investigation
    • Location: TagManagementTests.swift

Code Quality [P2]

  • Remove debug cellBorder() from HistoryGrid (already done)

    • Location: HistoryGrid.swift:54-75
    • Already wrapped in #if DEBUG - no borders shown in release builds
  • Split AppState into smaller services

    • Location: MinutaApp.swift (320+ lines)
    • Handles: state, tags, records, deletion undo, tag merging
    • Consider: TagManagementService, RecordManagementService
  • Split RecordEditorViews

    • Location: RecordEditorViews.swift:1-450 (450+ lines)
    • Mixed concerns: display, editing, formatting, image operations
    • Extract image handling to separate component
  • Consolidate history reload triggers (2026-01-03)

    • Merged two .task modifiers into one for initial load
    • Added comments explaining each reload trigger
    • Location: ContentView.swift:113-126

Performance [P2]

  • Layout optimization

    • Profile and reduce view complexity across History, Today, and filter sections
    • Identify unnecessary re-renders and layout passes
  • Improve HistoryGrid performance

    • Location: HistoryGrid.swift:173-282
    • Current: 50 initial + 50 per batch (LoadMoreTrigger fixed 2026-01-02)
    • Known issues:
      1. Double grouping: allMonthGroups and visibleMonthGroups both call groupRecordsByMonth
      2. Full re-grouping on each loadMore() instead of appending to existing groups
      3. Nested Grid is not lazy - even 50 records creates 50+ views upfront
      4. onChange(of: records.count) may over-trigger re-renders
    • Potential fixes:
      • Cache allMonthGroups and derive visibleMonthGroups by slicing
      • Incremental loading: append to existing groups instead of re-compute
      • Consider LazyVStack with custom sticky headers instead of nested Grid
      • Debounce filter changes to avoid rapid re-computation
      • Pre-compute day groups and slice by index rather than re-grouping
    • TODO: Profile and identify additional bottlenecks
    • Benchmark targets: Launch <2.0s, Scroll (6 swipes) <8.8s
    • Related: 801-optimization-plan Phase 6, 802-scroll-profiling, 803-load-more-bug
  • Investigate slow year report generation

    • Year range report is noticeably slow (about 500 records)
    • Profile SVGReportRenderer and WebKitReportService
  • Add pagination for large record sets

    • Location: FileStorageService.swift:218-256
    • All records loaded into memory - problematic for years of data

UI/UX Improvements [P2]

  • Add 300ms debounce to record saving while editing
    • Avoid saving on every keystroke; debounce input changes by 300ms before persisting
    • Location: RecordEditorViews.swift (onChange handlers)
  • Edit record sheet

    • Current: floating editor positioned above the clicked row
    • Replace with a proper sheet (modal or slide-over)
    • Location: HistoryGrid.swift, TodaySectionView.swift
  • Better controls for date/time selection in record editor

    • Current text fields are bare; need proper date/time pickers
    • Consider inline pickers or popovers for start/end time editing
  • Keyboard accessibility

    • Arrow keys for tag navigation, Tab for all controls
    • Mac: Shift-Tab for tab character, Tab for navigation
  • Arrow key navigation in CalendarDatePicker

    • Navigate between year/month/week/day/preset buttons with arrow keys
    • Enter/Space to select
    • Location: CalendarDatePicker.swift
  • Tab focus on tag filter buttons

    • Tags in TagFilterView should be focusable via Tab key
    • Location: TagFilterView.swift
  • Support drag and drop for image attachments

    • Drop images onto RecordEditor from Finder, Photos, browsers
  • Apply color theme to whole project

    • Consistent theming, design tokens, dark mode consistency
  • Save everything on Cmd+S

  • Create app logo

    • iOS and Mac icon, App Store assets

Infrastructure [P2]

  • Fix GitHub Actions

    • Figure out good way to run and fix setup
  • Monorepo structure (turborepo or bazel)

    • Build and screenshots cached, triggered on code changes
    • Use code hash as cache key

Publishing [P2]

  • Research iOS app publishing from Uzbekistan

    • App Store Connect requirements, payments, tax implications
  • Trademark protection for “Minuta”

    • Research registration in software category
    • Consider jurisdiction (US, EU, local)

macOS Enhancements [P3]

  • Minified/PiP mode (Mac only)

    • Compact floating view showing only running timers
    • Toggle via pip icon in title bar toolbar (next to pin/settings)
    • Layout: small window with just timer list + play button
    • Behavior:
      • Remembers minified state separately from main window size
      • Works with existing pin (always-on-top) feature
      • Hides History section, Today section, settings button
      • Shows: running timers, floating play button, expand button
      • Expand button (pip.exit) returns to full view
    • Implementation:
      • Add isMinified state to WindowPinManager (rename to WindowStateManager?)
      • Add pip toolbar button in SceneDelegate
      • Conditionally render sections in ContentView based on minified state
      • Animate window resize transition
      • Store minified window frame separately in UserDefaults
    • Related: pairs well with pin button for “mini always-on-top timer”
  • Menu Bar app

    • Status item showing running timers
    • Quick start/stop actions
    • Click to summon/hide always-on-top widget
  • Always-on-top widget (superseded by Minified mode above)

    • Floating window with current timer state
    • Minimal, non-intrusive design

Widgets [P3]

  • Home Screen / Desktop widgets (WidgetKit) (2026-01-05)

    • Small: running timer with stop button, or today summary with start button
    • Medium: running timer + quick start tags with interactive buttons
    • Large: running timers list with stop buttons, today summary, quick start tags grid
    • App Groups for shared data (group.tools.minuta.app)
    • Timeline provider with 1-minute updates for running timers
    • Interactive buttons using App Intents (start/stop timers)
    • Note: Widget extension disabled in build until App Groups configured in Developer portal
  • Lock Screen widgets (iOS) (2026-01-05)

    • Circular: gauge showing running timer progress or today’s hours
    • Rectangular: running timer with tag name and duration, or today summary
    • Inline: compact text showing timer or today’s total
  • Enable widget extension (requires Apple Developer portal setup)

    • Register App Group: group.tools.minuta.app
    • Register bundle ID: tools.minuta.app.widgets with App Groups capability
    • Enable App Groups for tools.minuta.app
    • Uncomment widget lines in project.yml
    • Restore App Groups entitlement in Minuta.entitlements
  • iPhone nightstand mode widget

Watch App [P3]

  • Apple Watch companion
    • Start/stop timers, show current state
    • Complication for quick access
    • WatchConnectivity for sync

Integrations [P3]

  • iCloud sync

    • Architecture is ready, needs implementation
  • Automerge for conflict-free sync

    • CRDT library (automerge-swift v0.6.1)
    • Decision: Use Automerge over custom CRDT implementation
    • See 505-automerge-migration-plan for 11-phase implementation plan
  • Deel timesheet sync

  • Google Calendar import

    • Convert calendar events to time records
  • System calendar auto-tracking

    • Auto-create records from meetings

Architecture [P3]

  • Introduce view model layer

    • Reduce direct AppState coupling from views
  • Implement repository pattern

    • Abstract storage implementation details
  • Stop storing tag colors

    • Tag colors are deterministically derived from tag names via TagColorGenerator (OKLCH-based)
    • Remove color field from the stored Tag model; compute on read instead
    • Drop migration logic that carries forward legacy stored colors
    • Location: Shared/Sources/MinutaShared/Models/Tag.swift, call sites in views
  • Create shared reports library

    • Extract report generation (SVG/PDF/PNG/CSV) into a reusable module consumable by both app and CLI
    • Location: Shared/Sources/MinutaShared/Services/ (ReportService, SVGReportRenderer, WebKitReportService)

Websites [P3]

  • Docs and promo sites
    • SvelteKit + IDS styles
    • Static generation with backlinks component
    • Prepare scaffold project with Daler

CalendarDatePicker [P3]

  • Use full month title instead of abbreviated (2026-01-03)

    • Changed from shortMonthName() (MMM) to monthName() (MMMM)
  • Round borders only for range start/end days (2026-01-03)

    • Added RoundedCorners shape for selective corner rounding
    • Start day: left corners rounded, End day: right corners rounded
    • Middle days: no corners, Single day: all corners
  • Use pressable button style for navigation (2026-01-03)

    • Added PressableButtonStyle with scale and opacity animation
    • Applied to all interactive elements
  • Use pressable button style for presets (2026-01-03)

    • Applied PressableButtonStyle to Today, Week, Month preset buttons
  • Add drag-to-select date range (2026-01-03)

    • Drag across calendar days to select a range
    • Handles dragging in both directions (forward and backward)
  • Toggle visual state for all selectable elements (2026-01-04)

    • Year/month/week labels and presets show “selected” visual when their range matches
    • Single rangeBinding(from:to:) factory method creates bindings for any date range
    • Added SelectableLabelToggleStyle, PresetToggleStyle, WeekNumberToggleStyle
    • All styles now use LayoutMetrics constants and PressableButtonStyle
  • Fix calendar control layout

    • Add folded variant (collapsed view)
    • Make year and month labels wider
    • Set default state to today (not expanded full calendar)
    • Fix overall layout alignment and spacing issues
  • Replace calendar toggler with simple button

    • Current: FilterToggleButton (capsule toggle) to show/hide calendar
    • Change to: plain button that opens calendar on tap
    • Location: HistorySection.swift

TagFilterView [P3]

  • All tags unselected by default

    • Currently all tags are selected on first open; change default to no tags selected (show all records unfiltered)
  • Rearrange filter components

    • Review layout and order of CalendarDatePicker, tag filters, and summary controls
    • Improve visual hierarchy and discoverability
  • Add drag-to-select for tag filters

    • Press and drag across tags to toggle selection
    • Similar to calendar drag-to-select behavior

Inbox

Unsorted ideas. Review periodically.

  • Play button background gradient in OKLCH with hours marks
  • Logo: play button with gradient and hours/minutes marks, 3D watch concept
  • Sort new timers consistent with file system
  • Heatmap view for records (monthly calendar visualization)
  • Auto start/stop based on app activity (macOS, accessibility permissions)
  • Better timeline navigation (swipe gestures, scrubber)
  • Native Apple Charts in history view (iOS 16+ / macOS 13+)
  • Not sure if we need to show Today section when it falls within the selected date span — experiment with hiding it

Related