Filter for Tasks #328

Closed
opened 2026-10-25 10:25:22 +01:00 by kfickel · 2 comments
Owner
No description provided.
Collaborator

Plan: filter the items of a state command by tags and by due range

Source: a handover spec and a clickable mock-up (plan/README.md, plan/task-screen-mockup.html; the folder is not tracked, so everything you need is below). The mock-up's data and the dimension names project / area are placeholders: use the real partitioned_by dimensions of the command.

Decisions (agreed with the user)

  • "Until today" keeps the current behaviour: an item is shown only if moment <= now. It is the default.
  • "Next 7 days" shows items up to the end of the day today + 7, in the timezone of the project (inclusive). "Any date" has no limit.
  • Selected tags are combined with OR across all dimensions: an item matches if it has any selected (dimension, value). The app shows one chip row for all partitioned_by dimensions, replacing today's one row per dimension.
  • Counts follow Show closed: the tag counts, the tab counts and "Show N tasks" count over the listed states (open only, or closed too when Show closed is on), as the chips do today.
  • The CLI and the terminal view get the due range too (--due). They keep their partition flags as they are (AND across dimensions); only the app gets the OR selection.
  • The filter is not saved across app restarts (out of scope). It survives configuration changes and process death through rememberSaveable, per command key, as partition does today.
  • The Stream, All, Changes, reminder and activity screens do not change.

1. Core (crates/streamd-core)

commands/state/items.rs

  • Add pub enum Due { Now, Days(u32), Any }, which is how far into the future items are listed:
    • Now: moment <= now (what show_future = false is today).
    • Days(n): moment < start_of_day(today + n + 1, tz), where today is the date of now in tz. Use localize::start_of_day, the one place a local day becomes a moment.
    • Any: no limit (what show_future = true is today).
    • Give it a method that resolves it once, e.g. Due::shows(&self, moment, now, tz) -> bool, or resolve it into a limit before the loop.
    • FromStr / Due::parse for the CLI: now, any, <N>d (e.g. 7d). An error names the accepted forms. Add CoreError::InvalidDue, or reuse an existing variant if one fits.
  • Filter gets OR alternatives besides its AND partition: Filter::any_of(values: &[(String, String)], config) builds one query::Condition per (dimension, value) (alias-aware, like Condition::new). An item passes if it matches any of them; an empty list passes everything. Filter::new (the AND map) stays for the CLI.
  • Filter::shows(item, due, now, tz) replaces the show_future: bool parameter. Update shown_items the same way.

commands/state/board.rs

  • board(shards, command, filter, due, now, tz) instead of show_future. partition_counts is unchanged.

api.rs

  • state_items(…, partition, due: Due, now): replaces show_future. The tz comes from project.timezone().
  • state_board(project, stream, key, any_of: &[(String, String)], due: Due, now): the partition becomes the OR list. Only the bindings call it; [] means all.
  • partition_values(project, stream, key, due: Due, include_closed, now): counts over Filter::none() within due, as today.
  • Update every caller (CLI, LSP if any) by mapping the old bool to Due::Any / Due::Now.

Tests (core): Days(7) includes the last minute of day 7 and excludes the first of day 8, also across a DST change (use a zone such as Europe/Berlin). Now equals the old show_future = false, Any equals true. any_of with two values of different dimensions shows the union. An alias in any_of matches its tag. Counts within Days(7) include future items.

2. Bindings (crates/streamd-ffi)

  • Add the enum Due { Now, Days { days: u32 }, Any } to records.rs with its conversion into the core type.
  • project.stateItems(stream, key, partition, due, now), project.stateBoard(stream, key, anyOf: List<Placement>, due, now) and project.partitionValues(stream, key, due, includeClosed, now) take Due instead of showFuture.
  • Update tests/kotlin/StoreTest.kt (lines ~136, 155, 176, 270, 276) and add a Rust test that Days(7) reaches a future item.

3. CLI (crates/streamd-cli)

  • commands/state/cli.rs: add --due <now|Nd|any>, default now, parsed by Due::parse (a wrong value is a usage error). --show-future stays as a shorthand for --due any and conflicts with --due. StateAction::{List, Interactive} carry a Due instead of show_future. Add parse tests next to the existing ones.
  • run.rs: pass the Due through.
  • Interactive view (interactive/model.rs, view.rs, terminal.rs): f cycles now → 7d → any → now. Key bar: "change how far ahead items are shown". Summary: · due within 7 days / · with future items. Update the view tests.

4. App (android/)

core/StateList.kt

  • data class PartitionTag(val dimension: String, val value: String).
  • enum class DueRange { UntilNow, NextWeek, AnyDate }, mapped to Due.Now, Due.Days(7u), Due.Any in StreamSession.
  • data class TaskFilter(val due: DueRange = UntilNow, val tags: Set<PartitionTag> = emptySet()).
  • StateLists.list(key, filter: TaskFilter, showClosed) replaces the partition map.
  • New: StateLists.preview(key, filter, showClosed): Previewed, with Previewed.Done(matching: Int, partitions: List<Partition>) / Failed(reason). This is the live count of the sheet. It calls stateBoard and partitionValues only and reads no file. matching is the number of items in the listed states.

core/StreamSession.kt

  • list: stateBoard(stream, key, filter.tags → Placements, due, now) and partitionValues(…, due, showClosed, now).
  • Add preview.
  • The overview (line ~473) and the note moves (~654) keep their present behaviour: Due.Now where they pass false today.

Pure logic: new ui/TaskFilter.kt, unit-tested in src/test/

  • topFive(partitions): every (dimension, value) of all partitions, sorted by count descending, then value, then dimension; the first five.
  • isDefault(filter, top): due == UntilNow and (no tags, or exactly one tag that is in top). This drives the badge dot.
  • chipRow(filter, top): the chips in order: the scope chip if due != UntilNow, All, a selected chip for each selected tag outside top (sorted like the list), then the top five.
  • tapped(filter, tag?): a chip replaces the selection with its tag; All (null) clears it; the due range is kept.
  • tagRows(partitions, query): all values sorted as topFive sorts them, each with whether it is top five and its count. Matching is case-insensitive on the value or on the dimension name, ignoring a leading @ in the query. It returns the matched range in the value and/or in the dimension, for the highlight.
  • tagsSummary(n): "all tags" / "N selected".
  • reset(): TaskFilter().
  • dueHint(moment, now, zone) for a future item: "today" (later today), "tomorrow", "in N days" (plurals resource). Null if the moment is not after now.
  • keeping (StateMoves.kt) becomes TaskFilter.keeping(partitions): it drops the selected tags that are no longer among the values.

ui/StateList.kt

  • StateListScreen: keeps filter: TaskFilter in rememberSaveable(key) with a Saver (the due range by name, the tags as pairs of strings) instead of partition, and sheetOpen. The LaunchedEffect keys on filter.
  • Header: an IconButton with StreamdIcons for ic_tune.xml (it is already in res/drawable; add it to Icons.kt if missing), between Search and ⋮, with content description "Filter tasks" (string resource). Wrap it in BadgedBox { Badge() } while !isDefault.
  • The chip row replaces PartitionChips: one horizontally scrolling row from chipRow. Tag chips are labelled value count, as today. The scope chip ("Next 7 days" / "Any date" with ic_expand_more) is a FilterChip/AssistChip outlined and labelled in colorScheme.primary; it opens the sheet.
  • ItemRow: when dueHint is not null, the date part of the supporting line is drawn in colorScheme.primary followed by · in 8 days, e.g. 2026-11-02 · in 8 days · <file title>. No literal colours: use theme roles.
  • The tab counts already come from the filtered board.

Filter sheet: new ui/TaskFilterSheet.kt

  • TaskFilterSheet wraps a ModalBottomSheet (drag handle, skipPartiallyExpanded = true) around internal fun TaskFilterSheetContent(draft, preview: Previewed?, query, onDraft, onQuery, onReset, onApply). This split is for the screenshot tests: layoutlib does not render the window of a modal sheet, so the content is drawn directly.
  • The sheet holds the draft (remember of the applied filter when it opens) and asks lists.preview whenever the draft changes. Dismissing it (scrim, swipe, back) discards the draft. "Show N tasks" applies it and closes the sheet.
  • From top to bottom:
    1. Header: "Filter" on the left, a TextButton "Reset" on the right. It sets TaskFilter() and is disabled when the draft already equals it.
    2. Due: ConnectedChoice (Components.kt) with "Until today" / "Next 7 days" / "Any date". One line of helper text below:
      • "Hides tasks dated later than now."
      • "Shows tasks up to 7 days ahead."
      • "Shows tasks of any date."
    3. Tags: the label "Tags" with tagsSummary on the right, then SearchField (hint "Search tags or dimensions", a clear (x) button while text is entered). The rows are in a LazyColumn; each row has:
      • a Checkbox (multi-select),
      • @Value in StreamdTheme.mono with the tint of the tag (TagTint/tagSpan, as tags are coloured elsewhere) and the match highlighted (e.g. bold or a background on the matched span),
      • a small "TOP 5" label for the top five,
      • the dimension on a second line in small onSurfaceVariant mono, also highlighted,
      • the count on the right.
        With no match it shows No tag matches "<query>". The counts come from the preview, so they follow the draft's due range.
    4. Footer: a full-width Button "Show N tasks" (plurals), or "No tasks match" when 0. It stays enabled at 0, so an empty result can still be applied, as in the mock-up.
  • All strings go in res/values/strings.xml, counts as <plurals>.

Screenshot tests (StateListScreenshots.kt)

  • Update the existing scenes for the single chip row.
  • Add:
    • StateListFilteredScreenshot: Next 7 days, the scope chip, a selected non-top-five chip, the badge, future rows with their hint.
    • StateListFilterSheetScreenshot: the default draft, with more than five tags over two dimensions.
    • StateListFilterSheetSearchScreenshot: a query matching a dimension name, highlighted.
    • StateListFilterSheetNoMatchScreenshot.
    • StateListFilterSheetEmptyScreenshot: "No tasks match".
  • Draw the sheet content in a Surface within Screen { }, with a fixed NOW (Fixtures.kt).
  • Record with updateDebugScreenshotTest and look at every image.
  • Unit tests for everything in TaskFilter.kt, and for the Saver.

5. Documentation

  • README.md:
    • the paragraph on the state screen of the app (line ~1047): the filter button, the sheet, the single chip row with the top five, the due range and the date hint;
    • the CLI section of state commands (--due, --show-future);
    • the f key of the interactive view (line ~611 on).
  • REQUIREMENTS.md:
    • the requirement of the state listing / the interactive view (the --due forms, f);
    • R35 (the Due of the bindings: stateItems, stateBoard with OR placements, partitionValues);
    • the app's part (the filter of the state screen).
  • AGENTS.md:
    • commands/state/ (Due, Filter::any_of);
    • the facade list (state_board takes OR values and a Due, partition_values a Due);
    • records.rs (Due);
    • StreamSession (list(key, filter, showClosed), preview);
    • ui/ (TaskFilter.kt, TaskFilterSheet.kt).

Acceptance

  • Filter icon next to search, with a dot while the filter is not the default.
  • The default view is unchanged: items after now are hidden.
  • "Next 7 days" and "Any date" reveal future items, with the coloured date and the relative hint.
  • The tag search matches the value or the dimension name, case-insensitive, ignoring @; the match is highlighted; there is a clear button and the no-match line.
  • Every tag row shows its dimension, the TOP 5 label where it applies, and its count within the draft's due range.
  • The chip row shows All, the top five, a chip for every other selected value and, if the due range is not the default, the scope chip that opens the sheet.
  • Back, swipe-down and the scrim discard the draft; "Show N tasks" applies it; Reset is disabled at the default.
  • streamd task --due 7d, --due any, --show-future; f cycles in streamd task -i.
  • nix flake check passes; nix build .#streamd-app passes (unit and screenshot tests).
