Activity command #277

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

A new command type, activity. It shows on which days shards matching one or more queries (series) were written: as a GitHub-style year grid, a month calendar or a single day, with day statistics. Typical uses: on which days did I run, climb, or both? How many days was I in the home office, and how many only in the office (hence "exclude mixed days")?

The full spec from the design session (concepts, configuration, computation, app screens) is in the first comment. It is plan/README.md, and the screenshots of the prototype are attached to the stories that use them. Split into #278–#286.

Decisions (made after the spec, binding for all stories)

  • CLI (streamd <key> [RANGE]): an adaptive chart. One day prints its shards, 2–42 days a month calendar, longer ranges one 53-week strip per year. The totals and the Days table follow. streamd <key> shards [RANGE] lists shards as file:line. The default range is this month, and --exclude-mixed / --include-mixed override the config. → #280
  • TUI (streamd <key> -i): a dashboard with drill-down in boxed tiles like the app, with one page scrolling vertically. Ranges above 42 days are drawn as one strip per year, stacked. The day view is the shard list only (no axis in the terminal). t opens an overlay with a text field and quick picks. → #281, #282
  • Mixed days with three or more series and mixed = "split": the app draws wedges. The terminal shows ◩ in the first two colours.
  • Range input is parsed (open question 2): the text syntax is parsed in the core, as you type, by the TUI overlay and the app sheet alike. The examples are labelled.
  • Counting (open question 5): a shard counts by its outermost match of a series' query, as an if reminder names its shards (R29). So no propagate = false rule is needed. A shard matching two series counts for both and is reported as a warning at run time, not by validation.
  • Days table in the day view (open question 3): the app keeps it, as in the screenshots. The TUI day view has no table.
  • Timeline overlap (open question 4): out of scope; follow-up #314.
  • All shards paging (open question 6): paged from the core (offset / limit).
  • The default colour palette starts with #D97A2B, #2F5BEA and has at least four more. The colour belongs to the series' position in the config.

Dependencies

A solid arrow means "must be merged first". A dotted arrow is a link that works as a placeholder until the other story is merged.

flowchart TD
    T278["#278 Config: the activity type"]
    T279["#279 Core: computation and ranges"]
    T280["#280 CLI: streamd key"]
    T281["#281 TUI: dashboard"]
    T282["#282 TUI: range overlay"]
    T283["#283 Bindings"]
    T284["#284 Android: Activity screen"]
    T285["#285 Android: range sheet, all shards, stream filter"]
    T286["#286 Android: command form"]

    T278 --> T279
    T279 --> T280
    T279 --> T281
    T280 --> T281
    T281 --> T282
    T279 --> T283
    T283 --> T284
    T284 --> T285
    T283 --> T286

    T281 -.->|t| T282
    T284 -.->|range label, show all| T285
    T284 -.->|Edit command| T286

Stories

Status (20 Oct 2026): done

