Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,26 @@ npm install
npm start # your pet appears
```

The [Development section of the README](README.md#development) covers the day-to-day commands. The short version:
The [development guide](docs/development.md) covers the day-to-day commands. The short version:

| Command | What it does |
|---------|--------------|
| `npm test` | Smoke tests (node --test). Must be green before a PR. |
| `npx eslint src/ tests/` | Lint. Lint these folders, not `.` (local scratch dirs are noisy). |
| `npm run sheet` | Renders every pose x coat into `previews/contact-sheet.png` for visual QA. |
| `npm run lint` | Lint (eslint, cached). CI runs exactly this. |
| `npm run poses:cat` | Renders every activity x coat into `previews/cat-poses.png` for visual QA. Headless: no Electron, no GPU. |
| `npm run test:boot` | Boots the app headlessly and renders one frame. |

## Before you open a PR

1. **Tests green:** `npm test` (CI runs the same thing).
2. **Visual changes need visual proof.** If you touch `src/renderer.js`, `src/cat-sprite.js`, or anything that draws, run `npm run sheet` before and after. For pure refactors the sha256 of the contact sheet should not change; for intentional changes, include a before/after crop in the PR.
2. **Visual changes need visual proof.** If you touch `src/renderer.js`, `src/cat-sprite.js`, or anything that draws, run `npm run poses:cat` before and after. For pure refactors the sha256 of the sheet should not change; for intentional changes, include a before/after crop in the PR.
3. **Keep the site cat in sync.** `site/cat-sprite.js` must stay a byte-identical copy of `src/cat-sprite.js` (a test enforces this). If you change the sprite, re-copy it: `node -e "fs.copyFileSync('src/cat-sprite.js','site/cat-sprite.js')"`.
4. **Respect line endings.** Files in this repo are a mix of LF and CRLF for historical reasons. Do not normalize files you are not otherwise changing; keep each file's existing endings.
5. **Commit style:** conventional commits (`feat:`, `fix:`, `docs:`, `test:`, `chore:`), imperative mood, like the existing history.

## Good places to start

- **Own a Mac?** The macOS port is code-complete but untested on real hardware. Running the [beta checklist](README.md#build-a-standalone-app) and reporting what happened is the single most useful contribution right now.
- **Own a Mac?** The macOS port is code-complete but untested on real hardware. Running the [beta checklist](docs/development.md#macos-beta-checklist) and reporting what happened is the single most useful contribution right now.
- **Design a coat** and share it (Settings → Custom coats → Export). Great ones can become built-ins.
- Anything labeled [good first issue](https://github.com/JOhnsonKC201/pixelpets/labels/good%20first%20issue) or [help wanted](https://github.com/JOhnsonKC201/pixelpets/labels/help%20wanted).

Expand Down
590 changes: 91 additions & 499 deletions README.md

Large diffs are not rendered by default.

Binary file removed assets/pixelcat-demo.gif
Binary file not shown.
Binary file removed assets/pixelcat-demo.mp4
Binary file not shown.
Binary file removed assets/pixelcat.ico
Binary file not shown.
25 changes: 25 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# pixelpets documentation

The [README](../README.md) is the tour. These are the details it links out to.

| Document | What is in it |
|---|---|
| [features.md](features.md) | Every interaction, coat, mood, sound, and productivity feature |
| [custom-coats.md](custom-coats.md) | Designing, hand-editing, and sharing your own coat |
| [architecture.md](architecture.md) | How one sprite covers 15 coats, and the project layout |
| [development.md](development.md) | Running from source, building installers, visual QA |
| [frame-pack.md](frame-pack.md) | Painting a pose by hand and importing it |
| [reference-art/](reference-art/) | The original hand-drawn coat studies, kept as history |

Elsewhere in the repo:

| Location | What is in it |
|---|---|
| [`integrations/`](../integrations/) | Agent hook configs: Claude Code, Codex, Cursor, Antigravity, Kiro |
| [`scripts/`](../scripts/) | What each script does and which are safe to move |
| [`assets/climb/`](../assets/climb/) | Adding hand-painted rope-climb frames for a coat |
| [`tools/ipad-terminal/`](../tools/ipad-terminal/) | Driving this machine from an iPad browser |
| [`tools/lobby-jam/`](../tools/lobby-jam/) | The lo-fi jam generator as a standalone page |
| [CONTRIBUTING.md](../CONTRIBUTING.md) | How to open a good PR here |
| [SECURITY.md](../SECURITY.md) | What the app touches, and reporting a vulnerability |
| [CHANGELOG.md](../CHANGELOG.md) | Release history |
94 changes: 94 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# How it works

Why one `sit` pose covers fifteen coats and two species, and why the repo ships
no sprite sheets and no audio files.

## One sprite, recolored

The cat is one role-coded sprite (outline, coat, markings, white, patch, eye,
nose, inner ear) built procedurally, then recolored per pattern at draw time, so
a dozen cats come from one shape.

Every pose is composed into that same grid, including the limbs. A raised paw is
made of the same cells as the rest of the pet, so it picks up the coat's shading,
outline halo, markings and breathing scale for free. Nothing is painted on top
afterwards. That is why washing, pondering, batting a leaf, boxing at the
butterfly and swiping at the scroll leaf all work in all 15 coats and both
species without a single extra sprite asset.

Poses that vary continuously (how high a paw is raised, how far it reaches, which
paw is mid-swipe) are quantised to a handful of steps and memoised, and built
only for the coat currently on screen. Pixel art wants stepped limbs anyway, so
the cheap thing and the right-looking thing are the same thing.

## Rendering

Rendering happens on an HTML canvas in a full-screen transparent Electron window.
Ordinary screenshots cannot capture it (it is GPU-composited), so previews use a
self-capture:

```powershell
electron . --shot --pattern=<name> [--species=cat|dog] [--state=<pose>] [--at=<ms>]
```

The mochi drag is a spring system (a pinned handle plus a trailing body point)
with a three-band stretch that keeps the head and feet rigid.

## Scheduling and sound

Break timers and reminders are scheduled in the main process (the renderer
throttles and pauses when idle), which pushes fire events to the cat over IPC.

Sound is synthesized with Web Audio in the renderer, so there are no audio assets
to ship: every meow, bark, purr, pant, swipe and thud is generated live, and so
is the lo-fi jam.

## Project layout

```
pixelpets/
src/
main.js # overlay window, global input hooks, tray + menu,
# scheduler (breaks, reminders, Pomodoro), config IPC
renderer.js # the pet: sprites, palettes, physics, reactions, sound
preload.js # safe IPC bridge for the overlay
cat-sprite.js # the role-coded cat, mirrored to site/ for the browser demo
dog-sprite.js # the Black Lab, and the poses composed from it
pets.js # per-species registry: menu labels, IPC channels, copy
patterns.js # shared coat list, recolored at draw time
art-frames.js # baked hand-painted poses that win over the composer
climb-frames.js # the yarn-rope climb, per coat
audio.js # synthesized voice: meow, purr, bark, pant, chirp
jam.js # the live lo-fi jam, generated bar by bar
effects.js / bubble.js # hearts and leaves, and the speech bubble
focus.js # when to leave you alone: meetings, work mode, quiet hours
quiet-hours.js # the nightly do-not-disturb window
mail.js / mail-worker.js # IMAP unread-mail checks (isolated worker)
cal.js / cal-worker.js # .ics calendar feed (isolated worker)
config.js # settings.json load/save/normalize (per-user app data)
datadir.js # one-time rescue of that folder after the rename
themes.js # custom-coat load/validate
template.js # message placeholders: {name}, {time}, {date}, {count}
index.html # the overlay page
settings*.{html,js} # settings window + its IPC bridge

