Commit 7cd6560
feat(stream): add an activity heatmap that drives the date filter
The date range added with the search filters was hidden behind a dropdown, so
choosing one meant guessing which period had anything in it. A "Heatmap" entry
in the app navigation now opens an overview drawing a year of activity as a
calendar grid, and picking cells chooses a period: click a day, or shift-click
a second one for a span. Clicking the only selected day deselects it, so the
same cell toggles rather than trapping the reader in a one day view. "Show
these activities" then opens the stream restricted to that period, carrying
the range as query parameters because the stream already restores its filters
from the URL, which also makes the result linkable.
It is a view of its own rather than a band above the feed: a chart that is
permanently in the way of the list people came to read is a distraction, and
this one answers a different question from the stream it feeds.
Counts come from a new endpoint,
`GET /api/v2/activity/{filter}/histogram`, returning activities per calendar
day.
It shares its WHERE building with the stream query. That refactor is the
point rather than tidiness: a histogram whose columns count rows the feed
below it does not list is worse than no histogram, and the filter, type, app
and favourites conditions are far too involved to keep correct in two
places. The endpoint honours the active search and account for the same
reason, but deliberately takes no from/to, because the histogram is the
control a range is picked *with* and has to keep showing the days outside
the selection.
Days are bucketed in the account's timezone rather than UTC, so an activity
at 01:00 local time belongs to that local day. A real DateTimeZone is passed
rather than a fixed offset, which is what keeps a window spanning a DST
change correct. Bucketing runs in PHP: grouping by day needs integer
division or a modulo, neither expressible through IQueryBuilder, and a raw
expression would need four dialects for the databases this app tests
against. The query stays cheap regardless — one column, one affecteduser, a
timestamp range, which is the existing activity_user_time index.
Hitting the row guard is reported as `partial_before` instead of hidden, so
the affected days are marked rather than drawn as measured but understated
values.
On the colour: the ramp is the theme's own accent mixed into the surface at
45/63/81/100% in OKLab, so it follows a custom primary colour, and because
--color-primary-element is contrast-adjusted per theme by the server the
scale runs light-to-dark on white and dark-to-light on the dark surface —
the anchor flip a sequential scale needs, without a second palette. The four
steps were checked against both Nextcloud surfaces: monotone OKLCH
lightness, adjacent gaps >= 0.09, single hue. The lightest step was raised
until it clears 2:1 against the surface, because the palest tint is a day
with *one* activity and had been indistinguishable from an empty one; "no
activity" is a neutral instead, which reads as nothing rather than as a
small value.
Days with no activity are omitted from the payload rather than sent as
zeroes, so its size tracks real activity instead of the window length, and
the client fills the gaps.
The grid is a real table with a caption and row headers, every cell is a
button carrying the date and exact count as its accessible name, and focus
shows the same readout as hover — so no value depends on either hovering or
reading a colour. One cell is exposed to Tab at a time, with arrow keys
moving a day vertically and a week horizontally, and Home/End jumping to the
ends of the window. Selection is drawn as a ring, never a fill, since the
fill is spoken for by the value and repainting it would misstate the count.
Rendered in both themes and inspected before shipping, which is what caught
the window being too short: at 26 weeks the grid filled under half the column
and left the legend stranded, so it defaults to 52.
Signed-off-by: Frank Karlitschek <karlitschek@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent ef2519a commit 7cd6560
43 files changed
Lines changed: 2330 additions & 62 deletions
File tree
- appinfo
- docs
- js
- lib
- Controller
- src
- __tests__
- components
- utils
- views
- tests
- Controller
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
15 | 18 | | |
16 | 19 | | |
17 | 20 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
203 | 203 | | |
204 | 204 | | |
205 | 205 | | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 13 additions & 13 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
This file was deleted.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
File renamed without changes.
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 commit comments