Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
359 commits
Select commit Hold shift + click to select a range
a45dda5
Suppress the native focus ring on the new screens' buttons
sevenns Aug 16, 2026
8d3cae6
Rework the Customize hints, order and Linux section
sevenns Aug 16, 2026
027f8f3
Keep a long hint from pushing the value out of its column
sevenns Aug 16, 2026
31dcd3a
Clear the status strip left behind by the previous game
sevenns Aug 16, 2026
7edf6d1
Legend only names keys the keyboard actually has
sevenns Aug 16, 2026
f3bca6e
Merge pull request #29 from sevenns/feature/game-settings-in-launcher
sevenns Aug 16, 2026
bd44ad1
Give the keyboard a caret, a paste key and a held backspace
sevenns Aug 16, 2026
da0bd4d
Draw the typed spaces instead of collapsing them
sevenns Aug 16, 2026
9b0169b
Merge pull request #30 from sevenns/feature/osk-caret-and-paste
sevenns Aug 16, 2026
cd424c0
Add the notify SFX to five bundled sound sets
sevenns Aug 16, 2026
e64d755
Add the notification IPC contract and the notify sfx slot
sevenns Aug 16, 2026
06e359b
Add the pure notification inbox model and delivery rule
sevenns Aug 16, 2026
124d90b
Persist the notification inbox in notifications.json
sevenns Aug 16, 2026
9a1789d
Wire the notification service into main and its event sources
sevenns Aug 16, 2026
c277421
Report whether the user is at the launcher over ui:presence
sevenns Aug 16, 2026
058a947
Show notifications as a toast and a popup in the More menu
sevenns Aug 16, 2026
26e3ad2
Keep the notification list stable across repaints and languages
sevenns Aug 16, 2026
2a1fd7c
Bump version to 0.8.0-alpha.76
sevenns Aug 16, 2026
f3a5ff5
Drop the now-unused sfx:play main-to-renderer path
sevenns Aug 16, 2026
56b20a4
Bump version to 0.8.0-alpha.77
sevenns Aug 16, 2026
9d3643b
Fix six notification UI issues found in the dev build
sevenns Aug 16, 2026
b0e6038
Bump version to 0.8.0-alpha.78
sevenns Aug 16, 2026
81bb989
Mute notifications only while a game runs, and pulse on exit
sevenns Aug 16, 2026
1f42518
Bump version to 0.8.0-alpha.79
sevenns Aug 16, 2026
950fd59
Pulse the toast twice: mid-plate and before it leaves
sevenns Aug 16, 2026
06370e6
Let the notification list scroll, with the shared edge fades
sevenns Aug 16, 2026
34deabc
Bump version to 0.8.0-alpha.80
sevenns Aug 16, 2026
d893891
Keep a notification unread until the popup is opened
sevenns Aug 16, 2026
39d764e
Bump version to 0.8.0-alpha.81
sevenns Aug 16, 2026
d8533d8
Merge pull request #31 from sevenns/feature/notifications
sevenns Aug 16, 2026
0fdf234
Add a game from the Home More menu
sevenns Aug 16, 2026
4d85c79
Bump version to 0.8.0-alpha.82
sevenns Aug 16, 2026
76d3a8b
Step the add-game source with the D-pad and follow the new game
sevenns Aug 16, 2026
84b8aaa
Bump version to 0.8.0-alpha.83
sevenns Aug 16, 2026
9ecc8ca
Move Add game under Settings; refresh browse after a local delete
sevenns Aug 16, 2026
49f0604
Bump version to 0.8.0-alpha.84
sevenns Aug 16, 2026
3732083
Ask about the history record before deleting a game
sevenns Aug 16, 2026
c1b6f08
Bump version to 0.8.0-alpha.85
sevenns Aug 16, 2026
383b9e4
Let go of a ready state whose game was just deleted
sevenns Aug 16, 2026
9fe78ae
Bump version to 0.8.0-alpha.86
sevenns Aug 16, 2026
07d9333
Keep the game stats in view and scroll the Details stack
sevenns Aug 16, 2026
4e6d1ba
Bump version to 0.8.0-alpha.87
sevenns Aug 16, 2026
c663903
Merge pull request #32 from sevenns/feature/add-game
sevenns Aug 16, 2026
cfbd4fc
Untrack the node_modules symlink
sevenns Aug 16, 2026
07af918
Sleep the mouse until it is deliberately shoved
sevenns Aug 16, 2026
3138e76
Bump version to 0.8.0-alpha.88
sevenns Aug 16, 2026
2a82003
Raise the mouse wake threshold to 300px
sevenns Aug 16, 2026
6da2447
Bump version to 0.8.0-alpha.89
sevenns Aug 16, 2026
44be913
Mark rows, not lists, for the entrance animation
sevenns Aug 16, 2026
43b3f0d
Bump version to 0.8.0-alpha.90
sevenns Aug 16, 2026
6d33106
Make the focus fill instant, not a fading transition
sevenns Aug 17, 2026
39c4eb3
Bump version to 0.8.0-alpha.91
sevenns Aug 17, 2026
8f56ce6
Merge pull request #33 from sevenns/feature/mouse-sleep
sevenns Aug 17, 2026
26bf865
Add the limit and popup sound slots, borrowed when a set lacks them
sevenns Aug 17, 2026
7081573
Latch the limit sound to one per series of blocked attempts
sevenns Aug 17, 2026
bc9eb1e
Voice popup open/close and the dead ends on every surface
sevenns Aug 17, 2026
65e17a2
Bump version to 0.8.0-alpha.92
sevenns Aug 17, 2026
d007cd3
Refresh the bundled sound sets and add the xbox ambience track
sevenns Aug 17, 2026
cfa8134
Open More with the popup sound; keep button and back on screens
sevenns Aug 17, 2026
6e87001
Bump version to 0.8.0-alpha.93
sevenns Aug 17, 2026
c464053
Show the home screen in the README, not the Bloodborne card
sevenns Aug 17, 2026
60c65f6
Swap the README screenshot for the compressed jpg
sevenns Aug 17, 2026
2901a5a
Bump version to 0.8.0-alpha.94
sevenns Aug 17, 2026
1185ee7
Glide the strip through a held flip and quicken the morph
sevenns Aug 17, 2026
5a2e79b
Bump version to 0.8.0-alpha.95
sevenns Aug 17, 2026
0f51000
Halve the auto-move delay and chain one held run into the next
sevenns Aug 17, 2026
d69900d
Bump version to 0.8.0-alpha.96
sevenns Aug 17, 2026
90beabc
Hold the hero and palette until the auto-move really stops
sevenns Aug 17, 2026
e5c0322
Bump version to 0.8.0-alpha.97
sevenns Aug 17, 2026
18a7a41
Add the typing sound files and fix switch's popup-open
sevenns Aug 17, 2026
c7b8c3e
Sound a typed character with its own typing slot
sevenns Aug 17, 2026
73978ae
Bump version to 0.8.0-alpha.98
sevenns Aug 17, 2026
cdcca6e
Sound a backspace with the typing slot as well
sevenns Aug 17, 2026
12baad2
Bump version to 0.8.0-alpha.99
sevenns Aug 17, 2026
b7cb310
Press the caret keys with button, not the navigate sound
sevenns Aug 17, 2026
d089e32
Bump version to 0.8.0-alpha.100
sevenns Aug 17, 2026
df936b4
Merge pull request #34 from sevenns/feature/sfx-limit-popup
sevenns Aug 17, 2026
4c0d3dc
Move the launcher's system items into the carousel as cards
sevenns Aug 18, 2026
4efa242
Bump version to 0.8.0-alpha.101
sevenns Aug 18, 2026
e786cb7
Show the playable dot even with no history in the row
sevenns Aug 18, 2026
fd2f487
Fix the music and the exit when no game is on screen
sevenns Aug 18, 2026
54d52d5
Bump version to 0.8.0-alpha.102
sevenns Aug 18, 2026
ea5d4c9
Ignore input while the boot screen is still up
sevenns Aug 18, 2026
3af581d
Bump version to 0.8.0-alpha.103
sevenns Aug 18, 2026
44c0ae1
Sound the Settings screen once per gesture, not twice
sevenns Aug 18, 2026
59e9306
Bump version to 0.8.0-alpha.104
sevenns Aug 18, 2026
eeaaed5
Sound the press before a settings row opens a surface
sevenns Aug 18, 2026
105687e
Bump version to 0.8.0-alpha.105
sevenns Aug 18, 2026
d2415c7
Merge pull request #35 from sevenns/feature/carousel-system-cards
sevenns Aug 18, 2026
a9ef786
Add the Library screen: a grid of every game, with Add game
sevenns Aug 18, 2026
25ed1cb
Polish the Library: glow, animated re-flow, capped strip
sevenns Aug 18, 2026
62f2dd7
Move the Library glow behind the covers; cut, not fade
sevenns Aug 18, 2026
5f46126
Lift the Library glow above the neighbouring covers
sevenns Aug 18, 2026
925d960
Drop the Library glow; stagger section switches; hide the strip
sevenns Aug 18, 2026
8d1feb5
Restore the selected card scale; pool card nodes; debounce sections
sevenns Aug 18, 2026
ae0cc9d
Kill the strip's morph grace when detail comes from the Library
sevenns Aug 18, 2026
6af6406
Give the Library its entrance back on the way in from detail
sevenns Aug 18, 2026
daa4f1d
Clear entrance marks across the card pool, not the grid
sevenns Aug 18, 2026
d5f2dd0
Park the strip at the last game so launcher cards stay in view
sevenns Aug 18, 2026
afd9249
Park the strip on the Library card, keep the window as it was
sevenns Aug 18, 2026
9a60250
Let the bar caption travel with the parked strip's selection
sevenns Aug 18, 2026
c5cc483
Revert the parked-strip experiment on the carousel
sevenns Aug 18, 2026
6ec8fad
Rename the forget entry to Remove from library
sevenns Aug 18, 2026
1f35b2a
Speak of the library, not the history, across the UI
sevenns Aug 18, 2026
bf221c1
Close Customize back to the detail screen it opened over
sevenns Aug 18, 2026
5acab18
Point at the field a nested validator issue belongs to
sevenns Aug 18, 2026
a0575ed
Merge pull request #36 from sevenns/feature/game-library
sevenns Aug 18, 2026
82b0568
Bump version to 0.8.0-alpha.124
sevenns Aug 19, 2026
f7db54a
Add PC-library launch drafts and move-to-card transfer
sevenns Aug 19, 2026
291bd47
Bump version to 0.8.0-alpha.125
sevenns Aug 19, 2026
cdf4195
Polish move-to-card copy and align the library grid to its sidebar
sevenns Aug 19, 2026
0aea6e8
Bump version to 0.8.0-alpha.126
sevenns Aug 19, 2026
6dc70eb
Fix move-to-card previews, save path carry-over and screen titles
sevenns Aug 19, 2026
80d3706
Bump version to 0.8.0-alpha.127
sevenns Aug 19, 2026
e8d74ee
Show full notification text and repeat held directions in popups
sevenns Aug 19, 2026
e83a61f
Bump version to 0.8.0-alpha.128
sevenns Aug 19, 2026
83d6ad9
Keep the sidebar cursor on the section the pane shows
sevenns Aug 19, 2026
e679b25
Keep the launcher open without a card by default
sevenns Aug 19, 2026
45e6579
Bump version to 0.8.0-alpha.129
sevenns Aug 19, 2026
1e813cb
Write settings and index files that refuse an atomic replace
sevenns Aug 19, 2026
85b711f
Tell the user when their settings could not be saved
sevenns Aug 19, 2026
1e65f97
Bump version to 0.8.0-alpha.130
sevenns Aug 19, 2026
03859a1
Reach the in-place write fallback quickly and when staging fails
sevenns Aug 19, 2026
37e8a75
Bump version to 0.8.0-alpha.131
sevenns Aug 19, 2026
79475e7
Fix the review findings for the PC draft and move PR
sevenns Aug 20, 2026
bf8b6b2
Bump version to 0.8.0-alpha.132
sevenns Aug 20, 2026
f1d0bad
Merge pull request #37 from sevenns/feature/pc-draft-and-move
sevenns Aug 20, 2026
e1f6f0e
Glide one focus ring between cards instead of redrawing it
sevenns Aug 20, 2026
2504f12
Bump version to 0.8.0-alpha.133
sevenns Aug 20, 2026
4936797
Stretch and settle the focus ring as it travels
sevenns Aug 20, 2026
d23b429
Bump version to 0.8.0-alpha.134
sevenns Aug 20, 2026
b0d9319
Replace the focus ring with a soft body under the cover
sevenns Aug 20, 2026
157cec4
Bump version to 0.8.0-alpha.135
sevenns Aug 20, 2026
fdde0b4
Drop the jelly's glow and rough up its outline
sevenns Aug 20, 2026
b98b7fb
Bump version to 0.8.0-alpha.136
sevenns Aug 20, 2026
520a83b
Bring the jelly back with the card, not before it
sevenns Aug 20, 2026
4deb6d5
Sound Add game once, not twice
sevenns Aug 20, 2026
311776e
Bump version to 0.8.0-alpha.137
sevenns Aug 20, 2026
f1b25d8
Keep the jelly off the boot screen
sevenns Aug 20, 2026
cc86450
Widen the gap between cards to 24
sevenns Aug 20, 2026
cff49c8
Bump version to 0.8.0-alpha.138
sevenns Aug 20, 2026
5ca7910
Move the carousel title by the new gap too
sevenns Aug 20, 2026
810e059
Bump version to 0.8.0-alpha.139
sevenns Aug 20, 2026
bcabaaa
Give the selected card its own wider gap on the row
sevenns Aug 21, 2026
93ee68d
Bump version to 0.8.0-alpha.140
sevenns Aug 21, 2026
3a4a2a6
Add liquid focus: one body, per-layer windows
sevenns Aug 21, 2026
5f7859c
Bump version to 0.8.0-alpha.141
sevenns Aug 21, 2026
240f31c
Let the liquid body be the focus fill, and stop clipping it
sevenns Aug 21, 2026
77a01f5
Bump version to 0.8.0-alpha.142
sevenns Aug 21, 2026
9f3dc16
Scroll the inbox to its end when focus leaves the list
sevenns Aug 21, 2026
1179188
Tune the liquid body separately for covers and for UI
sevenns Aug 21, 2026
79de97f
Bump version to 0.8.0-alpha.143
sevenns Aug 21, 2026
3c25045
Drop the pulsing ring around Play
sevenns Aug 21, 2026
42df8f5
Bump version to 0.8.0-alpha.144
sevenns Aug 21, 2026
b93ce2a
Leash the body so a held direction cannot fling it away
sevenns Aug 21, 2026
1f91974
Move the row and the grid onto the one liquid body
sevenns Aug 21, 2026
1309f97
Start a detail screen on Play, always
sevenns Aug 21, 2026
0a5a25b
Bump version to 0.8.0-alpha.145
sevenns Aug 21, 2026
c83ee65
Lift the row's liquid window over the bar gradient
sevenns Aug 21, 2026
921f393
Make the body translucent, and hold it between screens
sevenns Aug 21, 2026
8602542
Wake the bar focus when a detail screen opens
sevenns Aug 21, 2026
5ce686f
Bump version to 0.8.0-alpha.146
sevenns Aug 21, 2026
6845bf5
Hide liquid windows on boot by class, not by id
sevenns Aug 21, 2026
2e79217
Give every screen its own liquid window
sevenns Aug 21, 2026
7fa1d21
Bump version to 0.8.0-alpha.147
sevenns Aug 21, 2026
eb54183
Pull the body taut while a direction is held
sevenns Aug 21, 2026
2245aab
Bump version to 0.8.0-alpha.148
sevenns Aug 21, 2026
255338d
Paint the liquid body red for one diagnostic build
sevenns Aug 21, 2026
d704804
Anchor the body to where the row will settle
sevenns Aug 21, 2026
5bb4b87
Bump version to 0.8.0-alpha.150
sevenns Aug 21, 2026
51c3241
Give the value menus their own liquid window
sevenns Aug 21, 2026
9774738
Bump version to 0.8.0-alpha.151
sevenns Aug 21, 2026
86ff320
Take the row's anchor from layout, not from the slide
sevenns Aug 21, 2026
206c000
Bump version to 0.8.0-alpha.152
sevenns Aug 21, 2026
2a92d01
Restore alpha.144 and put liquid on every UI surface
sevenns Aug 21, 2026
f74cc2d
Bump version to 0.8.0-alpha.153
sevenns Aug 21, 2026
5e4091e
Mount the keyboard and picker windows above their veils
sevenns Aug 21, 2026
31cbcf0
Bump version to 0.8.0-alpha.154
sevenns Aug 21, 2026
84ab618
Put the interface text above the body, and light only where it is
sevenns Aug 21, 2026
632423d
Bump version to 0.8.0-alpha.155
sevenns Aug 21, 2026
ff61bac
Drop the leftovers from the grid's old body
sevenns Aug 21, 2026
6f45645
Bump version to 0.8.0-alpha.156
sevenns Aug 21, 2026
ced22b7
Paint the body only on the layer that owns the focus
sevenns Aug 21, 2026
34b4a6f
Bump version to 0.8.0-alpha.157
sevenns Aug 21, 2026
635b87d
Tint on hover, invert only where the body is
sevenns Aug 21, 2026
7056045
Bump version to 0.8.0-alpha.158
sevenns Aug 21, 2026
bb7993a
Span the popup's window across the screen
sevenns Aug 21, 2026
8743565
Bump version to 0.8.0-alpha.159
sevenns Aug 21, 2026
17e6b1b
Keep the jelly on the cards only, drop liquid UI
sevenns Aug 21, 2026
48ffc6c
Bump version to 0.8.0-alpha.160
sevenns Aug 21, 2026
4f6c656
Merge pull request #38 from sevenns/feature/focus-jelly
sevenns Aug 21, 2026
c5f3476
Bump version to 0.8.0-alpha.162
sevenns Aug 21, 2026
a1c726a
Refresh the browsed local game after the library reloads
sevenns Aug 21, 2026
14c96bd
Hold the strip while a browse answer is still in flight
sevenns Aug 21, 2026
e612304
Bump version to 0.8.0-alpha.163
sevenns Aug 21, 2026
5d7cb19
Merge pull request #39 from sevenns/fix/pc-game-audio-palette
sevenns Aug 21, 2026
68a786b
Update the README screenshot
sevenns Aug 21, 2026
299ecaf
Fetch game metadata from Steam, SteamGridDB and Khinsider
sevenns Aug 21, 2026
2c3d57a
Fix the hidden keyboard and move Find online to the column
sevenns Aug 21, 2026
d95bf95
Take backgrounds from screenshots, add GOG and RAWG sources
sevenns Aug 21, 2026
ed79cd2
Label each art source and enrich the appid shortcut
sevenns Aug 21, 2026
2a71cea
Replace RAWG with Wallhaven as the wallpaper source
sevenns Aug 22, 2026
e2add9e
Pin the Steam region and stop clicks through overlays
sevenns Aug 22, 2026
eae5d9f
Pick several backgrounds by mouse, and add without replacing
sevenns Aug 22, 2026
6034338
Keep a local game's music off the card audio channel
sevenns Aug 22, 2026
10946df
Give each track a menu to listen or take it
sevenns Aug 22, 2026
d5f8407
Match wallpapers by landscape shape, not exact ratios
sevenns Aug 22, 2026
81f4577
Store genres, release date and platforms for later use
sevenns Aug 22, 2026
150e2cd
Add Wallpaper Cave as a second wallpaper source
sevenns Aug 22, 2026
046339f
Page the artwork gallery with a load-more tile
sevenns Aug 22, 2026
9f875a7
Strip trademark marks from search terms
sevenns Aug 22, 2026
58aa331
Filter the artwork gallery by source and size
sevenns Aug 22, 2026
b972040
Drop the full-size preview from the artwork gallery
sevenns Aug 22, 2026
e807404
Give soundtracks the gallery's two-column surface
sevenns Aug 22, 2026
5fd3dac
Match GOG results by title, and skip them when Steam answered
sevenns Aug 22, 2026
695376c
Scroll the picker sidebar with the focus
sevenns Aug 22, 2026
2848523
Merge Find online into one full-screen surface
sevenns Aug 22, 2026
b7f5ece
Report online results through the launcher popup and toast
sevenns Aug 22, 2026
d9579bf
Send Customize results to the popup and the toast too
sevenns Aug 22, 2026
a8d3db8
Ask before replacing the title with the store spelling
sevenns Aug 22, 2026
bd8422d
Stack notification plates instead of queueing them
sevenns Aug 22, 2026
11d5046
Use the launcher popup for online questions and progress
sevenns Aug 22, 2026
a25df17
Pass the candidate name into the title confirm
sevenns Aug 22, 2026
3c7a0bd
Hang notification plates off the right edge
sevenns Aug 22, 2026
3cc4ff3
Take the candidate name into an empty title field
sevenns Aug 22, 2026
392b338
Read big Khinsider albums and their track names right
sevenns Aug 22, 2026
5078940
Let a load take the focus only if the user has not moved it
sevenns Aug 23, 2026
31df97f
Land a new game in the library and drop stale heroes
sevenns Aug 23, 2026
2acf40a
Fix Update title and widen the search cascade
sevenns Aug 23, 2026
ff079e7
Merge pull request #40 from sevenns/feature/metadata-fetch
sevenns Aug 23, 2026
1fccbb6
Add macOS support: darwin platform bundle, dmg build and CI
sevenns Aug 23, 2026
68f0b61
Bump version to 0.8.0-alpha.196
sevenns Aug 23, 2026
6c8ae1f
Explain manual updates on macOS instead of dead update rows
sevenns Aug 23, 2026
84a9f10
Bump version to 0.8.0-alpha.197
sevenns Aug 23, 2026
b3f5a44
Fix black screen after minimizing Playhook on macOS
sevenns Aug 23, 2026
a75c5d9
Bump version to 0.8.0-alpha.198
sevenns Aug 23, 2026
32ac0c8
Merge pull request #41 from sevenns/feature/macos-support
sevenns Aug 23, 2026
29d69a3
Add happy-dom and renderer DOM test base
sevenns Aug 23, 2026
3c26527
Add DOM tests for sidebar, OSK and file picker
sevenns Aug 23, 2026
282fdb0
Add DOM tests for settings and game settings screens
sevenns Aug 23, 2026
e18ef80
Cover test/ with typecheck and ESLint, fix legacy suites
sevenns Aug 23, 2026
a1a89ad
Document renderer DOM test convention in CLAUDE.md
sevenns Aug 23, 2026
70814de
Address PR 42 review: per-test fixture, mouse coverage
sevenns Aug 23, 2026
e9b977e
Bump version to 0.8.0-alpha.199
sevenns Aug 23, 2026
d8b681b
Merge pull request #42 from sevenns/feature/renderer-jsdom-tests
sevenns Aug 23, 2026
99ebbdf
Keep closed overlays out of the compositor on Retina
sevenns Aug 23, 2026
d5037fd
Bump version to 0.8.0-alpha.200
sevenns Aug 23, 2026
6bac66a
Stop narrowing the tint veils, only the blur tolerates that cut
sevenns Aug 23, 2026
69abf64
Bump version to 0.8.0-alpha.201
sevenns Aug 23, 2026
a96c60a
Merge pull request #43 from sevenns/fix/macos-tile-memory
sevenns Aug 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: Build Linux

