Skip to content

Latest commit

 

History

History
251 lines (186 loc) · 11 KB

File metadata and controls

251 lines (186 loc) · 11 KB

Agent Rules

Mandatory: Test-Driven Development (TDD)

TDD must be used always, no exceptions.

Every feature, bugfix, or change must follow the Red-Green-Refactor cycle:

  1. Red — Write a failing test first that describes the expected behavior
  2. Green — Write the minimum code to make the test pass
  3. Refactor — Clean up the code while keeping tests green

If you are about to write implementation code and no test exists for it yet, STOP and write the test first.

Project Overview

GithubSaver is a Windows screensaver (.scr) that displays GitHub activity as D3.js visualizations. It uses C# .NET 8 with WebView2 to host web content fullscreen.

Architecture

GithubSaver.scr (C# .NET 8 + WebView2)
├── Program.cs              — Entry point, parses /s /c /p args
├── ScreensaverForm.cs      — Fullscreen WebView2, exits on any input
├── ConfigForm.cs           — WebView2 settings dialog (HTML-rendered)
├── PreviewForm.cs          — Tiny WebView2 for Windows preview
├── Services/
│   ├── GitHubDataService.cs — GitHub REST + GraphQL API + JSON caching
│   ├── SettingsManager.cs   — JSON settings at %LOCALAPPDATA%\GithubSaver\
│   └── CredentialStore.cs   — Windows Credential Manager for GitHub PAT
└── wwwroot/
    ├── shared/             — D3.js, common CSS, data-loader.js
    ├── modules/            — Screensaver visualization modules (HTML/JS/D3)
    └── config/             — Settings UI (HTML/JS)

Screensaver Modules

Each module is a self-contained index.html in wwwroot/modules/{name}/:

Module Description
heatmap-multidata 4 stacked grids: Commits (green), PRs (blue), Issues (purple), Reviews (orange)
heatmap-years 7 years of contribution grids stacked vertically
rain-days 365 columns, chronological year sweep
rain-repos Each column = a repo, rain intensity = activity level
heatmap-rain-weeks Matrix-style contribution rain with real week data highlights

Data Flow

.scr /s starts → SettingsManager loads config
→ GitHubDataService fetches data (or reads cache)
→ Writes JSON to wwwroot/data/
→ WebView2 loads module's index.html
→ data-loader.js provides data to D3 visualization
→ Any user input → screensaver exits

Coding Standards

C# (.NET 8)

  • Use file-scoped namespaces (namespace GithubSaver;)
  • Nullable reference types enabled — handle nulls explicitly
  • Async/await for all I/O operations
  • Never throw exceptions in the screensaver runtime path — catch and degrade gracefully
  • Use System.Text.Json for serialization (not Newtonsoft)

JavaScript / HTML (Screensaver Modules)

  • All module JS and CSS inline in the single index.html file
  • Use requestAnimationFrame for all animations — target 60fps
  • Use Canvas for particle-heavy effects, SVG for structured visualizations
  • Pure black background (#000) — OLED burn-in prevention
  • No static text — all labels must fade in/out and drift position
  • Modules must work with demo data (no GitHub connection required)
  • Reference shared assets via relative paths: ../../shared/d3.v7.min.js

Screensaver Requirements

  • Must exit immediately on any user input (mouse move >5px, click, key, wheel, touch)
  • No static UI elements that could cause OLED burn-in
  • All animations must be continuous — no frozen frames
  • Must work offline (cached data or demo data fallback)
  • Must never show an error dialog or crash visibly

Testing

C# Tests

  • Use xUnit as the test framework
  • Test project at tests/GithubSaver.Tests/
  • Mock external dependencies (HTTP, file system, Credential Manager)
  • Key areas to test:
    • SettingsManager: Load/Save round-trip, missing file handling, corrupt JSON
    • GitHubDataService: API response parsing, cache TTL, offline fallback
    • Program.cs: Command-line argument parsing (/s, /c, /p)
    • Settings: Default values, serialization

JavaScript Tests

  • Use a browser-based test runner or Node.js with jsdom
  • Key areas to test:
    • data-loader.js: Demo data generation, fetch fallback, postMessage handling
    • Module data consumption: Verify each module handles missing/partial data

Versioning

This project uses Semantic Versioning:

  • MAJOR.MINOR.PATCH (e.g. 1.2.3)
  • Pre-release: append -beta, -alpha, etc. (e.g. 0.1.0-beta)
  • MAJOR: Breaking changes (settings format, module API)
  • MINOR: New features (new modules, new settings)
  • PATCH: Bug fixes, performance improvements

Currently in beta (0.x) — the API and settings format may change.

Tag releases with v prefix: v0.1.0-beta, v1.0.0.

Important: Always bump the version in GithubSaver.csproj AND installer/Package.wxs before building a new MSI. Windows Installer uses the version to detect upgrades — same version = skipped.

Build Outputs

  • dist/ — Development builds for local testing. Rebuilt frequently during development. Use for quick iteration and debugging. Gitignored.
  • releases/v{version}+{build}/ — Release builds (e.g. releases/v0.2.4-beta+3/). Each build gets its own folder. Gitignored.

Key difference: dist/ is throwaway — rebuild it anytime. releases/ are versioned snapshots, built by the release script.

Commands

# Dev build (fast iteration)
dotnet publish src/GithubSaver/GithubSaver.csproj -c Release -r win-x64 --self-contained -o dist
copy dist\GithubSaver.exe dist\GithubSaver.scr

# Release build (automated — preferred)
.\build-release.ps1 -Version 0.2.5-beta

# Release build (skip tests for quick rebuild)
.\build-release.ps1 -Version 0.2.5-beta -SkipTests

# Run tests
dotnet test tests/GithubSaver.Tests/GithubSaver.Tests.csproj
npx jest tests/js/

Release Build Script (build-release.ps1)

Always use build-release.ps1 to create release candidates. It automates the full pipeline:

  1. Auto-increments a build number (4th digit) for MSI upgrade detection
  2. Updates version in GithubSaver.csproj and installer/Package.wxs
  3. Runs all tests (C# + JS) — fails fast on any failure
  4. Publishes self-contained single-file build to releases/v{version}+{build}/
  5. Copies GithubSaver.exeGithubSaver.scr
  6. Builds MSI installer with WiX
  7. Prints artifact summary and next steps

The build number auto-increments within the same base version and resets when the version changes. This ensures every MSI build is upgradable without burning real semver numbers on test iterations.

  • Assembly version: 0.2.4-beta+3 (semver + build metadata)
  • MSI version: 0.2.4.3 (4-digit numeric for Windows Installer)
  • Build number stored in .build-number (gitignored, local per machine)

Use -SkipTests only when rebuilding a version whose tests have already passed.

Releasing to GitHub

⚠️ MANDATORY: Never create a GitHub Release unless the user has explicitly confirmed they have tested the build and approved it for release.

Release checklist

  1. Run .\build-release.ps1 -Version {version} — this handles steps 2-4 automatically
  2. User has tested the build and confirmed it works ← REQUIRED
  3. Only then: create GitHub Release with release notes

Creating a GitHub Release

# Push tags
git push origin master --tags

# Create GitHub Release via gh CLI (or via github-mcp-server)
gh release create v0.1.0-beta --title "v0.1.0-beta" --notes "Release notes here" --prerelease

Release notes format

## What's New
- Feature 1
- Feature 2

## Bug Fixes
- Fix 1

## Screensaver Modules
- heatmap-multidata, heatmap-years, rain-days, rain-repos, heatmap-rain-weeks

## Installation
1. Download and extract the release ZIP
2. Copy `GithubSaver.scr` to `C:\Windows\System32\`
3. Open Screen Saver Settings and select GithubSaver

## Requirements
- Windows 10/11
- WebView2 Runtime (pre-installed on Windows 11)

Git Branching: GitHub Flow

This project follows GitHub Flow:

  1. main is always deployable — never commit directly to main
  2. Create a branch — for every feature, bugfix, or change, create a descriptively named branch from main (e.g. feature/multi-monitor, fix/mouse-exit-threshold)
  3. Commit often — make small, focused commits with clear messages
  4. Open a Pull Request — when work is ready for review, open a PR against main
  5. Review and discuss — get feedback, iterate
  6. Merge to main — after approval, merge the PR (squash or merge commit)
  7. Delete the branch — clean up after merge

Branch naming: feature/, fix/, chore/, docs/ prefixes followed by a short kebab-case description.

Repository

Lessons Learned

Data Flow

  • FetchAndCacheAllAsync must return Dictionary<string,string> — callers need in-memory data, not just file side-effects. File writes are for JS fallback only.
  • Never cache empty API responses ({}, []). A 2-byte cached file treated as "fresh" blocks all subsequent fetches until TTL expires.
  • GraphQL requires authentication — public REST endpoints work without a token, but GraphQL returns 401. Always warn when no token is configured.
  • The 15-second fetch timeout is necessary for first-time users fetching 7 years of GraphQL data. 5 seconds is too short.

MSI / WiX Installer

  • MSI version must be numeric 4-digit (MAJOR.MINOR.PATCH.BUILD). Pre-release suffixes like -beta must be stripped.
  • Same MSI version = upgrade skipped. Use auto-incrementing build number (4th digit) during development.
  • MajorUpgrade runs uninstall-then-install. Use REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE to run cleanup only on full uninstall, not upgrade.
  • WiX v4 Custom action conditions use Condition="" attribute, not inner text.
  • WiX regex for Package Version must not match InstallerVersion or XML declaration version=.
  • The installed .scr runs from C:\Windows\System32 — it reads modules from C:\Program Files\GithubSaver\wwwroot via registry path. Must reinstall MSI to pick up new code.

WebView2

  • EnsureCoreWebView2Async can hang forever if WebView2 runtime is missing. Always use a timeout (10s) with fallback UI.
  • Data is injected via AddScriptToExecuteOnDocumentCreatedAsync before navigation. Use JSON.parse() wrapper to prevent code injection from malicious JSON.
  • Each form needs a separate userDataFolder to avoid locking conflicts between screensaver and config.

Testing

  • Use InternalsVisibleTo to test internal methods without making them public.
  • When extracting shared JS functions to a separate file, update ALL module <script> tags and run ALL JS tests — partial extraction breaks modules silently.
  • Static classes are hard to test. Use a static facade pattern: Logger.Log() delegates to FileLogger instance, Logger.Instance exposes ILogger for DI.