All nine stories are merged into main, and CI on main (f9f1b12) is green. The terminal track (#280 → #281 → #282) and the app track (#283 → #284 → #285/#286) are complete. R36, the README and AGENTS.md describe the feature; #313 updates the last stale line about Edit command, which now opens the activity command's own form.

Left over, outside this epic:

  • #314: stack timeline dots that are minutes apart (open question 4).
  • The Android screens were never checked by hand on an emulator or device (no KVM was available for #284). This manual check is still to be done.
A new command type, `activity`. It shows on which days shards matching one or more queries (**series**) were written: as a GitHub-style year grid, a month calendar or a single day, with day statistics. Typical uses: on which days did I run, climb, or both? How many days was I in the home office, and how many only in the office (hence "exclude mixed days")? The full spec from the design session (concepts, configuration, computation, app screens) is in the first comment. It is `plan/README.md`, and the screenshots of the prototype are attached to the stories that use them. Split into #278–#286. ## Decisions (made after the spec, binding for all stories) - **CLI** (`streamd <key> [RANGE]`): an **adaptive chart**. One day prints its shards, 2–42 days a month calendar, longer ranges one 53-week strip per year. The totals and the Days table follow. `streamd <key> shards [RANGE]` lists shards as `file:line`. The default range is this month, and `--exclude-mixed` / `--include-mixed` override the config. → #280 - **TUI** (`streamd <key> -i`): a **dashboard with drill-down** in **boxed tiles** like the app, with one page scrolling vertically. Ranges above 42 days are drawn as **one strip per year**, stacked. The **day view is the shard list only** (no axis in the terminal). `t` opens an **overlay with a text field and quick picks**. → #281, #282 - **Mixed days with three or more series** and `mixed = "split"`: the app draws **wedges**. The terminal shows `◩` in the first two colours. - **Range input is parsed** (open question 2): the text syntax is parsed in the core, as you type, by the TUI overlay and the app sheet alike. The examples are labelled. - **Counting** (open question 5): a shard counts by its **outermost** match of a series' query, as an `if` reminder names its shards (R29). So **no** `propagate = false` rule is needed. A shard matching two series counts for both and is reported as a warning at run time, not by validation. - **Days table in the day view** (open question 3): the app keeps it, as in the screenshots. The TUI day view has no table. - **Timeline overlap** (open question 4): out of scope; follow-up #314. - **All shards paging** (open question 6): paged from the core (`offset` / `limit`). - The default colour palette starts with `#D97A2B`, `#2F5BEA` and has at least four more. The colour belongs to the series' position in the config. ## Dependencies A solid arrow means "must be merged first". A dotted arrow is a link that works as a placeholder until the other story is merged. ```mermaid flowchart TD T278["#278 Config: the activity type"] T279["#279 Core: computation and ranges"] T280["#280 CLI: streamd key"] T281["#281 TUI: dashboard"] T282["#282 TUI: range overlay"] T283["#283 Bindings"] T284["#284 Android: Activity screen"] T285["#285 Android: range sheet, all shards, stream filter"] T286["#286 Android: command form"] T278 --> T279 T279 --> T280 T279 --> T281 T280 --> T281 T281 --> T282 T279 --> T283 T283 --> T284 T284 --> T285 T283 --> T286 T281 -.->|t| T282 T284 -.->|range label, show all| T285 T284 -.->|Edit command| T286 ``` ## Stories - [x] #278 — PR #287 - [x] #279 — PR #305 - [x] #280 — PR #306 - [x] #281 — PR #308 - [x] #282 — PR #310 - [x] #283 — PR #307 - [x] #284 — PR #309 - [x] #285 — PR #311 - [x] #286 — PR #312 ## Status (20 Oct 2026): done All nine stories are merged into `main`, and CI on `main` (`f9f1b12`) is green. The terminal track (#280 → #281 → #282) and the app track (#283 → #284 → #285/#286) are complete. R36, the README and AGENTS.md describe the feature; #313 updates the last stale line about **Edit command**, which now opens the activity command's own form. Left over, outside this epic: - #314: stack timeline dots that are minutes apart (open question 4). - The Android screens were never checked by hand on an emulator or device (no KVM was available for #284). This manual check is still to be done.
kfickel added
epic
and removed
planned
labels 2026-10-17 08:25:39 +02:00
Author
Collaborator

The spec from the design session (plan/README.md), verbatim. Where it conflicts with the Decisions in the issue body, the decisions win. The screenshots are attached to #282, #284, #285 and #286.


streamd: Activity command — handover

Feature spec for a new command type, activity, in streamd and its planned Android app. It shows on which days shards matching one or more queries were written, as a GitHub-style activity grid, a month calendar or a single-day timeline, with day statistics.

Everything here comes from a design session. The screens in screenshots/ are rendered from the interactive prototype. The activity type does not exist in streamd yet. All config keys below are a proposal agreed in that session.

Typical uses:

  • Sport: on which days did I run, climb, or both?
  • Work location: how many days was I in the home office, how many only in the office? This is the main reason for "Exclude mixed days".

1. Context (assumptions from earlier in the session)

  • streamd is a Rust CLI (kfickel/streamd) over time-stamped Markdown files (YYYYMMDD-HHMMSS [markers].md). Configuration lives in .streamd.toml: dimensions, tags, file types and typed commands (state, timesheet, file, reminder, alias).
  • The Android app embeds the Rust core via UniFFI. Kotlin does file I/O (Storage Access Framework) and Git (JGit), and passes file contents to the core.
  • The app builds its navigation from the configured commands. Every command appears under "All" and can be pinned to the bottom bar (two pins). activity becomes one more command type there.

2. Concepts

Term Meaning
Series A named, coloured query. A shard belongs to a series if it matches the series' query.
Shard event A matching shard is a point in time, not a span. Its timestamp is the note's file-name timestamp. The global timezone decides which calendar day it belongs to.
Day classification Each tracked day is one of: single-series (only one series matched), mixed (two or more series matched) or none (no series matched).
Tracked days Days in the selected range from start (or the first matching shard) up to today. Future days and days before start are not counted.
Count Number of matching shards per series per day. It drives brightness when intensity = "count".

3. Configuration (.streamd.toml)

Only the general form is in scope. A "short form" (dimension + values) was discussed and dropped.

[commands.sport]
type = "activity"
description = "Training days"
intensity = "count"        # "count" (default) or "fixed"
mixed = "split"            # "split" (default), "first" or "most"
exclude_mixed = false      # default for the ⋮ toggle
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" },
]

Queries should support the same syntax as for example the day commands, multiple fields should be possible to be checked.

Home-office example:

[commands.office]
type = "activity"
description = "Where I worked"
intensity = "fixed"
exclude_mixed = true

[[commands.office.series]]
name = "Home office"
query = { work_location = "home" }

[[commands.office.series]]
name = "Office"
query = [{ work_location = "office" }, { work_location = "client" }]

Keys

Key Type Default Meaning
type string — Must be "activity".
description string — Help text, same as other command types.
intensity "count" | "fixed" "count" count: brightness steps at 1, 2, 3, 4+ shards per day. fixed: every matching day gets full colour.
mixed "split" | "first" | "most" "split" How a mixed day is drawn. split: diagonal halves, series 1 upper-left, series 2 lower-right. first: colour of the first matching series in list order. most: the series with the most shards that day wins; a tie goes to the first in list order.
exclude_mixed bool false Default for the statistics mode (§5.8). It can be toggled per session in the ⋮ menu.
start bare TOML date first matching shard Days before it are "not tracked", not "none".
series array of tables — At least one. List order matters for split and first, and for most ties.
series.name string — Display name. It is the only series-specific wording in the UI.
series.color hex string fixed default palette, in order Colour follows the series, never its position in a filtered list.
series.query table, or list of tables — Each table is dimension = value pairs (all must match), same syntax as expect in reminder rules. A list means OR. The key is deliberately named query, not expect.

Decisions:

  • There is no levels key. Brightness steps are fixed at 1, 2, 3 and 4+.
  • Brightness alphas used in the prototype: 32 %, 55 %, 78 % and 100 % of the series colour over white.

Validation (proposed)

  • Every dimension used in a query must exist.
  • Queried dimensions should not propagate (propagate = false), like the rule for state commands. Otherwise sub-headings would count as extra events.
  • Warn if a single shard matches two series. Mixed days should come from separate shards.
  • color must be #RRGGBB. Default colours in the prototype are #D97A2B (orange) and #2F5BEA (blue); this pair passes colour-blindness separation checks.

4. Computation (Rust core)

For a range [from, to], clamped to [start, today]:

  1. Collect matching shards. For each, store its series index, timestamp and local day. A shard can match several series (warning, see above).
  2. For each tracked day, compute count[series] and classify it as single, mixed or none.
  3. Statistics:
    • shards = total matching shards; shards_per_series.
    • tracked_days, active_days = days with at least one match.
    • Per week = shards / (tracked_days / 7), shown with one decimal.
    • Day counts in both modes:
      • Inclusive (exclude_mixed = false): series_days[i] = days where series i matched, mixed days included; mixed_days; none_days.
      • Exclusive (exclude_mixed = true): series_only_days[i] = days where only series i matched; mixed_days; none_days.
    • Percentages are relative to tracked_days. Inclusive percentages may sum to more than 100 %.
  4. The day colour for rendering follows mixed and intensity (§3).
  5. Shard list: newest first, by day, then by time descending.

Suggested core API (UniFFI): activity(command, from, to, exclude_mixed) -> ActivityResult { days: [DayCell], stats, shards_page }, with paging for the shard list.

5. App screens

The screenshots show the flow from coarse to fine. Sample data is generated: Run and Climbing, Jan 2025 – Oct 2026, with "today" = Sat 17 Oct 2026.

5.1 Header

  • Back arrow: only when drilled in (§5.4).
  • Title: the command name, plus a small monospaced subtitle streamd <key> · <view>.
  • Time range pill, top right: ‹ label ›.
    • The label is short: All time, 2026, Oct 2026, 16 Oct 2026, 3–17 Oct.
    • Tapping the label opens the time range sheet (§5.10).
    • The arrows step by the current unit (day, month, year, or the custom range's length). They are disabled for All, and when the next step would lie fully in the future or before the data.
  • ⋮ menu (§5.9).

5.2 View by range length

Range View Screenshot
1 day Day timeline 03-day-timeline.png
2 – 42 days (≤ 6 weeks) Month calendar 02-month-calendar.png
> 42 days, and All Year grid 01-all-time-year-grid.png

5.3 Year grid

  • GitHub-style layout: one column per week, Monday at the top, 10 px cells, 3 px gap.
  • One horizontal strip that scrolls left–right. It does not wrap into rows, and opens scrolled to the newest week.
  • Weekday labels (Mo, We, Fr, Su) stay fixed on the left while the weeks scroll.
  • Month labels run above; January and the first label also show the year ("Jan 2026").
  • Cells:
    • Series colour at its count alpha; split cells use a 135° hard-stop gradient.
    • None days: neutral #ECEDE8.
    • Future days: transparent with a 1 px outline.
    • Today: a dark ring.
  • A legend sits under the chart: ramps per series, plus a "Mixed" swatch. The prototype shows no hint texts about tapping or swiping (removed on request).

5.4 Drill-down and back

  • Tapping a day in the year grid opens that day's month (month calendar).
  • Tapping a day in the month calendar opens that day (day timeline).
  • Back walks one level up (day → month → previous range).
  • Choosing a range in the sheet starts a new root and clears the back stack.

5.5 Month calendar

  • 7 columns, Monday first, 44 px cells (touch targets).
  • Leading and trailing days outside the range are shown muted, not tappable. Day 1 of a month reads "1 Nov".
  • Same colouring rules as the grid. Future days have dashed borders; today has a solid border.

5.6 Day timeline (events, not durations)

  • One lane per series.
  • A 0–24 h axis with labels at 00, 06, 12, 18 and 24, and light gridlines.
  • Each shard is a dot at its timestamp, with its time (06:45) as a label. In the same lane, labels of dots within 150 minutes of each other alternate above and below the line.
  • Earlier iterations drew bars with durations. That was rejected: shards are events.
  • Known limitation: dots minutes apart still overlap. Stacking them vertically is a possible follow-up.

5.7 Totals tiles (all views)

  • Range views: [range label: shards] · [Active days: N of M days] · [Per week: x.x shards].
  • Day view: [date: shards] · [series 1: count, "at 06:45"] · [series 2: count, "at 18:30"].
  • The first tile is dark; numbers are large monospaced text.

5.8 Lists and the Days table

  • Latest shards: at most 5, newest first. When there are more than 5, in every view including the day view, a "Show all N shards" button opens the All shards screen (06-all-shards.png):
    • Header: count and range, plus a back arrow.
    • Filter chips: All and one per series, each with a count.
    • The list is grouped by day, newest first. Each row has time, title, detail and series tag, and opens the note.
  • Days table (02-…png inclusive, 05-…png exclusive):
    • Inclusive: <Series> "incl. N mixed" · Mixed "also counted above" · None "no series matched".
    • Exclusive: <Series> only "no other series that day" · Mixed "several series that day" · None.
    • Each row shows the day count and a % of tracked days. Under "Shards", the shard count per series follows.
    • A "… · change" link under the heading opens the ⋮ menu.
    • Wording must be generic. The tool doesn't know the domain, so the UI never says "rest", "running" and so on. The only domain words are the series names from the config.

5.9 ⋮ menu (05-options-menu-exclude-mixed.png)

  • Exclude mixed days: a switch with an explanation. It overrides exclude_mixed for the session.
  • Edit command: opens the command editor (§5.11).
  • Show shards in stream: opens the stream filtered to the matching shards.

5.10 Time range sheet (04-time-range-sheet.png)

A bottom sheet, simplified from a reference design.

  • Header: "Time range" + Apply. Tapping the scrim cancels.
  • Range input: shows the pending range in text syntax (§6), e.g. 2026-10-03..2026-10-17. Below it: the resolved dates 2026-10-03 → 2026-10-17 and "15 days · to today".
  • Tabs: All · Days · Month · Year. All comes first. There is no Quarter and no separate Range tab.
    • All: the tracked span ("Since 6 Jan 2025 · N days") and a "Select all time" button.
    • Days: a month calendar with ‹ › navigation. Tap one day = single day; tap a second day = range (either order). The range shows as a tinted band with dark end caps; future days are disabled. A hint below explains the two taps.
    • Month: year navigation and 12 month buttons; future months are disabled.
    • Year: buttons for years with data.
  • Quick picks, as 44 px chips: Today · Yesterday · Last 7 days · Last 30 days · This month · Last month · This year · Last year · All time.
  • Granularity is whole days only.
  • Apply sets the range and clears the back stack.

5.11 Edit command, activity type (07-edit-command-activity.png)

  • Description field.
  • Series cards, numbered:
    • Colour swatch (tap to change), name, and a delete button.
    • Under them: query, "a shard matches if any line fits". Each condition is a chip (sport = climbing, removable); chips are joined by "or", with a "+ or" button.
    • A "+ Add" button for new series. A note says order matters for mixed days.
  • Colouring:
    • Brightness: By count / Fixed.
    • Mixed days: Split / First / Most, each with a one-line explanation.
    • A preview row of six example days (Run 1, Run 3, Climbing 2, Run 1 + Climbing 2, Run 2 + Climbing 1, none) that reflects the current choices.
  • Statistics:
    • "Exclude mixed days" switch (exclude_mixed).
    • "Tracking starts" date field (start).
  • Save writes .streamd.toml; the change goes through the app's Git "Changes" flow.
  • Not on this screen (removed on request): the TOML preview and "Pin to bottom bar". Pinning stays in the All commands list.

5.12 Integration

  • All commands: a new section "Activity" with a mini-grid icon, a one-line summary and a pin button.
  • Config screen: lists the command with type badge activity; tapping a row opens §5.11.

6. Range text syntax (shared by the app input and the CLI)

all                      everything tracked
2026                     a year
2026-10                  a month
2026-10-16               a day
2026-10-03..2026-10-17   inclusive day range
last 30 days             rolling window ending today

CLI proposal:

streamd sport                        # current month, table output
streamd sport 2026
streamd sport 2026-10-03..2026-10-17
streamd sport all --exclude-mixed    # e.g. "only home office" days

7. Open questions

  1. More than two series on one day with mixed = "split". The prototype only handles two halves. Options: thirds or wedges, or fall back to most.
  2. Example syntax line in the range sheet. The grey monospaced line lists accepted formats. It is unlabelled, and the prototype doesn't parse typed input. Decide: parse typed input and keep tappable, labelled examples, or remove the line.
  3. Days table in the day view. It is trivial there (0 % or 100 %). Keep it for consistency or hide it?
  4. Day-timeline overlap when events are minutes apart: stack the dots?
  5. Validation proposals: the non-propagating dimension rule and the multi-series match warning still need confirming against the core's semantics.
  6. All shards screen for large ranges: paging or lazy loading.

8. Screenshots

File Shows
01-all-time-year-grid.png Range "All time": scrolling year grid, tiles, latest 5 shards + "Show all", Days table (inclusive). → #284
02-month-calendar.png Range "Oct 2026": month calendar with split mixed days, future days dashed, today outlined. → #284
03-day-timeline.png Range "16 Oct 2026": event timeline with dots per series lane. → #284
04-time-range-sheet.png Time range sheet, Days tab, range 3–17 Oct selected, quick picks. → #285, #282
05-options-menu-exclude-mixed.png ⋮ menu open, "Exclude mixed days" on; the table switched to "Run only / Climbing only / Mixed / None". → #284
06-all-shards.png "Show all shards" screen for Oct 2026, grouped by day, with filter chips. → #285
07-edit-command-activity.png Command editor for an activity command. → #286

Screenshots are 2× (780 px wide) and rendered with fallback fonts. The prototype uses Instrument Sans for UI text and JetBrains Mono for timestamps, keys and numbers.

**The spec from the design session (`plan/README.md`), verbatim.** Where it conflicts with the *Decisions* in the issue body, the decisions win. The screenshots are attached to #282, #284, #285 and #286. --- # streamd: Activity command — handover Feature spec for a new command type, `activity`, in streamd and its planned Android app. It shows on which days shards matching one or more queries were written, as a GitHub-style activity grid, a month calendar or a single-day timeline, with day statistics. Everything here comes from a design session. The screens in `screenshots/` are rendered from the interactive prototype. **The `activity` type does not exist in streamd yet.** All config keys below are a proposal agreed in that session. Typical uses: - Sport: on which days did I run, climb, or both? - Work location: how many days was I in the home office, how many only in the office? This is the main reason for "Exclude mixed days". --- ## 1. Context (assumptions from earlier in the session) - streamd is a Rust CLI (`kfickel/streamd`) over time-stamped Markdown files (`YYYYMMDD-HHMMSS [markers].md`). Configuration lives in `.streamd.toml`: dimensions, tags, file types and typed commands (`state`, `timesheet`, `file`, `reminder`, `alias`). - The Android app embeds the Rust core via UniFFI. Kotlin does file I/O (Storage Access Framework) and Git (JGit), and passes file contents to the core. - The app builds its navigation from the configured commands. Every command appears under "All" and can be pinned to the bottom bar (two pins). `activity` becomes one more command type there. ## 2. Concepts | Term | Meaning | |---|---| | **Series** | A named, coloured query. A shard belongs to a series if it matches the series' `query`. | | **Shard event** | A matching shard is a point in time, **not a span**. Its timestamp is the note's file-name timestamp. The global `timezone` decides which calendar day it belongs to. | | **Day classification** | Each tracked day is one of: *single-series* (only one series matched), *mixed* (two or more series matched) or *none* (no series matched). | | **Tracked days** | Days in the selected range from `start` (or the first matching shard) up to today. Future days and days before `start` are not counted. | | **Count** | Number of matching shards per series per day. It drives brightness when `intensity = "count"`. | ## 3. Configuration (`.streamd.toml`) Only the **general form** is in scope. A "short form" (`dimension` + `values`) was discussed and dropped. ```toml [commands.sport] type = "activity" description = "Training days" intensity = "count" # "count" (default) or "fixed" mixed = "split" # "split" (default), "first" or "most" exclude_mixed = false # default for the ⋮ toggle 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" }, ] ``` Queries should support the same syntax as for example the day commands, multiple fields should be possible to be checked. Home-office example: ```toml [commands.office] type = "activity" description = "Where I worked" intensity = "fixed" exclude_mixed = true [[commands.office.series]] name = "Home office" query = { work_location = "home" } [[commands.office.series]] name = "Office" query = [{ work_location = "office" }, { work_location = "client" }] ``` ### Keys | Key | Type | Default | Meaning | |---|---|---|---| | `type` | string | — | Must be `"activity"`. | | `description` | string | — | Help text, same as other command types. | | `intensity` | `"count"` \| `"fixed"` | `"count"` | `count`: brightness steps at 1, 2, 3, 4+ shards per day. `fixed`: every matching day gets full colour. | | `mixed` | `"split"` \| `"first"` \| `"most"` | `"split"` | How a mixed day is **drawn**. `split`: diagonal halves, series 1 upper-left, series 2 lower-right. `first`: colour of the first matching series in list order. `most`: the series with the most shards that day wins; a tie goes to the first in list order. | | `exclude_mixed` | bool | `false` | Default for the statistics mode (§5.8). It can be toggled per session in the ⋮ menu. | | `start` | bare TOML date | first matching shard | Days before it are "not tracked", not "none". | | `series` | array of tables | — | At least one. **List order matters** for `split` and `first`, and for `most` ties. | | `series.name` | string | — | Display name. It is the only series-specific wording in the UI. | | `series.color` | hex string | fixed default palette, in order | Colour follows the series, never its position in a filtered list. | | `series.query` | table, or list of tables | — | Each table is `dimension = value` pairs (all must match), same syntax as `expect` in reminder rules. A **list means OR**. The key is deliberately named `query`, not `expect`. | Decisions: - There is **no `levels` key**. Brightness steps are fixed at 1, 2, 3 and 4+. - Brightness alphas used in the prototype: 32 %, 55 %, 78 % and 100 % of the series colour over white. ### Validation (proposed) - Every dimension used in a `query` must exist. - Queried dimensions should not propagate (`propagate = false`), like the rule for state commands. Otherwise sub-headings would count as extra events. - Warn if a single shard matches two series. Mixed days should come from separate shards. - `color` must be `#RRGGBB`. Default colours in the prototype are `#D97A2B` (orange) and `#2F5BEA` (blue); this pair passes colour-blindness separation checks. ## 4. Computation (Rust core) For a range `[from, to]`, clamped to `[start, today]`: 1. Collect matching shards. For each, store its series index, timestamp and local day. A shard can match several series (warning, see above). 2. For each tracked day, compute `count[series]` and classify it as single, mixed or none. 3. Statistics: - `shards` = total matching shards; `shards_per_series`. - `tracked_days`, `active_days` = days with at least one match. - Per week = `shards / (tracked_days / 7)`, shown with one decimal. - Day counts in both modes: - **Inclusive** (`exclude_mixed = false`): `series_days[i]` = days where series *i* matched, mixed days included; `mixed_days`; `none_days`. - **Exclusive** (`exclude_mixed = true`): `series_only_days[i]` = days where *only* series *i* matched; `mixed_days`; `none_days`. - Percentages are relative to `tracked_days`. Inclusive percentages may sum to more than 100 %. 4. The day colour for rendering follows `mixed` and `intensity` (§3). 5. Shard list: newest first, by day, then by time descending. Suggested core API (UniFFI): `activity(command, from, to, exclude_mixed) -> ActivityResult { days: [DayCell], stats, shards_page }`, with paging for the shard list. ## 5. App screens The screenshots show the flow from coarse to fine. Sample data is generated: Run and Climbing, Jan 2025 – Oct 2026, with "today" = Sat 17 Oct 2026. ### 5.1 Header - **Back arrow:** only when drilled in (§5.4). - **Title:** the command name, plus a small monospaced subtitle `streamd <key> · <view>`. - **Time range pill**, top right: `‹ label ›`. - The label is short: `All time`, `2026`, `Oct 2026`, `16 Oct 2026`, `3–17 Oct`. - Tapping the label opens the time range sheet (§5.10). - The arrows step by the current unit (day, month, year, or the custom range's length). They are disabled for All, and when the next step would lie fully in the future or before the data. - **⋮ menu** (§5.9). ### 5.2 View by range length | Range | View | Screenshot | |---|---|---| | 1 day | Day timeline | `03-day-timeline.png` | | 2 – 42 days (≤ 6 weeks) | Month calendar | `02-month-calendar.png` | | > 42 days, and All | Year grid | `01-all-time-year-grid.png` | ### 5.3 Year grid - GitHub-style layout: one column per week, Monday at the top, 10 px cells, 3 px gap. - **One horizontal strip that scrolls left–right.** It does not wrap into rows, and **opens scrolled to the newest week**. - Weekday labels (Mo, We, Fr, Su) stay fixed on the left while the weeks scroll. - Month labels run above; January and the first label also show the year ("Jan 2026"). - Cells: - Series colour at its count alpha; split cells use a 135° hard-stop gradient. - None days: neutral `#ECEDE8`. - Future days: transparent with a 1 px outline. - Today: a dark ring. - A legend sits under the chart: ramps per series, plus a "Mixed" swatch. The prototype shows no hint texts about tapping or swiping (removed on request). ### 5.4 Drill-down and back - Tapping a day in the **year grid** opens **that day's month** (month calendar). - Tapping a day in the **month calendar** opens **that day** (day timeline). - **Back** walks one level up (day → month → previous range). - Choosing a range in the sheet starts a new root and clears the back stack. ### 5.5 Month calendar - 7 columns, Monday first, 44 px cells (touch targets). - Leading and trailing days outside the range are shown muted, not tappable. Day 1 of a month reads "1 Nov". - Same colouring rules as the grid. Future days have dashed borders; today has a solid border. ### 5.6 Day timeline (events, not durations) - One lane per series. - A 0–24 h axis with labels at 00, 06, 12, 18 and 24, and light gridlines. - **Each shard is a dot** at its timestamp, with its time (`06:45`) as a label. In the same lane, labels of dots within 150 minutes of each other alternate above and below the line. - Earlier iterations drew bars with durations. That was rejected: **shards are events.** - Known limitation: dots minutes apart still overlap. Stacking them vertically is a possible follow-up. ### 5.7 Totals tiles (all views) - **Range views:** [range label: **shards**] · [Active days: N **of M days**] · [Per week: x.x shards]. - **Day view:** [date: **shards**] · [series 1: count, "at 06:45"] · [series 2: count, "at 18:30"]. - The first tile is dark; numbers are large monospaced text. ### 5.8 Lists and the Days table - **Latest shards:** at most **5**, newest first. When there are more than 5, in **every** view including the day view, a "Show all N shards" button opens the **All shards screen** (`06-all-shards.png`): - Header: count and range, plus a back arrow. - Filter chips: All and one per series, each with a count. - The list is grouped by day, newest first. Each row has time, title, detail and series tag, and opens the note. - **Days table** (`02-…png` inclusive, `05-…png` exclusive): - Inclusive: `<Series>` "incl. N mixed" · `Mixed` "also counted above" · `None` "no series matched". - Exclusive: `<Series> only` "no other series that day" · `Mixed` "several series that day" · `None`. - Each row shows the day count and a % of tracked days. Under "Shards", the shard count per series follows. - A "… · change" link under the heading opens the ⋮ menu. - **Wording must be generic.** The tool doesn't know the domain, so the UI never says "rest", "running" and so on. The only domain words are the series names from the config. ### 5.9 ⋮ menu (`05-options-menu-exclude-mixed.png`) - **Exclude mixed days:** a switch with an explanation. It overrides `exclude_mixed` for the session. - **Edit command:** opens the command editor (§5.11). - **Show shards in stream:** opens the stream filtered to the matching shards. ### 5.10 Time range sheet (`04-time-range-sheet.png`) A bottom sheet, simplified from a reference design. - **Header:** "Time range" + **Apply**. Tapping the scrim cancels. - **Range input:** shows the pending range in text syntax (§6), e.g. `2026-10-03..2026-10-17`. Below it: the resolved dates `2026-10-03 → 2026-10-17` and "15 days · to today". - **Tabs: All · Days · Month · Year.** All comes first. There is no Quarter and no separate Range tab. - *All:* the tracked span ("Since 6 Jan 2025 · N days") and a "Select all time" button. - *Days:* a month calendar with ‹ › navigation. **Tap one day = single day; tap a second day = range** (either order). The range shows as a tinted band with dark end caps; future days are disabled. A hint below explains the two taps. - *Month:* year navigation and 12 month buttons; future months are disabled. - *Year:* buttons for years with data. - **Quick picks**, as 44 px chips: Today · Yesterday · Last 7 days · Last 30 days · This month · Last month · This year · Last year · All time. - **Granularity is whole days only.** - **Apply** sets the range and clears the back stack. ### 5.11 Edit command, activity type (`07-edit-command-activity.png`) - **Description** field. - **Series cards**, numbered: - Colour swatch (tap to change), name, and a delete button. - Under them: `query`, "a shard matches if any line fits". Each condition is a chip (`sport = climbing`, removable); chips are joined by "or", with a "+ or" button. - A "+ Add" button for new series. A note says order matters for mixed days. - **Colouring:** - Brightness: *By count* / *Fixed*. - Mixed days: *Split* / *First* / *Most*, each with a one-line explanation. - A preview row of six example days (Run 1, Run 3, Climbing 2, Run 1 + Climbing 2, Run 2 + Climbing 1, none) that reflects the current choices. - **Statistics:** - "Exclude mixed days" switch (`exclude_mixed`). - "Tracking starts" date field (`start`). - Save writes `.streamd.toml`; the change goes through the app's Git "Changes" flow. - **Not on this screen** (removed on request): the TOML preview and "Pin to bottom bar". Pinning stays in the All commands list. ### 5.12 Integration - **All commands:** a new section "Activity" with a mini-grid icon, a one-line summary and a pin button. - **Config screen:** lists the command with type badge `activity`; tapping a row opens §5.11. ## 6. Range text syntax (shared by the app input and the CLI) ``` all everything tracked 2026 a year 2026-10 a month 2026-10-16 a day 2026-10-03..2026-10-17 inclusive day range last 30 days rolling window ending today ``` CLI proposal: ```sh streamd sport # current month, table output streamd sport 2026 streamd sport 2026-10-03..2026-10-17 streamd sport all --exclude-mixed # e.g. "only home office" days ``` ## 7. Open questions 1. **More than two series on one day with `mixed = "split"`.** The prototype only handles two halves. Options: thirds or wedges, or fall back to `most`. 2. **Example syntax line in the range sheet.** The grey monospaced line lists accepted formats. It is unlabelled, and the prototype doesn't parse typed input. Decide: parse typed input and keep tappable, labelled examples, or remove the line. 3. **Days table in the day view.** It is trivial there (0 % or 100 %). Keep it for consistency or hide it? 4. **Day-timeline overlap** when events are minutes apart: stack the dots? 5. **Validation proposals:** the non-propagating dimension rule and the multi-series match warning still need confirming against the core's semantics. 6. **All shards screen for large ranges:** paging or lazy loading. ## 8. Screenshots | File | Shows | |---|---| | `01-all-time-year-grid.png` | Range "All time": scrolling year grid, tiles, latest 5 shards + "Show all", Days table (inclusive). → #284 | | `02-month-calendar.png` | Range "Oct 2026": month calendar with split mixed days, future days dashed, today outlined. → #284 | | `03-day-timeline.png` | Range "16 Oct 2026": event timeline with dots per series lane. → #284 | | `04-time-range-sheet.png` | Time range sheet, Days tab, range 3–17 Oct selected, quick picks. → #285, #282 | | `05-options-menu-exclude-mixed.png` | ⋮ menu open, "Exclude mixed days" on; the table switched to "Run only / Climbing only / Mixed / None". → #284 | | `06-all-shards.png` | "Show all shards" screen for Oct 2026, grouped by day, with filter chips. → #285 | | `07-edit-command-activity.png` | Command editor for an `activity` command. → #286 | Screenshots are 2× (780 px wide) and rendered with fallback fonts. The prototype uses Instrument Sans for UI text and JetBrains Mono for timestamps, keys and numbers.
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#277
No description provided.