## Plan: filter the items of a state command by tags and by due range Source: a handover spec and a clickable mock-up (`plan/README.md`, `plan/task-screen-mockup.html`; the folder is not tracked, so everything you need is below). The mock-up's data and the dimension names `project` / `area` are placeholders: use the real `partitioned_by` dimensions of the command. ### Decisions (agreed with the user) - **"Until today" keeps the current behaviour:** an item is shown only if `moment <= now`. It is the default. - **"Next 7 days"** shows items up to the end of the day *today + 7*, in the timezone of the project (inclusive). **"Any date"** has no limit. - **Selected tags are combined with OR across all dimensions:** an item matches if it has any selected `(dimension, value)`. The app shows **one** chip row for all `partitioned_by` dimensions, replacing today's one row per dimension. - **Counts follow *Show closed*:** the tag counts, the tab counts and "Show N tasks" count over the listed states (open only, or closed too when *Show closed* is on), as the chips do today. - **The CLI and the terminal view get the due range too** (`--due`). They keep their partition flags as they are (AND across dimensions); only the app gets the OR selection. - The filter is not saved across app restarts (out of scope). It survives configuration changes and process death through `rememberSaveable`, per command key, as `partition` does today. - The Stream, All, Changes, reminder and activity screens do not change. ### 1. Core (`crates/streamd-core`) **`commands/state/items.rs`** - Add `pub enum Due { Now, Days(u32), Any }`, which is how far into the future items are listed: - `Now`: `moment <= now` (what `show_future = false` is today). - `Days(n)`: `moment < start_of_day(today + n + 1, tz)`, where `today` is the date of `now` in `tz`. Use `localize::start_of_day`, the one place a local day becomes a moment. - `Any`: no limit (what `show_future = true` is today). - Give it a method that resolves it once, e.g. `Due::shows(&self, moment, now, tz) -> bool`, or resolve it into a limit before the loop. - `FromStr` / `Due::parse` for the CLI: `now`, `any`, `<N>d` (e.g. `7d`). An error names the accepted forms. Add `CoreError::InvalidDue`, or reuse an existing variant if one fits. - `Filter` gets OR alternatives besides its AND partition: `Filter::any_of(values: &[(String, String)], config)` builds one `query::Condition` per `(dimension, value)` (alias-aware, like `Condition::new`). An item passes if it matches any of them; an empty list passes everything. `Filter::new` (the AND map) stays for the CLI. - `Filter::shows(item, due, now, tz)` replaces the `show_future: bool` parameter. Update `shown_items` the same way. **`commands/state/board.rs`** - `board(shards, command, filter, due, now, tz)` instead of `show_future`. `partition_counts` is unchanged. **`api.rs`** - `state_items(…, partition, due: Due, now)`: replaces `show_future`. The tz comes from `project.timezone()`. - `state_board(project, stream, key, any_of: &[(String, String)], due: Due, now)`: the partition becomes the OR list. Only the bindings call it; `[]` means all. - `partition_values(project, stream, key, due: Due, include_closed, now)`: counts over `Filter::none()` within `due`, as today. - Update every caller (CLI, LSP if any) by mapping the old bool to `Due::Any` / `Due::Now`. **Tests (core):** `Days(7)` includes the last minute of day 7 and excludes the first of day 8, also across a DST change (use a zone such as `Europe/Berlin`). `Now` equals the old `show_future = false`, `Any` equals `true`. `any_of` with two values of different dimensions shows the union. An alias in `any_of` matches its tag. Counts within `Days(7)` include future items. ### 2. Bindings (`crates/streamd-ffi`) - Add the enum `Due { Now, Days { days: u32 }, Any }` to `records.rs` with its conversion into the core type. - `project.stateItems(stream, key, partition, due, now)`, `project.stateBoard(stream, key, anyOf: List<Placement>, due, now)` and `project.partitionValues(stream, key, due, includeClosed, now)` take `Due` instead of `showFuture`. - Update `tests/kotlin/StoreTest.kt` (lines ~136, 155, 176, 270, 276) and add a Rust test that `Days(7)` reaches a future item. ### 3. CLI (`crates/streamd-cli`) - `commands/state/cli.rs`: add `--due <now|Nd|any>`, default `now`, parsed by `Due::parse` (a wrong value is a usage error). `--show-future` stays as a shorthand for `--due any` and conflicts with `--due`. `StateAction::{List, Interactive}` carry a `Due` instead of `show_future`. Add parse tests next to the existing ones. - `run.rs`: pass the `Due` through. - Interactive view (`interactive/model.rs`, `view.rs`, `terminal.rs`): `f` cycles `now → 7d → any → now`. Key bar: "change how far ahead items are shown". Summary: `· due within 7 days` / `· with future items`. Update the view tests. ### 4. App (`android/`) **`core/StateList.kt`** - `data class PartitionTag(val dimension: String, val value: String)`. - `enum class DueRange { UntilNow, NextWeek, AnyDate }`, mapped to `Due.Now`, `Due.Days(7u)`, `Due.Any` in `StreamSession`. - `data class TaskFilter(val due: DueRange = UntilNow, val tags: Set<PartitionTag> = emptySet())`. - `StateLists.list(key, filter: TaskFilter, showClosed)` replaces the `partition` map. - New: `StateLists.preview(key, filter, showClosed): Previewed`, with `Previewed.Done(matching: Int, partitions: List<Partition>)` / `Failed(reason)`. This is the live count of the sheet. It calls `stateBoard` and `partitionValues` only and reads no file. `matching` is the number of items in the listed states. **`core/StreamSession.kt`** - `list`: `stateBoard(stream, key, filter.tags → Placements, due, now)` and `partitionValues(…, due, showClosed, now)`. - Add `preview`. - The overview (line ~473) and the note moves (~654) keep their present behaviour: `Due.Now` where they pass `false` today. **Pure logic: new `ui/TaskFilter.kt`, unit-tested in `src/test/`** - `topFive(partitions)`: every `(dimension, value)` of all partitions, sorted by count descending, then value, then dimension; the first five. - `isDefault(filter, top)`: `due == UntilNow` and (no tags, or exactly one tag that is in `top`). This drives the badge dot. - `chipRow(filter, top)`: the chips in order: the scope chip if `due != UntilNow`, `All`, a selected chip for each selected tag outside `top` (sorted like the list), then the top five. - `tapped(filter, tag?)`: a chip replaces the selection with its tag; `All` (null) clears it; the due range is kept. - `tagRows(partitions, query)`: all values sorted as `topFive` sorts them, each with whether it is top five and its count. Matching is case-insensitive on the value **or** on the dimension name, ignoring a leading `@` in the query. It returns the matched range in the value and/or in the dimension, for the highlight. - `tagsSummary(n)`: "all tags" / "N selected". - `reset()`: `TaskFilter()`. - `dueHint(moment, now, zone)` for a future item: "today" (later today), "tomorrow", "in N days" (plurals resource). Null if the moment is not after now. - `keeping` (`StateMoves.kt`) becomes `TaskFilter.keeping(partitions)`: it drops the selected tags that are no longer among the values. **`ui/StateList.kt`** - `StateListScreen`: keeps `filter: TaskFilter` in `rememberSaveable(key)` with a `Saver` (the due range by name, the tags as pairs of strings) instead of `partition`, and `sheetOpen`. The `LaunchedEffect` keys on `filter`. - `Header`: an `IconButton` with `StreamdIcons` for `ic_tune.xml` (it is already in `res/drawable`; add it to `Icons.kt` if missing), between Search and ⋮, with content description "Filter tasks" (string resource). Wrap it in `BadgedBox { Badge() }` while `!isDefault`. - The chip row replaces `PartitionChips`: one horizontally scrolling row from `chipRow`. Tag chips are labelled `value count`, as today. The scope chip ("Next 7 days" / "Any date" with `ic_expand_more`) is a `FilterChip`/`AssistChip` outlined and labelled in `colorScheme.primary`; it opens the sheet. - `ItemRow`: when `dueHint` is not null, the date part of the supporting line is drawn in `colorScheme.primary` followed by ` · in 8 days`, e.g. `2026-11-02 · in 8 days · <file title>`. No literal colours: use theme roles. - The tab counts already come from the filtered board. **Filter sheet: new `ui/TaskFilterSheet.kt`** - `TaskFilterSheet` wraps a `ModalBottomSheet` (drag handle, `skipPartiallyExpanded = true`) around `internal fun TaskFilterSheetContent(draft, preview: Previewed?, query, onDraft, onQuery, onReset, onApply)`. This split is for the screenshot tests: layoutlib does not render the window of a modal sheet, so the content is drawn directly. - The sheet holds the draft (`remember` of the applied filter when it opens) and asks `lists.preview` whenever the draft changes. Dismissing it (scrim, swipe, back) discards the draft. "Show N tasks" applies it and closes the sheet. - From top to bottom: 1. **Header:** "Filter" on the left, a `TextButton` "Reset" on the right. It sets `TaskFilter()` and is disabled when the draft already equals it. 2. **Due:** `ConnectedChoice` (`Components.kt`) with "Until today" / "Next 7 days" / "Any date". One line of helper text below: - "Hides tasks dated later than now." - "Shows tasks up to 7 days ahead." - "Shows tasks of any date." 3. **Tags:** the label "Tags" with `tagsSummary` on the right, then `SearchField` (hint "Search tags or dimensions", a clear (x) button while text is entered). The rows are in a `LazyColumn`; each row has: - a `Checkbox` (multi-select), - `@Value` in `StreamdTheme.mono` with the tint of the tag (`TagTint`/`tagSpan`, as tags are coloured elsewhere) and the match highlighted (e.g. bold or a background on the matched span), - a small "TOP 5" label for the top five, - the dimension on a second line in small `onSurfaceVariant` mono, also highlighted, - the count on the right. With no match it shows `No tag matches "<query>".` The counts come from the preview, so they follow the draft's due range. 4. **Footer:** a full-width `Button` "Show N tasks" (plurals), or "No tasks match" when 0. It stays enabled at 0, so an empty result can still be applied, as in the mock-up. - All strings go in `res/values/strings.xml`, counts as `<plurals>`. **Screenshot tests (`StateListScreenshots.kt`)** - Update the existing scenes for the single chip row. - Add: - `StateListFilteredScreenshot`: Next 7 days, the scope chip, a selected non-top-five chip, the badge, future rows with their hint. - `StateListFilterSheetScreenshot`: the default draft, with more than five tags over two dimensions. - `StateListFilterSheetSearchScreenshot`: a query matching a dimension name, highlighted. - `StateListFilterSheetNoMatchScreenshot`. - `StateListFilterSheetEmptyScreenshot`: "No tasks match". - Draw the sheet content in a `Surface` within `Screen { }`, with a fixed `NOW` (`Fixtures.kt`). - Record with `updateDebugScreenshotTest` and look at every image. - Unit tests for everything in `TaskFilter.kt`, and for the `Saver`. ### 5. Documentation - **README.md**: - the paragraph on the state screen of the app (line ~1047): the filter button, the sheet, the single chip row with the top five, the due range and the date hint; - the CLI section of state commands (`--due`, `--show-future`); - the `f` key of the interactive view (line ~611 on). - **REQUIREMENTS.md**: - the requirement of the state listing / the interactive view (the `--due` forms, `f`); - R35 (the `Due` of the bindings: `stateItems`, `stateBoard` with OR placements, `partitionValues`); - the app's part (the filter of the state screen). - **AGENTS.md**: - `commands/state/` (`Due`, `Filter::any_of`); - the facade list (`state_board` takes OR values and a `Due`, `partition_values` a `Due`); - `records.rs` (`Due`); - `StreamSession` (`list(key, filter, showClosed)`, `preview`); - `ui/` (`TaskFilter.kt`, `TaskFilterSheet.kt`). ### Acceptance - [ ] Filter icon next to search, with a dot while the filter is not the default. - [ ] The default view is unchanged: items after now are hidden. - [ ] "Next 7 days" and "Any date" reveal future items, with the coloured date and the relative hint. - [ ] The tag search matches the value or the dimension name, case-insensitive, ignoring `@`; the match is highlighted; there is a clear button and the no-match line. - [ ] Every tag row shows its dimension, the TOP 5 label where it applies, and its count within the draft's due range. - [ ] The chip row shows All, the top five, a chip for every other selected value and, if the due range is not the default, the scope chip that opens the sheet. - [ ] Back, swipe-down and the scrim discard the draft; "Show N tasks" applies it; Reset is disabled at the default. - [ ] `streamd task --due 7d`, `--due any`, `--show-future`; `f` cycles in `streamd task -i`. - [ ] `nix flake check` passes; `nix build .#streamd-app` passes (unit and screenshot tests).
Collaborator

