---
name: Implementation intentions
type: architecture-map-page
slice: 02-tauri-runtime
last_verified: 2026/05/09
---

# `commands::intentions`

> **Where you are:** [Architecture map](../../README.md) → [Tauri runtime](../README.md) → [Commands](README.md) → Implementation intentions

**Plain English summary.** Phase 7. Tiny "if-then" automation rules: "at 09:00 each day, surface my Inbox", "when the morning triage finishes, start a 5-minute timer", "when task X is completed, speak 'Nice'". Frontend owns scheduling and execution because v1 triggers are app-local events plus a local clock check; Rust owns durable storage, validation, and a main-window-only firewall.

## At a glance

- Path: `src-tauri/src/commands/intentions.rs`.
- LOC: 308.
- Tauri commands exposed (5 total), all main-window only:
  - `list_implementation_rules(window, state) -> Result<Vec<ImplementationRuleDto>, String>`.
  - `create_implementation_rule(window, state, request: CreateImplementationRuleRequest) -> Result<ImplementationRuleDto, String>`.
  - `set_implementation_rule_enabled(window, state, id, enabled) -> Result<ImplementationRuleDto, String>`.
  - `mark_implementation_rule_fired(window, state, id, fired_key) -> Result<ImplementationRuleDto, String>`.
  - `delete_implementation_rule(window, state, id) -> Result<(), String>`.
- Events emitted: none.
- Depends on: `lumotia_storage::{insert_implementation_rule, list_implementation_rules, set_implementation_rule_enabled, mark_implementation_rule_fired, delete_implementation_rule, get_task_by_id, ImplementationRuleRow}`, `uuid::Uuid`, `serde_json`. Plus `commands::security::ensure_main_window`.
- Called from frontend at: Settings → Implementation intentions (CRUD); the rule-runner module (`mark_implementation_rule_fired` after firing).

## What's in here

### Trigger and action enums (`src-tauri/src/commands/intentions.rs:21`)

- `RuleTriggerKind`: `TimeOfDay`, `TaskCompleted`, `MorningTriageFinished`. Serialises as `"time_of_day"`, `"task_completed"`, `"morning_triage_finished"`.
- `SurfaceTarget`: `Inbox`, `Today`, `Tasks`, `Task`. The `Task` variant requires a `taskId`.
- `RuleAction` (tagged enum, `kind` discriminator): `Surface { target, taskId, label }`, `StartTimer { seconds, taskId, label }`, `SpeakLine { text }`.

### `CreateImplementationRuleRequest` (`:72`)

`triggerKind`, `triggerValue: String`, `actions: Vec<RuleAction>`, `enabled` (default true), `lastFiredKey: Option<String>`.

### `ImplementationRuleDto` (`:88`) and `TryFrom` (`:100`)

The DTO unpacks the storage row's `actions_json` back into typed `Vec<RuleAction>`. Returns Err on unknown `trigger_kind` strings or invalid JSON — surfaces drift instead of silently swallowing.

### Validators

- `validate_hhmm(value)` (`:125`) — strict HH:MM, two digits each, hours 0..=23, minutes 0..=59. Tested.
- `validate_actions(state, actions)` (`:144`):
  - At least one action.
  - `Surface { target: Task }` requires a `taskId` and the task must exist (via `get_task_by_id`).
  - `StartTimer` rejects anything other than 300 seconds (v1 fixed at 5 minutes).
  - `SpeakLine` requires non-empty text and ≤ 240 chars.
- `validate_request(state, request)` (`:192`) — checks the trigger value matches the trigger kind (`TimeOfDay` requires HH:MM, the others reject any non-empty trigger_value).

### Commands (`:207` onwards)

All five commands `ensure_main_window`. CRUD is otherwise straight wrapping. `create_implementation_rule` generates a UUIDv4, serialises actions to JSON, normalises `trigger_value` (empty for non-time triggers), inserts, and returns the freshly-fetched DTO. `mark_implementation_rule_fired` requires a non-empty `fired_key` (the frontend uses ISO date strings to dedupe daily fires).

### Tests (`:296`)

`hhmm_validation_accepts_and_rejects_expected_shapes` covers a handful of valid and invalid clock strings.

## Data flow

```
Settings -> create_implementation_rule(req)
  -> ensure_main_window
  -> validate_request (HH:MM if applicable, action shape, task existence for surface-task)
  -> uuid::v4 for id
  -> serde_json serialise actions
  -> lumotia_storage::insert_implementation_rule
  -> list/return DTO

frontend rule-runner cron -> evaluates triggers in JS
  -> when a rule fires: invoke('mark_implementation_rule_fired', id, "2026-05-09")
                        -> stamp the dedupe key in DB
```

## Watch-outs

- **Action timer is hard-capped to 300 seconds.** The frontend has to surface this constraint. Future versions will need to widen the validator to accept arbitrary durations.
- **Rule execution lives entirely in the frontend.** If the user closes Lumotia at 08:55 with a 09:00 rule, the rule does not fire. There is no daemon. This is documented in the module header.
- **Validation is sync against the DB.** `validate_actions` calls `get_task_by_id` for every surface-task action. Multiple actions in one rule = multiple round-trips. Acceptable today; consider batching if a rule grows huge.
- **`mark_implementation_rule_fired` requires a non-empty `fired_key`.** The frontend should always pass an ISO date for daily rules and a per-event key for task-completed rules. Empty key = command rejection.
- **No `PowerAssertion`.** None of these actions run inference. No need.

## See also

- [Tasks](tasks.md) — `Surface { target: Task }` and `StartTimer { taskId }` reference task ids.
- [TTS](tts.md) — `SpeakLine` actions are dispatched by the frontend through `tts_speak`.
- [Capabilities and ACL](../capabilities-and-acl.md) — main-window-only is the firewall here.