# Build the Linux (SteamOS / Steam Deck) distributable in the cloud: the ubuntu runner has the
# toolchain to compile the native `drivelist` for the right ABI (impossible on macOS), and
# electron-builder produces a self-contained AppImage.
# toolchain to compile the native `drivelist` for the right ABI, and electron-builder produces a
# self-contained AppImage.
#
# Two ways to run it:
# - workflow_dispatch (manual): builds the AppImage and uploads it as a downloadable run ARTIFACT
Expand Down
98 changes: 98 additions & 0 deletions .github/workflows/build-macos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: Build macOS

# Build the macOS (Apple Silicon) distributable in the cloud, mirroring build-linux.yml. The runner is
# macos-14 — an arm64 image — because the build is arm64-only (Д7): Intel Macs get no artifact, and a
# universal build would additionally need a universal native `drivelist`.
#
# The dmg is NOT signed with a Developer ID and NOT notarized (Д6 — no Apple Developer account), so it
# does NOT self-update: the macOS build reports the updater as unsupported and the user re-downloads by
# hand. See README (macOS section) for the Gatekeeper steps a first launch needs.
#
# Two ways to run it:
# - workflow_dispatch (manual): builds the dmg/zip and uploads them as run ARTIFACTS (does NOT publish a
# release) — use this to grab a build for testing.
# - pushing a v* tag: also PUBLISHES them into the draft GitHub Release for that version, matching
# build-windows.yml / build-linux.yml.

