Activity: the activity command type in .streamd.toml #278

Closed
opened 2026-10-17 08:22:58 +02:00 by agentic-developer · 1 comment
Collaborator

Part of #277 (read it first: the decisions and the spec plan/README.md are there). Needs nothing.

Goal

type = "activity" becomes a command type of .streamd.toml (R28), read, validated, completed, outlined and changed like the others. Nothing is computed yet: streamd <key> of an activity command may fail with not implemented yet until #279/#280.

[commands.sport]
type = "activity"
description = "Training days"
intensity = "count"        # "count" (default) or "fixed"
mixed = "split"            # "split" (default), "first" or "most"
exclude_mixed = false
start = 2025-01-06         # optional bare date

[[commands.sport.series]]
name = "Run"
color = "#D97A2B"
query = { sport = "run" }

[[commands.sport.series]]
name = "Climbing"
color = "#2F5BEA"
query = [{ sport = "climbing" }, { sport = "bouldering" }]

Plan

  1. File model (config/project/file.rs): an ActivityCommandFile read as the table its type names, rejecting unknown fields. series is an array of tables { name, color?, query }. query is one table or a list of them through one_or_many, exactly the Placement that expect of a reminder takes. The key is query, not expect.
  2. Runtime (commands/activity/config.rs in the core): ActivityCommand { description, intensity: Intensity, mixed: MixedMode, exclude_mixed, start: Option<NaiveDate>, series: Vec<Series> }, Series { name, color: Rgb, query: Vec<query::Condition> }. A CommandDefinition::Activity arm. A series without color takes the next colour of a fixed default palette by its position in the config (#D97A2B, #2F5BEA first, they pass colour-blind separation; at least four more after them).
  3. Validation (validate.rs, a new rule in R28's list), every issue with its TOML path:
    • at least one series; series names non-empty and unique within the command;
    • color is #RRGGBB;
    • every dimension of a query is defined (message and closest suggestion as rule 2); every value is reachable (rule 4), aliases resolve as in R29;
    • intensity / mixed list their variants on a wrong value.
    • No propagate = false rule: a shard is counted by its outermost match (decided in #277), so a sub-heading never counts twice.
  4. Schema and completion (schema.rs, complete.rs): the fields with their documentation; query offers dimensions and values like expect; intensity and mixed offer their variants. Add the value fields to index.rs too (rename of a tag/value reaches into query, as it does for expect). rename_dimension covers the dimension names in a query.
  5. Outline and change (outline.rs, change.rs): CommandSettings gets the activity variant; apply_change writes it, keeping comments, rewriting series as an array of tables when it changed. Round-trip test with a configuration holding the example above.
  6. Front-end: frontend/views.rs gets an arm (a placeholder view saying not implemented yet is fine until #281); streamd init is unchanged.

Tests

Parsing (one table vs list query, defaults, unknown field), every validation rule with its path, completion at query = { |, the outline/change round trip, rename of a value used in a query.

Docs

REQUIREMENTS: new R36: Activity Commands (configuration part; R28 lists the type and its validation rule), README (the example), AGENTS.md (commands/activity/).

Part of #277 (read it first: the decisions and the spec `plan/README.md` are there). Needs nothing. ## Goal `type = "activity"` becomes a command type of `.streamd.toml` (R28), read, validated, completed, outlined and changed like the others. Nothing is computed yet: `streamd <key>` of an activity command may fail with `not implemented yet` until #279/#280. ```toml [commands.sport] type = "activity" description = "Training days" intensity = "count" # "count" (default) or "fixed" mixed = "split" # "split" (default), "first" or "most" exclude_mixed = false start = 2025-01-06 # optional bare date [[commands.sport.series]] name = "Run" color = "#D97A2B" query = { sport = "run" } [[commands.sport.series]] name = "Climbing" color = "#2F5BEA" query = [{ sport = "climbing" }, { sport = "bouldering" }] ``` ## Plan 1. **File model** (`config/project/file.rs`): an `ActivityCommandFile` read as the table its `type` names, rejecting unknown fields. `series` is an array of tables `{ name, color?, query }`. `query` is one table or a list of them through `one_or_many`, exactly the `Placement` that `expect` of a reminder takes. The key is `query`, not `expect`. 2. **Runtime** (`commands/activity/config.rs` in the core): `ActivityCommand { description, intensity: Intensity, mixed: MixedMode, exclude_mixed, start: Option<NaiveDate>, series: Vec<Series> }`, `Series { name, color: Rgb, query: Vec<query::Condition> }`. A `CommandDefinition::Activity` arm. A series without `color` takes the next colour of a fixed default palette by its **position in the config** (`#D97A2B`, `#2F5BEA` first, they pass colour-blind separation; at least four more after them). 3. **Validation** (`validate.rs`, a new rule in R28's list), every issue with its TOML path: - at least one series; series names non-empty and unique within the command; - `color` is `#RRGGBB`; - every dimension of a `query` is defined (message and closest suggestion as rule 2); every value is reachable (rule 4), aliases resolve as in R29; - `intensity` / `mixed` list their variants on a wrong value. - **No** `propagate = false` rule: a shard is counted by its outermost match (decided in #277), so a sub-heading never counts twice. 4. **Schema and completion** (`schema.rs`, `complete.rs`): the fields with their documentation; `query` offers dimensions and values like `expect`; `intensity` and `mixed` offer their variants. Add the value fields to `index.rs` too (rename of a tag/value reaches into `query`, as it does for `expect`). `rename_dimension` covers the dimension names in a `query`. 5. **Outline and change** (`outline.rs`, `change.rs`): `CommandSettings` gets the activity variant; `apply_change` writes it, keeping comments, rewriting `series` as an array of tables when it changed. Round-trip test with a configuration holding the example above. 6. Front-end: `frontend/views.rs` gets an arm (a placeholder view saying `not implemented yet` is fine until #281); `streamd init` is unchanged. ## Tests Parsing (one table vs list `query`, defaults, unknown field), every validation rule with its path, completion at `query = { |`, the outline/change round trip, rename of a value used in a `query`. ## Docs REQUIREMENTS: new **R36: Activity Commands** (configuration part; R28 lists the type and its validation rule), README (the example), AGENTS.md (`commands/activity/`).
Author
Collaborator

Implemented in PR #287 (branch 278_activity-command-config, 4 commits: the feature, the index and renames, the CLI tests, the docs).

  • Duration: about 15 minutes of wall-clock time, from branching to the PR.
  • Tokens: about 460k, as measured by the session's context budget (input + output, cache reads included).

Findings during the implementation

  • Bindings vs. app. Adding the variant to CommandKind / CommandSettings in streamd-ffi would break about a dozen exhaustive whens in the Android app (ConfigList.kt, ConfigDraft.kt, ConfigCommandForms.kt, App.kt, …). That is #283/#286 work. So in this story the bindings leave activity commands out of Project.commands() and the outline (CommandSummary::of, CommandSettings::of → None), and the Kotlin surface is unchanged. #283 should replace these with real records and add the when arms in the app.
  • Wrong intensity / mixed values are rejected by serde while the file is read (unknown variant `many`, expected `count` or `fixed` , with line and column), as rounding already is, not by validate.rs. Every other rule is in check_activity, with its TOML path.
  • The query is checked with check_placements, as expect is. It is reused unchanged: dimension defined (closest suggestion), not file, value placed by some tag or file type, aliases resolved through canonical().
  • The index and renames came for free. Adding series[].query to ConfigIndex::read was enough for rename_tag (values that are a tag's name) and rename_dimension to reach into queries. No change to rename.rs was needed, only tests.
  • locate points at the value of a key: an issue at ...query[1].sprt points at "Swim", not at the key sprt. That is the existing behaviour for expect too, so it was not changed.
  • outline::command_names now counts a dimension in a query as named by the command, so a form cannot remove a dimension an activity still queries.
  • Palette: #D97A2B, #2F5BEA, #2E9E6B, #C2408F, #8A6BD1, #B8A11F, starting again after the last. Only the first two come from the colour-blind-checked pair of the spec; the other four were picked by hand and could be checked before the app draws them (#284).
  • The front-end gives an activity command a tab with a placeholder ActivityView (not implemented yet), and streamd <key> fails with streamd <key> is not available: not implemented yet. #280 and #281 replace both.
Implemented in PR #287 (branch `278_activity-command-config`, 4 commits: the feature, the index and renames, the CLI tests, the docs). - **Duration:** about 15 minutes of wall-clock time, from branching to the PR. - **Tokens:** about 460k, as measured by the session's context budget (input + output, cache reads included). ## Findings during the implementation - **Bindings vs. app.** Adding the variant to `CommandKind` / `CommandSettings` in `streamd-ffi` would break about a dozen exhaustive `when`s in the Android app (`ConfigList.kt`, `ConfigDraft.kt`, `ConfigCommandForms.kt`, `App.kt`, …). That is #283/#286 work. So in this story the bindings leave activity commands out of `Project.commands()` and the outline (`CommandSummary::of`, `CommandSettings::of` → `None`), and the Kotlin surface is unchanged. #283 should replace these with real records and add the `when` arms in the app. - **Wrong `intensity` / `mixed` values** are rejected by serde while the file is read (``unknown variant `many`, expected `count` or `fixed` ``, with line and column), as `rounding` already is, not by `validate.rs`. Every other rule is in `check_activity`, with its TOML path. - **The query is checked with `check_placements`, as `expect` is.** It is reused unchanged: dimension defined (closest suggestion), not `file`, value placed by some tag or file type, aliases resolved through `canonical()`. - **The index and renames came for free.** Adding `series[].query` to `ConfigIndex::read` was enough for `rename_tag` (values that are a tag's name) and `rename_dimension` to reach into queries. No change to `rename.rs` was needed, only tests. - **`locate` points at the value of a key:** an issue at `...query[1].sprt` points at `"Swim"`, not at the key `sprt`. That is the existing behaviour for `expect` too, so it was not changed. - **`outline::command_names`** now counts a dimension in a query as named by the command, so a form cannot remove a dimension an activity still queries. - **Palette:** `#D97A2B`, `#2F5BEA`, `#2E9E6B`, `#C2408F`, `#8A6BD1`, `#B8A11F`, starting again after the last. Only the first two come from the colour-blind-checked pair of the spec; the other four were picked by hand and could be checked before the app draws them (#284). - **The front-end** gives an activity command a tab with a placeholder `ActivityView` (`not implemented yet`), and `streamd <key>` fails with ``streamd <key> is not available: not implemented yet``. #280 and #281 replace both.
Sign in to join this conversation.
No labels
android
cli
epic
planned
tui
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
kfickel/streamd#278
No description provided.