tests/ # node:test suites, no Electron and no GPU required
scripts/ # the vm harness, icon and demo generators, notify.js,
# install-hook.js, the boot check (see scripts/README.md)
tools/ # standalone extras: the iPad terminal, the Lobby Jam page
integrations/ # ready-made agent hook configs (5 agents)
assets/ # generated icons, showcase, hero + gallery clips
site/ # the browser demo deployed to Vercel
docs/ # this folder: features, architecture, development, frame pack
.github/ # CI and release workflows, issue and PR templates
```

## Entry points that are part of the public surface

These paths appear in other people's config files, so they do not move:

| Path | Who depends on it |
|---|---|
| `agent-hook.js` | every user's agent hook config (see [`integrations/`](../integrations/)) |
| `scripts/notify.js` | scripts, CI jobs and cron entries that push a message to the pet |
| `launch-pixelpets.vbs` | Windows desktop shortcuts that start the pet without a console |
52 changes: 52 additions & 0 deletions docs/custom-coats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Custom coats

Design your own coat under **Settings > Pet > Custom coats > "+ Add a custom
coat"**: pick a name, a body build (standard, slender, stocky, or fluffy),
optional tabby stripes, and eight colours (coat, marks, white, patch, eyes, nose,
inner ear, outline). Your coat shows up in the Coat dropdown and the tray menu
next to the 14 built-ins.

Custom coats are built from the cat's geometry, so they apply to cats only. The
Pet tab says so when a dog is selected.

## Where they live

`themes.json` in your app-data folder, which you can hand-edit too:

| Platform | Path |
|---|---|
| Windows | `%APPDATA%/pixelpets/themes.json` |
| macOS | `~/Library/Application Support/pixelpets/themes.json` |

```json
{ "themes": [
{ "name": "Galaxy", "build": "fluffy", "tabby": false,
"coat": "#3b2f63", "mark": "#2a2147", "white": "#c9c0e8", "patch": "#7a5cc0",
"eye": "#7fd6ff", "nose": "#e0a0c0", "inner": "#9a7ad0", "outline": "#15101f" } ] }
```

Every colour role is required and must be a `#rrggbb` hex. Invalid themes are
skipped rather than crashing the app.