on:
workflow_dispatch:
push:
tags:
- 'v*'

jobs:
build:
# macos-14 is the Apple Silicon image — it produces the arm64 build natively, with no cross-compile.
runs-on: macos-14
# Needed so electron-builder can upload assets to the GitHub Release (tag pushes only).
permissions:
contents: write

steps:
- name: Checkout
uses: actions/checkout@v5

# Node 22.12+ is required by electron@43 and @electron/rebuild@4 (their `engines`).
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '22'
cache: 'npm'

# npm ci runs postinstall (electron-builder install-app-deps) — the macOS runner ships clang and
# python, so the native drivelist builds for the right ABI (it does compile on macOS; the claim to
# the contrary in the other two workflows was stale).
- name: Install dependencies
run: npm ci

- name: Type check
run: npm run typecheck

- name: Lint
run: npm run lint

- name: Unit tests
run: npm test

- name: Compile TypeScript + assets
run: npm run build

# No `build:umu` step: umu-launcher/Proton is the Linux path, and macOS runs native games only.

# Only when releasing (a v* tag): draft the release up front so electron-builder uploads into it.
# Same rationale/fail-fast as build-windows.yml (avoid split drafts / a silently-empty publish).
- name: Ensure draft release exists
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: |
tag="v$(node -p "require('./package.json').version")"
isDraft=$(gh release view "$tag" --json isDraft --jq .isDraft 2>/dev/null || echo missing)
case "$isDraft" in
missing) gh release create "$tag" --draft --target "$GITHUB_SHA" --title "$tag" --notes "$tag" ;;
true) echo "Draft release $tag already exists — reusing it." ;;
false) echo "::error::Release $tag is already published. electron-builder refuses to upload assets into it. Delete the release (and its tag) and re-run." ; exit 1 ;;
esac
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# Publish (upload dmg/zip to the draft release) ONLY on a v* tag; a manual run just builds locally
# (--publish never) and relies on the artifact upload below.
#
# CSC_IDENTITY_AUTO_DISCOVERY=false stops electron-builder from hunting the runner's keychain for a
# signing certificate. It does not disable signing: `mac.identity: '-'` in electron-builder.yml still
# applies the AD-HOC signature the app needs to launch on Apple Silicon at all.
- name: Package (dmg + zip)
run: npx electron-builder --mac --publish ${{ startsWith(github.ref, 'refs/tags/v') && 'always' || 'never' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_IDENTITY_AUTO_DISCOVERY: false

# Always expose the build as a run artifact so a manual build can be downloaded for testing.
- name: Upload dmg
uses: actions/upload-artifact@v7
with:
name: playhook-macos
path: |
release/*.dmg
if-no-files-found: error
retention-days: 14
4 changes: 2 additions & 2 deletions .github/workflows/build-windows.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Build Windows

# Build the Windows distributable in the cloud: the windows runner has MSVC + Python,
# so the native `drivelist` compiles normally (which is impossible on macOS).
# Build the Windows distributable in the cloud: the windows runner has MSVC + Python, so the native
# `drivelist` compiles for the right ABI without a Windows machine of one's own.
# Triggered manually (workflow_dispatch) or on pushing a v* tag.

on:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
node_modules/
node_modules
dist/
release/
*.log
Expand Down
108 changes: 83 additions & 25 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,27 @@ Conventions for extending Playhook safely. These were distilled from an architec
was: **add features without breaking existing behaviour.** Follow them for new code; they are not a
mandate to rewrite what already works.

## UI text

- **Never type a literal `...` or `…` in user-facing text** (i18n strings, HTML fallback text, anything
rendered through the app's own font). The bundled font (M PLUS Rounded 1c) draws periods and the
ellipsis glyph CENTERED vertically — the CJK convention, not the Latin one — so they sit above the
baseline and read as a row of raised dots instead of trailing punctuation. `styles.css` carves those
two code points out of the font (see the `@font-face … unicode-range: U+002E, U+2026` overrides right
after the four real ones) so the fallback stack draws them properly wherever they DO appear — including
text this app does not author, like a game's own title — but that is a safety net, not a licence: new
copy should still be worded so nothing trails off, rather than leaning on the override.

## Layers (do not blur)

- **main** owns all game logic (fs, registry, process control, FFI). **renderer** is stateless UI.
- They talk **only over IPC**. The renderer never touches fs/registry; main never touches the DOM.
- Preload bridges are typed and sandboxed (`contextIsolation: true`, `sandbox: true`).
- A **pure** function BOTH sides must compute identically (no fs/electron either way) lives in
`src/shared/` alongside `types.ts` and `i18n/` — not duplicated in each layer, and not placed under
`src/main/`: `tsconfig.renderer.json` does not include it and esbuild builds the renderer for the
browser, so a `node:*` import there breaks the build, not just the convention. See
`src/shared/asset-move-names.ts` (move-to-card asset names, computed identically in main and renderer).

## Error-handling convention

Expand Down Expand Up @@ -68,32 +84,48 @@ happened once already, via `logger.ts` and `steam.ts`.

## Platform layer (OS-specific code)

Playhook runs on Windows and on the Steam Deck / Linux (Windows games via Proton/umu-launcher). **All
OS-specific behaviour lives behind the `Platform` bundle in `src/main/platform/`**, not scattered
`process.platform` checks. When you add code that differs per OS:
Playhook runs on **three** OSes: Windows, the Steam Deck / Linux (Windows games via Proton/umu-launcher)
and macOS. macOS is a deliberately narrower port — NATIVE mac games (a bare binary or a `.app` bundle) plus
Steam mode; a Windows `*.exe` does not run there (no Wine/CrossOver), install mode is unsupported, and the
build does not self-update. **All OS-specific behaviour lives behind the `Platform` bundle in
`src/main/platform/`**, not scattered `process.platform` checks. When you add code that differs per OS:

- Add the capability to an interface in `platform/types.ts` (the bundle is `ProcessMonitor`,
`SteamLocator`, `GameProcessLauncher`, `SavePathResolver`, `PowerBackend`, `resolveInstallDir`).
- Implement it in **both** `platform/win32.ts` and `platform/linux.ts` (linux Proton helpers live in
`platform/*.linux.ts` / `umu.ts`). `createPlatform(process.platform)` selects the bundle once at
bootstrap; the rest of the code is platform-agnostic and receives it via DI (`ControllerDeps.platform`).
- **Never change Windows behaviour** when adding the Linux side — the win32 implementation must stay 1:1
(the port's guiding invariant). Keep the OS-neutral fs/parse code (manifest, save-sync, `.acf`/VDF,
drive-watcher) shared — don't fork it.
- Card format is a **Windows dictionary** on both OSes (`%APPDATA%`, `*.exe`, `install.type`); on Linux it
is interpreted relative to the game's Wine prefix. A `game.json` must work unchanged on both platforms —
Linux-only manifest fields (`winetricks`, `umuGameId`) are ignored on Windows, never rejected.
- Extract the pure bits (path/env/argv construction, `/proc` parsing, prefix mapping) into electron-free
helpers and unit-test them (see `umu.ts`, `proc.ts`, `save-path.linux.ts`).
- **Build Linux paths with `path.posix`, never bare `path.join`.** `path.join` follows the OS the code
RUNS on, and CI runs the test suite on Windows too — so a Linux path built with `path.join` comes out as
`\home\deck\...` there and fails a test that (correctly) expects `/home/deck/...`. This has broken the
Windows job repeatedly. In any `*.linux.ts` module — and in any Linux-only feature elsewhere — use
`path.posix.join` / `path.posix.dirname` / `path.posix.basename`. Reference: `umu.ts` `prefixDir`,
`steam-userdata.linux.ts`. The win32 side keeps plain `path.join` (there it is right).
`SteamLocator`, `SteamShortcuts`, `GameProcessLauncher`, `SavePathResolver`, `PowerBackend`,
`RemovableMounter`, `resolveInstallDir`).
- Implement it in **all three** of `platform/win32.ts`, `platform/linux.ts` and `platform/darwin.ts`
(linux Proton helpers live in `platform/*.linux.ts` / `umu.ts`; the macOS ones in `platform/*.darwin.ts`).
`createPlatform(process.platform)` selects the bundle once at bootstrap in an explicit three-way branch
(win32 / darwin / everything-else = linux); the rest of the code is platform-agnostic and receives it via
DI (`ControllerDeps.platform`).
- **Never change the behaviour of an OS you are not porting.** Adding the Linux side must leave win32 1:1;
adding macOS must leave BOTH win32 and linux 1:1 (the port's guiding invariant, and the one most easily
broken by accident — a visibility rule phrased as "only on Linux" silently takes a section away from
Windows too; phrase it as "not on the OS being added"). Keep the OS-neutral fs/parse code (manifest,
save-sync, `.acf`/VDF, drive-watcher) shared — don't fork it.
- Card format is a **Windows dictionary** on every OS (`%APPDATA%`, `*.exe`, `install.type`), interpreted
per platform: on Linux relative to the game's Wine prefix; on macOS translated into the mac profile
(`%APPDATA%`/`%LOCALAPPDATA%`/`%LOCALLOW%` → `~/Library/Application Support`, `%USERPROFILE%` → `~`,
`%DOCUMENTS%` → `~/Documents`) while a card whose `executable` is a `*.exe` simply refuses to launch
there. A `game.json` must work unchanged wherever it CAN work — Linux-only manifest fields (`winetricks`,
`umuGameId`) are ignored elsewhere, never rejected. `watchProcesses` names may omit the `.exe` suffix
(a native mac process has none); keep the `*.exe` spelling on a card meant to travel — the macOS matcher
normalizes the suffix away, so one spelling matches on all three.
- Extract the pure bits (path/env/argv construction, `/proc` and `ps` parsing, prefix mapping) into
electron-free helpers and unit-test them (see `umu.ts`, `proc.ts`, `save-path.linux.ts`,
`process-monitor.darwin.ts`, `save-path.darwin.ts`).
- **Build Linux AND macOS paths with `path.posix`, never bare `path.join`.** `path.join` follows the OS the
code RUNS on, and CI runs the test suite on Windows too — so a Linux path built with `path.join` comes
out as `\home\deck\...` there and fails a test that (correctly) expects `/home/deck/...`. This has broken
the Windows job repeatedly. The rule applies verbatim to macOS: `path.join('~/Library/Application
Support', …)` in a darwin module yields `\Library\…` on the Windows runner. In any `*.linux.ts` or
`*.darwin.ts` module — and in any OS-specific feature elsewhere — use `path.posix.join` /
`path.posix.dirname` / `path.posix.basename`. Reference: `umu.ts` `prefixDir`, `steam-userdata.linux.ts`,
`steam-locator.darwin.ts`. The win32 side keeps plain `path.join` (there it is right).
Beware the silent variant: when a value is *derived* from a path (the Steam shortcut appid is a CRC32 of
it), a wrong separator does not fail loudly — it produces a wrong value.
Quick check before pushing: `grep -rn "path\.\(join\|dirname\|basename\|resolve\)(" src/main/platform/*.linux.ts`
Quick check before pushing:
`grep -rn "path\.\(join\|dirname\|basename\|resolve\)(" src/main/platform/*.{linux,darwin}.ts`

## Tests

Expand All @@ -104,17 +136,43 @@ OS-specific behaviour lives behind the `Platform` bundle in `src/main/platform/`
was) and test that.
- Prefer covering the risky, data-touching functions: manifest validation/anti-traversal, stats merge,
save-sync retry, argument quoting.
- **The suite runs on Windows AND Linux in CI, so a green local run proves nothing about path handling.**
- **DOM tests of the renderer's screen controllers live in `test/renderer/**`** and run under
**happy-dom** instead of plain Node (`environmentMatchGlobs` in `vitest.config.ts` — scoped by glob, so
every other suite keeps its Node environment and its POSIX path literals). A controller is testable
there because it is a factory taking a narrow `…Deps` seam: the fixture is the REAL
`src/renderer/index.html` (loaded by `test/renderer/helpers/fixture.ts`, so every id `req()` asks for
has to exist), the deps are faked (`helpers/fakes.ts`: audio, the screen APIs, the keyboard / file
picker / online picker surfaces), and the translator is the real `createTranslator('en')`. Input is the
`NavSurface` primitives called directly — no gamepad polling; the hover/veil branches are reachable
through `hoverOver()` (they all sit behind the `mouse-asleep` class the fixture starts with).
Four rules that bite:
- **The screens that fetch their own data open ASYNCHRONOUSLY** — `filePicker.open()` awaits `listDir`,
`gameSettings.open(id)` awaits the manifest read, so assert after `await flushAsync()`. `SettingsScreen`
is the exception: its `open()` is synchronous and the snapshot arrives through `applySettings()`.
- **rAF must be the harness in `helpers/raf.ts`, with a frame-BOUNDED `flush(n)`** — the marquees
reschedule themselves forever while element widths are zero, which they always are without layout.
- **Load the fixture per test** (`beforeEach`), and create the controller after it: no controller removes
its listeners, so a fixture shared across a file collects one live instance per test on the same nodes.
- **`app.ts` stays out** — it touches `window.api` at module scope.
Covered so far: `screen-sidebar`, `osk`, `file-picker`, `settings-screen`, `game-settings-screen`. Still
uncovered and next in line for the same base: `controls.ts`, `online-picker.ts`, `library-screen.ts`,
`carousel.ts`. Anything needing real layout (`scrollHeight`, canvas) is still a manual check on the Deck.
Upgrade note: `environmentMatchGlobs` is deprecated in vitest 3 and GONE in vitest 4 — an upgrade must
move `test/renderer/**` to `test.projects` (or a per-file `@vitest-environment` docblock) or the suites
will quietly run in Node again and fail on `document is not defined`.
- **The suite runs on Windows, Linux AND macOS in CI, so a green local run proves nothing about path
handling.**
A test that asserts a Linux path against a literal (`expect(...).toBe('/home/deck/...')`) is correct and
should stay — it is the *source* that must use `path.posix` (see the platform-layer rule above). Never
"fix" such a failure by rewriting the expectation with `path.join`: that makes the test assert whatever
the code does and stops testing anything at all.

## Tooling (all run in CI before build)

- `npm run typecheck` — strict `tsc`, no `any`, no non-null `!`.
- `npm run typecheck` — strict `tsc`, no `any`, no non-null `!`. Covers `test/` as well as `src/`.
- `npm run lint` — ESLint with type-aware rules (`no-floating-promises`, `no-misused-promises`,
`strict-boolean-expressions`).
`strict-boolean-expressions`), over `src` and `test`. Tests switch off `require-await` and
`unbound-method` (both only ever fire on test doubles) and allow a `_`-prefixed unused parameter.
- `npm test` — vitest.
- `npm run format` / `format:check` — Prettier (available for new code; the existing hand-aligned
files are intentionally not mass-reformatted).
Loading