Implemented in #331.

Time: about 25 minutes, including nix flake check, the bindings build and nix build .#streamd-app.
Tokens: about 255k.

Findings during the implementation

  • --due is a new flag on every state command. Validation reserved only show-future and help as flag names, so due is now reserved too: a partitioned_by dimension named due would have clashed with it.
  • Days(n) is resolved as "the start of day n+1 in the project's timezone, minus one nanosecond", through start_of_day. That makes it exact across a clock change; a test on the Berlin change of 2026-10-25 covers it.
  • Due::parse uses its own small DueError with FromStr instead of a new CoreError variant. Only the CLI parses it; the bindings pass the enum directly.
  • In the TUI, f now cycles three ranges (now → 7d → any). A front-end test that expected one press to show everything was adjusted.
  • The old map-based keeping (StateMoves.kt) became TaskFilter.keeping; chosen values that disappear are still cleared.
  • The existing state-list screenshot references changed only by the new filter button.
  • rememberModalBottomSheetState is deprecated in the pinned Material version. It is kept, like ActivityRange.kt, so both sheets can move to the new API together.
  • The strings say "tasks", as agreed, although a state command can be about other things (orders, …). If that matters, it's a one-line change in strings.xml.
Implemented in #331. **Time:** about 25 minutes, including `nix flake check`, the bindings build and `nix build .#streamd-app`. **Tokens:** about 255k. **Findings during the implementation** - `--due` is a new flag on every state command. Validation reserved only `show-future` and `help` as flag names, so `due` is now reserved too: a `partitioned_by` dimension named `due` would have clashed with it. - `Days(n)` is resolved as "the start of day n+1 in the project's timezone, minus one nanosecond", through `start_of_day`. That makes it exact across a clock change; a test on the Berlin change of 2026-10-25 covers it. - `Due::parse` uses its own small `DueError` with `FromStr` instead of a new `CoreError` variant. Only the CLI parses it; the bindings pass the enum directly. - In the TUI, `f` now cycles three ranges (now → 7d → any). A front-end test that expected one press to show everything was adjusted. - The old map-based `keeping` (`StateMoves.kt`) became `TaskFilter.keeping`; chosen values that disappear are still cleared. - The existing state-list screenshot references changed only by the new filter button. - `rememberModalBottomSheetState` is deprecated in the pinned Material version. It is kept, like `ActivityRange.kt`, so both sheets can move to the new API together. - The strings say "tasks", as agreed, although a state command can be about other things (orders, …). If that matters, it's a one-line change in `strings.xml`.
Sign in to join this conversation.
No labels
android
cli
epic
planned
tui
No milestone
No project
No assignees
2 participants
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#328
No description provided.