## Preview one without the overlay

```powershell
npx electron . --shot --pattern=galaxy
```

## Share them

Export and Import live in the same Settings panel. They write and read a plain
JSON file, and imported coats merge into your set by name, so sharing a coat is
sending one small file.

Great coats can become built-ins. Open a
[Discussion](https://github.com/JOhnsonKC201/pixelpets/discussions) with the
export and a screenshot.

## Painting a pose instead

Custom coats change *colour*. Changing the pet's *shape* for a given pose is a
different job, and it has its own escape hatch: paint the pose, import it, and it
wins over the composer for exactly the coats you name. See
[frame-pack.md](frame-pack.md).
137 changes: 137 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Development

Run from source, build an installer, and check visual changes on a project whose
output is GPU-composited and cannot be screenshotted.

Requires git and Node 20 or newer.

```powershell
git clone https://github.com/JOhnsonKC201/pixelpets.git
cd pixelpets
npm install
npm start
```

## Commands

```powershell
npm start # run the app
npm test # 200+ tests: config and data migration, poses, interactions,
# audio, focus and quiet hours, mail and calendar, site drift
npm run lint # eslint over src, tests and scripts (what CI runs)
npm run test:boot # launch the real app and assert it renders a frame
npm run check:hook # does the global input hook load on this Electron?
npm run poses:cat # previews/cat-poses.png (every activity x every coat)
npm run poses:dog # the same for the Black Lab
npm run frames:import -- <dir> # import painted PNGs as baked poses
npm run demo:all # regenerate the README media (hero, gallery, carousel)
npm run hook -- cursor # print a path-filled agent hook config
npm run icon # regenerate the tray + app-tile icons
```

CI runs `npm run lint`, `npm test`, and the boot check on Windows and macOS for
every push and pull request.

> [!TIP]
> **Windows:** `npm start` leaves a console window open behind the pet.
> `launch-pixelpets.vbs` starts the same thing silently, so it works as a desktop
> shortcut (point one at `wscript.exe "<path>\launch-pixelpets.vbs"`). It locates
> the project from its own path, so moving the folder does not break it.

> [!NOTE]
> **macOS:** same commands. On first run, grant Accessibility permission (System
> Settings > Privacy & Security > Accessibility) so your pet can react to your
> typing and cursor. Input is only detected, never logged or sent anywhere.

## Build a standalone app

```powershell
npm run pack # portable build -> dist/win-unpacked/pixelpets.exe (no installer)
npm run dist # Windows installer -> dist/pixelpets Setup <version>.exe
```

`pack` works out of the box. `dist` (the NSIS installer) needs permission to
create symlinks while electron-builder unpacks its bundled signing tools: enable
Windows Developer Mode (Settings > Privacy & security > For developers) or run
the build once from an Administrator terminal. The native `uiohook-napi` module
ships N-API prebuilds, so `npmRebuild` is disabled in the build config and no
Visual Studio is needed.

**macOS:** `npm run dist:mac` (on a Mac) builds a dmg and zip for Apple Silicon
and Intel; the release workflow also builds them in CI on every version tag.
Builds are ad-hoc signed rather than notarized, so Gatekeeper still asks before
the first launch: open it once, then **System Settings > Privacy & Security >
Open Anyway**. The ad-hoc seal matters for more than the warning: macOS keys the
Accessibility grant and the Keychain entry to a code signature, so without it both
would be thrown away on every update.

### macOS beta checklist

The mac port is code-complete but has not been smoke-tested on real hardware. If
you own a Mac, running this and opening an issue with whatever you see is the
single most useful contribution to the project right now.

- [ ] The overlay shows over all apps and Spaces, including fullscreen.
- [ ] Clicks pass through everywhere except on the pet.
- [ ] The typing reaction works after granting Accessibility.
- [ ] The pet rests on the Dock edge, not the menu bar.
- [ ] The tray menu works in the menu bar.
- [ ] Login launch works.

## Visual QA

The overlay is GPU-composited, so ordinary screenshots cannot capture it. Poses
are reviewed with a one-command contact sheet instead:

```powershell
npm run poses:cat
```

That renders **every activity across every coat** into one image, bottom-aligned
on a shared floor line so silhouettes can be compared down a column. It runs
headlessly, with no Electron and no GPU, by loading the overlay's script stack in
a vm (`scripts/overlay-vm.js`) and reading the pose grids back out. That is also
what the pose tests drive, so the sheet and the suite are looking at exactly the
same sprites.

Single poses preview via:

```powershell
npx electron . --shot --state=<sit|typing|hunt|loaf|groom|paper|overheat|pet|startle|work> --pattern=<coat>
```

Add `--at=<ms>` to capture an animated pose at a chosen phase (a typing key-press,
say): `npx electron . --shot --state=typing --at=760`. Add `--note="<text>"` to
pin a speech bubble open in the capture, which is how bubble wrapping and
screen-edge clamping get checked against a real font rather than only in unit
tests: `npx electron . --shot --note="a long reminder that has to wrap"`.

## Painting a pose by hand

Every pose is composed in code, which is why one `sit` covers 15 coats and a new
coat costs nine hex values instead of an art pass. The trade is that changing how
the pet looks means editing geometry.

`npm run frames:import` is the escape hatch: paint a pose against a placeholder
palette, import it, and it wins over the composer for exactly the coats you name
while everything else keeps composing. Five held poses can be baked (`sit`,
`type`, `loaf`, `rear`, `hunt`); the six raised-limb activities are parameterised
rigs whose limbs sweep through quantised frames, so a still would freeze them.

Palette, naming and the checks the importer runs are in
[frame-pack.md](frame-pack.md).

## README media

The hero banner, the gallery, and the coat carousel are all rendered from the
same sprite geometry by `scripts/make-demo-gif.js`, in pure Node with no browser
or GPU. Regenerate any one with
`node scripts/make-demo-gif.js <hero|gallery|carousel> [mp4]`, or all three with
`npm run demo:all`.

## Tech

Electron, HTML canvas, Web Audio,
[`uiohook-napi`](https://github.com/SnosMe/uiohook-napi) (system-wide keyboard
hook), [`imapflow`](https://github.com/postalsys/imapflow), and
[`node-ical`](https://github.com/jens-maus/node-ical).
Loading