Skip to content

feat(scripts): automated screenshots with realistic demo data - #1500

Merged
ErikBjare merged 4 commits into
masterfrom
feat/screenshots
Oct 6, 2026
Merged

ErikBjare merged 4 commits into
masterfrom
feat/screenshots

Conversation

@ErikBjare

Copy link
Copy Markdown
Member

Adds scripts/screenshots.py (uv script, Playwright) and make screenshots: one command to take fresh screenshots of the current web UI for the website, README, docs and store listings.

  • Builds (--build) or reuses the web UI and aw-server-rust from the submodule pins, then starts an isolated server: free port (refuses 5600/5666), temp db/HOME/XDG dirs, removed on exit, and refuses to seed a server that already has buckets.
  • Seeds a deterministic year of realistic demo data across three devices (macOS work laptop, Linux home desktop, Android phone; the latter two as synced buckets), matching the default category rules, with a calmer demo palette.
  • Hides nudges, polls and dev-only UI; blocks external requests; masks the host name.
  • Captures 13 views in light and dark (1270×760 @2x by default, --size, --full-page, --only, --keep-running to browse and pick extra shots). About 70 s per run once built.

Output: dist/screenshots/{light,dark}/NN-<view>.png. Trends and Work report are behind --only until ActivityWatch/aw-webui#1058 is fixed.

