Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Execute Gradle build
run: ./gradlew ${{ inputs.gradle-tasks }} --scan
- name: Upload dists
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: dist
name: dist-${{ inputs.runs-on }}
path: build/dist/**/*
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,10 @@ build
/gen-src
# mac stuff
**/.DS_Store

# KI / Agent stuff

.agentbridge
opencode.jsonc
opencode.jsonc.tui-migration.bak
tui.json
105 changes: 105 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# AGENTS.md - SimpleTimeTracking (STT)

## Purpose

SimpleTimeTracking (STT) is a cross-platform desktop application for **fast, unobtrusive time tracking**.
It prioritizes the individual user's workflow over management reporting — design goal is to let you start/stop tracking with minimal friction and copy results into other systems.

## Use Cases

- **Start/stop working** on a task with a single command or button click
- **Resume** the last or any previous activity
- **Search** across historical activities by comment text
- **Report** on time spent per activity/group (day, period, search filter)
- **Overtime** calculation against configured worktime rules
- **CSV/Jira export** for integration with other systems
- **Dual interface**: JavaFX GUI for desktop use, CLI for scripting/terminal use
- **Automatic backup** and item logging

## Architecture

### Tech Stack

| Layer | Technology |
|---|---|
| Language | **Kotlin** (JVM), with some Java source files |
| UI | **JavaFX 21**, ControlsFX, RichTextFX |
| Build | **Gradle** (Kotlin DSL), JDK 21+ |
| DI | **Dagger 2** (annotation-based, `kapt`) |
| Parsing | **ANTLR 4** for command text parsing |
| Config | **YAML** (SnakeYAML) |
| Event Bus | **MBassador** (in-process pub/sub) |
| Testing | **JUnit 4**, **AssertJ**, **Mockito** (+ mockito-kotlin), TestFX |

### Module / Package Layout

All source under `src/main/kotlin/org/stt/`:

```
org.stt/
├── cli/ # CLI entry points (Main, ReportPrinter, FormatConverter)
├── command/ # Command pattern: Commands.kt, CommandHandler.kt, CommandFormatter (ANTLR)
├── config/ # Configuration loading from YAML, config classes
├── connector/jira/ # Jira integration (REST client)
├── csv/ # CSV import/export
├── event/ # Event bus classes (NotifyUser, ShuttingDown, TimePassedEvent)
├── gui/ # JavaFX UI (MainWindow, ActivitiesController, ReportController, Settings)
├── model/ # Domain model (TimeTrackingItem, ReportingItem)
├── persistence/ # ItemReader / ItemWriter interfaces + STT file format implementation
├── query/ # Query/filter logic over time tracking items (Criteria, WorkTimeQueries)
├── reporting/ # Report generation (SummingReportGenerator, OvertimeReportGenerator)
├── text/ # Text completion, categorization, grouping (CommonPrefixGrouper, JiraExpansion)
├── time/ # Date/time utilities, duration rounding
├── update/ # Update check mechanism
├── validation/ # Input validation
```

> See [doc/ui-architecture.md](doc/ui-architecture.md) for a detailed breakdown of UI components, controllers, data objects, and event bus wiring.

### Key Design Patterns

- **Command pattern**: `Command` (sealed hierarchy) + `CommandHandler` interface (Visitor), parsed via ANTLR grammar
- **Dependency Injection**: Dagger `@Module` / `@Provides` / `@Inject`; components are `Dagger*Application` (e.g., `DaggerCLIApplication`, `DaggerUIApplication`)
- **Event-driven**: `MBassador` event bus for decoupled UI updates (time ticks, shutdown, notifications)
- **Repository**: `ItemReader` / `ItemWriter` interfaces abstract storage; `STTItemReader` / `STTItemWriter` implement the plain-text file format
- **Service lifecycle**: `Service` interface with `start()`/`stop()` for config, backup, and logging services
- **Data stored**: A plain-text file (`.stt/activities`) with one record per line

## Code Style

- **Language**: Kotlin (prefer immutable data classes, `val`, extension functions)
- **Naming**: `camelCase` for methods/variables, `PascalCase` for classes, no underscores
- **Test naming**: `should[Expectation]` — e.g., `shouldCreateItemWithoutEnd`
- **Test structure**: GIVEN / WHEN / THEN comment annotations, `sut` (system under test) variable
- **Imports**: explicit single imports (no wildcard `.*` except for standard lib / assertions)
- **Nullability**: explicit nullable types with `?`, prefer `?:` elvis operator
- **DI**: constructor injection via `@Inject`, module-provided bindings for platform/third-party types
- **Logging**: `java.util.logging.Logger` (`Logger.getLogger(...)`)
- **File format**: one time-tracking record per line, human-readable text

## Build & Test

```bash
./gradlew build # compile + test + assemble fat jar
./gradlew test # run all tests (JUnit 4)
./gradlew check # + static analysis (SonarQube if configured)
./gradlew dist # jlink + jpackage for native distribution
./gradlew run # compile and start the GUI application
```

Output fat jar: `build/libs/STT-<version>.jar`

## Commit Convention

Use [Conventional Commits](https://www.conventionalcommits.org/):

```
feat: add resume-last-activity CLI command
fix: report crashes on empty activity list
refactor: extract DurationRounder from ReportGenerator
test: add overtime calculation edge cases
docs: update README with new CLI usage
chore: bump Dagger to 2.50
```

Scopes (optional): `cli`, `gui`, `persistence`, `query`, `reporting`, `config`, `time`, `deps`
115 changes: 115 additions & 0 deletions doc/ui-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# SimpleTimeTracking — UI Architecture

## Overview

Single-window, **tab-based JavaFX interface** with four tabs. All panels live inside a `TabPane`; there are no separate windows.

```
UIMain (JavaFX Application entry point)
└── DaggerUIApplication (DI component)
└── MainWindowController (orchestrator)
├── ActivitiesController
├── ReportController
├── SettingsController
└── InfoController
```

---

## 1. Activities Tab — `ActivitiesController.kt` + `ActivitiesPanel.fxml`

Primary workspace: enter new time entries, view/search history, edit/delete/continue/stop items.

| UI Element | Purpose | Data Object |
|---|---|---|
| **Command text area** (`StyleClassedTextArea` via RichTextFX) | Type activity text (ANTLR grammar); `Ctrl+Enter` executes, `Ctrl+Space` auto-completes | Parsed into `Command` sealed hierarchy (`StartItem`, `StopItem`, `DeleteItem`, `EditItem`, ...) |
| **Activity list** (`ListView<TimeTrackingItem>`) | Scrollable list of all items, grouped by day with date headers | `ObservableArrayList<TimeTrackingItem>` — full in-memory set |
| **Per-item action buttons** (continue, stop, edit, delete) | Appear on hover via `FadeTransition` in `TimeTrackingItemCellWithActions` | Operates on a single `TimeTrackingItem` |
| **Search/filter field** | Real-time list filtering | Filters `allItems` by activity text match via `TimeTrackingListFilter` |
| **Worktime pane** (`WorktimePane`, embedded `FlowPane`) | Shows remaining worktime today (or overtime) and weekly total | Queries `WorkTimeQueries`; updates every 1s via `TimePassedEvent` |

**Data flow**: User types → `CommandFormatter` (ANTLR) parses → `ValidatingCommandHandler` validates → `CommandHandler` persists to `.stt/activities` → `ItemModified` events fire on the bus → all listeners refresh.

**Dialogs triggered from this tab**:
- Delete-confirm: `STTOptionDialogs.showDeleteOrKeepDialog()`
- Overlap warning: `STTOptionDialogs.showItemCoversOtherItemsDialog()`
- Bulk-rename prompt: `STTOptionDialogs.showRenameDialog()`
- No-current-item: `STTOptionDialogs.showNoCurrentItemAndItemIsLaterDialog()`

---

## 2. Report Tab — `ReportController.kt` + `ReportPanel.fxml`

Daily report with date picker, grouped activity table, and summary sidebar.

| UI Element | Purpose | Data Object |
|---|---|---|
| **DatePicker** (custom day-cell rendering) | Select a date; tracked days are highlighted | Returns `LocalDate` |
| **Report table** (`TableView<ReportListItem>`) | Grouped activities: comment, raw duration, rounded duration | `ReportListItem` (comment, isBreak, duration, roundedDuration) |
| **Summary labels** | Sidebar: total, effective, break, uncovered, non-effective, start/end | Computed from `SummingReportGenerator.Report` (`List<ReportingItem>` + duration totals) |

**Data flow**: Date selected → `ReportBinding` (an `ObjectBinding`) computes a `Report` via `SummingReportGenerator` using a `Criteria` query → `MappedListBinding` transforms into `ReportListItem` rows.

---

## 3. Settings Tab — `SettingsController.kt` (no FXML, programmatic UI)

ControlsFX `PropertySheet` populated from config beans.

| UI Element | Purpose | Data Object |
|---|---|---|
| **ControlsFX `PropertySheet`** | Auto-generated editable property grid | `ConfigRoot`, `ActivitiesConfig`, `BackupConfig`, `WorktimeConfig`, `JiraConfig`, `CommonPrefixGrouperConfig`, `ReportConfig`, `CliConfig` |
| **Custom editors** | `PathSetting` (file chooser button), `PasswordSetting` (obfuscated field) | Wrapper types for special config values |

Persistence happens on shutdown via `ConfigServiceFacade`.

---

## 4. Info Tab — `InfoController.kt` + `InfoPanel.fxml`

App metadata and update check.

| UI Element | Purpose | Data Object |
|---|---|---|
| **Version / Commit labels** | Display app metadata | `@Named("version")`, `@Named("commit hash")` strings |
| **"Check for updates" button** | Triggers async `UpdateChecker` | `UpdateChecker` queries a remote URL |
| **Project homepage link** | Opens browser | Hardcoded URL |

---

## Event Bus Wiring

All controllers communicate through a shared `MBassador<Any>` singleton (Dagger-provided).

| Publisher | Event | Listeners |
|---|---|---|
| `ActivitiesController` | `ItemInserted`, `ItemDeleted`, `ItemReplaced` | `ActivitiesController` (self-refresh), `ReportController.OnItemChangeListener`, `WorktimePane` |
| `UIMain` (1s timer) | `TimePassedEvent` | `WorktimePane` (recalc worktime) |
| `MainWindowController` (ESC/close) | `ShuttingDown` | `UIMain` (service stop + `Platform.exit()`) |
| Any controller | `NotifyUser` | `MainWindowController` (show ControlsFX `Notification`) |
| `BulkRenameHelper` (inside ActivitiesController) | `ItemReplaced` (monitors single edits) | Detects single-item rename and prompts to rename all matching items |

---

## Model Classes (Core)

| Class | Key Fields | Role |
|---|---|---|
| `TimeTrackingItem` | `activity: String`, `start: LocalDateTime`, `end: LocalDateTime?` | Fundamental persistence unit — one line in `.stt/activities` |
| `ReportingItem` | `duration`, `roundedDuration`, `comment`, `isBreak` | Aggregate view for report rows |
| `SummingReportGenerator.Report` | `reportingItems`, `start`, `end`, `uncoveredDuration`, etc. | Complete daily report payload |
| `Command` (sealed) | Subtypes: `StartItem`, `StopItem`, `DeleteItem`, `EditItem`, ... | Parsed user input from command text area |
| `Criteria` | `start`, `end`, `activity` | Query specification for filtering items |
| `ItemModified` / `ItemInserted` / `ItemDeleted` / `ItemReplaced` | Respective payload fields | Event bus messages |

## Config Objects (mapped to Settings tab)

| Class | Purpose |
|---|---|
| `ActivitiesConfig` | UI behavior: grouping, filtering duplicates, close-on-continue/stop, ask-before-delete, delete-closes-gaps |
| `BackupConfig` | Backup file settings |
| `WorktimeConfig` | Worktime rules for overtime/remaining-time calculation |
| `JiraConfig` | Jira REST API credentials and settings |
| `CommonPrefixGrouperConfig` | Text grouping settings |
| `ReportConfig` | Report formatting options |
| `CliConfig` | CLI encoding settings |
Loading