Used for the v0.14.0 screenshots on activitywatch.net (ActivityWatch/activitywatch.github.io#88).

… data

Starts a throwaway aw-server-rust (free port, temp db and HOME), seeds a
deterministic year of multi-device demo data (macOS laptop, Linux desktop,
Android phone synced via aw-sync), configures settings for clean shots,
and captures the main web UI views in light and dark with Playwright.

Run with `make screenshots` or `uv run scripts/screenshots.py`.
…report behind --only

Demo categories get muted colors derived per theme so bar labels stay
readable (#333 on light, white on dark, ~5:1 contrast). Trends and Work
report have known webui bugs, so they are only captured on request.
@ErikBjare

Copy link
Copy Markdown
Member Author

@greptileai review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

ErikBjare added a commit to ActivityWatch/activitywatch.github.io that referenced this pull request Oct 6, 2026
…e and schema.org image

Generated with scripts/screenshots.py (ActivityWatch/activitywatch#1500): light and
dark for 13 views. Latest section shows light with a dark-mode link; Activity and
Timeline sliders gain a v0.14.0 stop. Homepage swaps the v0.9.3/v0.8.0 images.
@greptile-apps

greptile-apps Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Low risk] Adds a marketing screenshot generation script.

The PR appears safe to merge, with a non-blocking concern about overlapping lunch and work events in the demo data.

Findings

  1. P2 Lunch and work overlap ▶

Summary

Adds make screenshots and scripts/screenshots.py to generate demo activity and capture the web UI in light and dark themes.

  • Uses a temporary server and database rather than the user’s running ActivityWatch.
  • Fixes relative paths, stable screenshot names, and missing lunch events.
  • One non-blocking concern remains: the new lunch fill can overlap work events.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Build or reuse server and web UI"] --> B["Generate demo activity"]
  B --> C["Start temporary server"]
  C --> D["Seed buckets and settings"]
  D --> E["Capture selected views"]
  E --> F["Stop server and remove temporary data"]
Loading

Reviews (2) · Last reviewed commit: "fix(scripts): resolve path args, stable ..."

Comment thread scripts/screenshots.py
parser.add_argument("--full-page", action="store_true", help="capture every view as a full-page (tall) screenshot")
parser.add_argument("--keep-running", action="store_true", help="leave the seeded server up after capturing")
parser.add_argument("--no-capture", action="store_true", help="only start and seed the server (implies --keep-running)")
args = parser.parse_args()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Relative paths break startup

Relative --server-bin and --webpath values pass the file checks in the caller’s directory, but run_server reuses them after changing to a temporary directory. For example, --server-bin aw-server-rust/target/release/aw-server passes the check, then fails to launch with FileNotFoundError. Resolve both paths before building, checking, or starting the server.

Suggested change
args = parser.parse_args()
args = parser.parse_args()
args.server_bin = args.server_bin.resolve()
args.webpath = args.webpath.resolve()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 597fe56: resolve --server-bin/--webpath (and --out) right after parse_args, before build/check/start.

Comment thread scripts/screenshots.py Outdated
page.on("pageerror", lambda exc: console_errors.append(str(exc)))
theme_dir = out_dir / theme
theme_dir.mkdir(parents=True, exist_ok=True)
for i, shot in enumerate(shots, start=1):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Partial refresh leaves old screenshots

--only changes each selected screenshot’s numeric prefix. After a full run writes 07-timeline.png, running --only timeline writes 01-timeline.png and leaves the old image untouched. Anyone using the original filename still gets the stale screenshot. Keep each view’s filename stable before filtering.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 597fe56: each view's NN prefix is fixed by its position in the full list (trends/work-report are 14/15), independent of --only.

Comment thread scripts/screenshots.py Outdated
Comment on lines +669 to +671
for a_s, a_e in active:
pieces = [(a_s, a_e, None)]
for m in meetings + lunch_browse:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Lunch activity is missing

Lunch browsing is marked as active without creating its window or web events. active excludes lunch, but the code only creates lunch events while splitting those remaining intervals. A browse session wholly inside lunch never reaches fill_session, so the demo misses the intended activity. Generate the lunch-browse sessions separately so the activity data matches the not-away periods.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 597fe56: lunch-browse sessions are now filled separately inside the lunch break; verified 0 window events outside not-afk.

…nch-browse sessions

- Resolve --server-bin/--webpath/--out before starting the server in a temp cwd
- File prefixes come from a view's place in the full list, so --only
  overwrites the same files (trends/work-report are 14/15)
- Lunch-time browsing lies inside the lunch break and was never filled;
  generate it separately so window/web events match not-afk periods
@ErikBjare

Copy link
Copy Markdown
Member Author

@greptileai review

Comment thread scripts/screenshots.py
Comment on lines +700 to +701
for lb_s, lb_e in lunch_browse:
fill_session(dev, rng, lb_s, lb_e, laptop_lunch())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Lunch and work overlap

The separate lunch fill can create events that overlap work events. lunch lasts 35–55 minutes, but lunch_browse can end 39 minutes after lunch starts. When browsing runs past lunch, both loops fill the same time, recording two foreground activities at once. This makes the demo data less realistic and can inflate recorded activity time by a few minutes.

Clamp the browse end to lunch[1] before using it for events and not-away periods.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in cdd86d3: the browse end is clamped to lunch[1].

@ErikBjare
ErikBjare merged commit bbcc9b8 into master Oct 6, 2026
19 of 20 checks passed
@ErikBjare
ErikBjare deleted the feat/screenshots branch October 6, 2026 15:47
ErikBjare added a commit to ActivityWatch/activitywatch.github.io that referenced this pull request Oct 6, 2026
#88)

* feat(screenshots): data-driven page with latest shots, per-view version slider, and web-sized images

The page is now generated from _data/screenshots.yml: a Latest section (filled by
ActivityWatch/activitywatch scripts/screenshots.py), a 'How it has evolved' slider
per view across versions, and the community competition gallery with credits.
Displays downscaled WebP copies (36 MB -> 1.5 MB) and links the full PNGs.

* feat(screenshots): add v0.14.0 shots (demo data) to the page, homepage and schema.org image

Generated with scripts/screenshots.py (ActivityWatch/activitywatch#1500): light and
dark for 13 views. Latest section shows light with a dark-mode link; Activity and
Timeline sliders gain a v0.14.0 stop. Homepage swaps the v0.9.3/v0.8.0 images.

* fix(screenshots): announce the selected version on the slider (aria-valuetext)
Comment thread scripts/screenshots.py
def wait_until_loaded(page, timeout_s: float = 60) -> None:
try:
page.wait_for_load_state("networkidle", timeout=timeout_s * 1000)
except Exception:
Comment thread scripts/screenshots.py
page.wait_for_function(WAIT_FOR_LOADED_JS, timeout=timeout_s * 1000, polling=250)
try:
page.wait_for_load_state("networkidle", timeout=timeout_s * 1000)
except Exception:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants