What Piano shipped.
Every entry in docs/CHANGELOG.md, read at 51ba9d7 and dated by the commit that wrote it.
- PI111 · 55a9c5cPiano has no public page, README or LICENSE, so nobody finds it and the MIT claim points at no file
Piano has a public site at /piano/ on freewilly's model, its facts generated from the repo, deployed with the sample pack, plus LICENSE, README and CONTRIBUTING (design recorded in
site/README.md). - PI110 · 6e4672fno installer survives the release workflow, so v0.1.0 cannot be published
All three installers now package, install and pass the association check in CI: Linux names its executable, Windows lets go of .piano, macOS reads split claims.
- PI109 · 595940fA chosen option is told from the ones beside it by the weight of its text, which reads as no difference
A chosen option carries the accent and is measured against its neighbours in both themes, which also caught the light accent failing its own text contrast.
- PI108 · dafae33Open recent still offers a deleted piece, and the system's own list still offers one at the path it has moved from
A deleted piece leaves Open recent with it, and the system's own list is rebuilt from ours whenever ours changes, so neither offers a file that has gone.
- PI107 · 047a05cDeleting or retitling from chat skips the bin and leaves the practice records behind, where the panel does neither
With no window a delete says it cannot be undone before doing it, and a correction leaves a note the app applies to the records next launch.
- PI106 · 4da6187Open recent names a piece by the title it had when it was opened, so a corrected one is listed under its old name
A corrected piece is offered from Open recent under what it is called now, at the path it moved to, without waiting to be opened again.
- PI105 · 27ca9bcTidying a library that grew by the batch means opening, confirming and closing one row at a time
Rows can be picked in the panel and then deleted or tagged together, with one question asked of the whole set and one sentence about how it went.
- PI104 · a9dcd39Claude Code can write a score into the library but never remove one, nor correct what one says without rewriting it
correct_score changes what a score says about itself and delete_score takes one out, both through the library module the window writes with.
- PI103 · d602c8ePractice history is keyed by a score's id or, where it has none, its title: renaming a piece starts its record over
A correction gives a piece the id it is filed under, its records move to that key, and deleting says the attempts recorded against it are kept.
- PI102 · bf0ee6fA retitled piece keeps the file name and id it was filed under, so the panel, the folder and chat disagree
Retitling a piece moves it to the name that title gives, asks about a name already taken the way filing does, and says what it is filed as now.
- PI101 · 77c6d8aNothing in the app removes a score: a piece filed by mistake leaves the library only through the file manager
A row offers to delete, asks once naming the piece by title, composer and length, and sends the file to the system's bin, so a wrong click can be put back from there.
- PI100 · d0aae17A piece's title, composer, level and tags can be set only as it is filed, never corrected afterwards
A row in the library opens the filing form filled in, and saving it writes the corrected title, composer, level, difficulty and tags back into the same file, leaving the notes alone.
- PI99 · c01a49cA file the library folder cannot take in stays there unlisted, and only the log says why
The Library panel names every file the folder would not take in and why, and offers to open the ones whose only problem is a name already used.
- PI98 · c2ceb47The empty library tells people to ask Claude Code and never says that a MIDI file of their own can go in
The empty library names both ways in and offers the second as a door: ask Claude Code for a piece, or open a file already on the disk.
- PI97 · 535c175A MIDI or MusicXML file copied into the library folder is ignored by the index, and nothing on screen says why
A MIDI or MusicXML file copied into the library folder is imported once and filed as a score beside it, so a folder of downloads becomes a library.
- PI96 · 1117e24An imported piece is filed with a title and nothing else: no composer, no level, no tags, so no filter finds it
Filing asks for the composer, level and tags in one form, prefilled from the file, so an imported piece is one the list's own filters reach.
- PI95 · a5cb1aaAn import filed under a title the library already holds overwrites it, with nothing asked and nothing said
Filing a piece whose id is taken says what is there and offers to number the new one or replace the old, so two pieces with one title both survive.
- PI94 · a87cb47Nothing in the window puts a score into the library: an imported MIDI or MusicXML piece is gone when it closes
An Add to library button files the open score through the library package, so an imported MIDI or MusicXML piece is listed and playable next launch.
- PI93 · e2c1073An import whose repeats and jumps expand past 4,000 bars is cut off mid-piece and nothing says it was cut
An import stopped by the 4,000-bar ceiling now says so in
dropped, told apart from one that simply reached the end of the piece. - PI92 · aec7643The installed-app check hands over a .piano only, so the MIDI and MusicXML it registers are never opened end to end
The installed app is now started with a .piano, a .mid, a .musicxml and a .mxl, and has to say it opened each one.
- PI91 · ce1fbdbThe transport bar's first button is wrapped in two hints, and the outer one can never open
The transport bar's first button carries one hint instead of two, and a test now covers its tooltip beside the loop button's.
- PI90 · 6b9c889A da capo or dal segno is reported and not followed, so an imported piece ends where the writing stops
A da capo, a dal segno, a to coda and a fine are now followed, so an imported piece plays its jumps the way a reader takes them.
- PI89 · c7d6be3A .musicxml or .mxl double-clicked in the file manager opens something else, the installer claiming neither
A .musicxml or .mxl now offers the piano under Open With on Windows, as an Alternate claim on macOS and a MIME type on Linux, taking neither from a notation editor.
- PI88 · 1ddc70aA MusicXML file cannot be opened, so the stave guesses spellings, hands and fingering the file already states
MusicXML opens as a score, keeping the spellings, hands, fingering and articulation the file wrote down, as .musicxml, .xml or a zipped .mxl.
- PI87 · 99a608dA parts row shows a speaker, an S and an eye, and nothing says which of them silences and which hides
Hovering or focusing a parts row's speaker, S or eye now says which part it silences, solos or hides, in the button's own words.
- PI86 · 07ecad1The transport bar is a row of icons, and a pointer resting on one is told nothing
Every icon in the bar says its own name on hover and on focus, read off the button so there is one string to keep right (design superseded: a hint carries its own provider: panels mount alone).
- PI85 · 08e2a06Moving the stave's zoom slider changes nothing on the page
The slider magnifies the page now: App passes the setting the stave was always able to use, and a test drives the whole path.
- PI84 · e7d0281A library row whose composer is a sentence shows no title: the chips squeeze it to nothing
Every library row shows its title: the chips are capped at half the row and truncate, and a browser test measures the width rather than assuming it.
- PI83 · 83108ceThe stave is drawn at one size, and on a dense score its notes are too small to read
The stave has a zoom of its own now, and the page follows the sounding bar sideways as well as down (design superseded: one magnifier showing the open view's slider, not two at once).
- PI82 · 64ae001The button that leaves the stave shows the same note glyph as the MIDI monitor two places along
The button now wears a piano and the monitor keeps its note, so the two no longer read as one control (design superseded: jsdom sees an icon: it is inline SVG, not a matter of layout).
- PI81 · 344d8e8Notes in a busy bar are drawn on top of each other, so a page of sixteenths is unreadable
A bar is now as wide as its figures need, so a page of sixteenths reads instead of stacking its notes on each other.
- PI80 · 1e8209cNoteheads, clefs and rests stay black in the dark theme, which is the theme the app opens on
Every glyph on the page now carries a token, so the stave reads in the dark theme instead of sitting black on a dark panel (design superseded: clefs and barlines are the stave's siblings).
- PI79 · 52002a1Eighth notes and shorter are drawn with a flag each, so the page shows no beat grouping
Eighths are beamed by the beat, in threes where the meter is compound, and left flagged where it is odd, which the page says it did.
- PI78 · 6e6ea7fEvery launch loads the engraver and its music font, even for a player who never opens the stave
The engraver and its font are a chunk of their own, fetched when somebody opens the stave, and the entry chunk is 598 kB again (design recorded in
apps/desktop/src/renderer/lib/sheet-draw.ts). - PI77 · 3814f5dThe view infers clefs, accidentals and rests, then shows the guess as if it were the score
The page now says which of its readings are the app's: the clefs from hands, the key, the sharps it chose, the gaps it drew as rests, the ties it could not mark and the figures it rounded.
- PI76 · ebd9b61VexFlow draws black on white, unreadable in the dark theme and outside the app's colour system
The engraving takes its ink, bar lines and stave lines from tokens, and reads in either theme (design superseded: handing colours in is the detour a canvas needs; a stave is SVG, which CSS colours).
- PI75 · c20832dThe stave stands still while the piece plays, so the reader loses their place by the second bar
The stave follows the clock: the sounding bar banded, its notes lit with the roll's own verdicts, and the page turning by system (design recorded in
apps/desktop/src/renderer/lib/sheet-follow.ts). - PI74 · 2bdf209Nothing on screen reaches the sheet view: the roll owns the centre of the window
A button in the bar swaps the centre of the window between the roll and the stave, and the window reopens on whichever was last read.
- PI73 · 1c1b66eThe piece has no written form: the falling roll is the only way to read what comes next
The open piece now reads as staves, one per hand, wrapped to the panel and drawn by VexFlow (design recorded in
apps/desktop/src/renderer/components/SheetMusic.tsx). - PI72 · 2e2b687Durations are ticks, so nothing says whether a note is a dotted quarter or what fills the gap after it
Ticks now read as a figure and its dots, and one bar of one hand as the figures and rests that tile it, each with its leftover (design recorded in
packages/score-format/src/time.ts). - PI71 · e2ba877The design system's gallery renders in the app, showing sample controls to whoever opens the piano
The app ends at its footer: the design system's gallery is a page for whoever builds it, mounted by its own test and nothing else.
- PI70 · 5676537Nothing starts the installed app with a score and watches it open that score
A headless run started with a score says which one it opened, and the association check starts the installed app to read that line.
- PI69 · 974f8b5The report panel's copy button hands over every piece's history from a view about one piece
The report panel's copy hands over the piece on screen, as its neighbour forgets it; the whole history stays the footer's door.
- PI68 · 1adb249Nothing exercises the file associations an installer writes, so a wrong macro or plist key ships unnoticed
An installed piano is read back from the registry, Launch Services and the desktop entry, in CI and before a release (design recorded in
scripts/associations.mjs). - PI67 · f8d8d01Practice history sits unvalidated in browser storage, and nothing in the app exports or deletes it
Practice history is a validated, versioned file in the profile, carried over from browser storage, with footer doors to save a copy or delete it (design recorded in
packages/ipc/src/history.ts). - PI66 · 131eae9Double-clicking a score in the file manager never reaches the piano: no file type is registered
Scores are saved as .piano, one extension the app claims on every system, while MIDI is only offered under Open With (design recorded in
apps/desktop/electron-builder.yml). - PI65 · bfc3a0dvalidate_score passes a score with the left hand above the right, a chord no hand can reach, or a bar half full
validate_score and save_score warn beside a valid result of crossed hands, unreachable chords and bars that don't add up (design recorded in
packages/score-format/src/warnings.ts). - PI64 · ccac422Nothing is published to download: the plugin sends a person without the app to an empty releases page
A v* tag builds installers on each OS into a draft release, serves the pack from Pages, checks it downloads, then publishes (design recorded in
.github/workflows/release.yml). - PI63 · 4bd599bA worked-out arrangement lives for one session: it is derived again at every launch and a correction has nowhere to go
"Keep this version in the score" writes the worked-out arrangement into the open file, signed; a correction stays (design recorded in
packages/score-format/src/arrangement.ts). - PI62 · f010212The roll says a note was late but not by how much, so a player who is always a hair early cannot see it
Each right strike leaves a fading dash above the strike line, early above the middle and late below, so a bias reads as a row (design recorded in
apps/desktop/src/renderer/lib/timing-lane.ts). - PI61 · e8bc2ddThe synthesised fallback and the recordings are not matched in loudness or onset, so the handover can be heard
Measured on the pack, the synth was 7-30 dB loud and 3 ms early; fitted, it is 3 dB off on average and on the hammer (design recorded in
apps/desktop/src/renderer/audio/synth-engine.ts). - PI60 · 15b5821The format-ownership guard reads a split "type Score," import line as a declaration and fails the lint
The ownership guard now matches a Score type only where a declaration follows the name, so a split import passes and a local declaration still fails.
- PI59 · 159baf4With the window hidden, Chromium may throttle the timer that wakes the scheduler, and notes then arrive late
Minimised, the window woke 3 times in 2.5 s; unthrottled, every 25 ms wake stays inside the look-ahead, checked live (design recorded in
apps/desktop/src/main/window-preferences.ts). - PI58 · 199e419Nothing in the app saves a score as MIDI, so the way into a DAW that the recording non-goal promises is unreachable
Save as MIDI (Ctrl+E or the header) writes what is playing, at its level, where asked, then says what MIDI could not keep (design recorded in
apps/desktop/src/main/score-export.ts). - PI55 · a3abdabA new install opens on an empty library, so there is nothing to hear and nothing to try
Ode to Joy, the Minuet in G and the Prelude in C ship public domain with three authored arrangements each, seeded on first launch (design recorded in
apps/desktop/src/main/bundled/index.ts). - PI54 · a02df6bThe sample pack cannot ship inside the installer, and there is no way to fetch it
The app offers the pack with its size, downloads it in main as the synth plays, resumes by range, checks each sha256, installs it whole (design recorded in
apps/desktop/src/main/pack-download.ts). - PI53 · 0a0e705Every setting resets on restart: device, theme, calibration and level are chosen again each time
Device, calibration, theme, level, effects, lead time and strictness live in one validated file main owns; a bad file falls back to defaults, said (design recorded in
packages/ipc/src/settings.ts). - PI52 · abc9ae7Scores pile up in a folder with no index: nothing lists, searches or filters them
One library package keeps an index the MCP tools and the app's Library panel both read; scores copied in by hand appear, a torn index is rebuilt (design recorded in
packages/library/src/library.ts). - PI51 · 2ded6a7There is no way to open a file: a score sitting on disk cannot be loaded into the app at all
Dialog, drop, recent list, command line and Claude Code's library id open a score through one validation; a refused file leaves the open piece (design recorded in
apps/desktop/src/main/opener.ts). - PI50 · 2f733d6Nothing proves the premise: no single run goes from a request to a piece actually playing
A recorded model score goes through the plugin's server into the built app, which opens and plays it; its opening is heard note by note, in CI (design recorded in
apps/desktop/src/test/premise.ts). - PI49 · f3e6ae5A local port that accepts scores and plays them is an open door on the machine
The link answers on loopback only and its token file is readable by its owner alone; ids from any caller land inside the library, never on a device name, and a record cannot start another program.
- PI48 · 881fce1Asking to play a piece fails whenever the app is closed, which is most of the time
A request for music with no window open starts the installed app once, waits for it to listen and plays; a running one is brought forward (design recorded in
apps/mcp-server/src/launch.ts). - PI47 · 60c5e85A rejected score comes back as a validation dump the model cannot act on
A refusal names the path, what arrived, what was wanted and the fix where one exists, grouped and bounded, so a model repairs its own score (design recorded in
packages/score-format/src/repair.ts). - PI46 · 2dd8cfbA model writing a score guesses at the format and produces files that almost validate
The plugin's piano-score skill teaches the format by a complete example that validates, and names the musical mistakes no validator catches.
- PI45 · ea11474There is no plugin: the tools only work for someone who wires an MCP server by hand
The repository is a plugin marketplace whose plugin carries its own bundled server and four commands, and a missing app is answered with where to get it.
- PI44 · b3483c4The MCP server and the app are separate processes with no way to find each other
A tool call finds the most recently focused live window from its presence file and plays it there, and a version mismatch says which side to update.
- PI43 · 9d70688Claude Code cannot reach the app: there is no way to send a score or start playback
The tools say what somebody asks for — validate, save, list, play, seek, practise — and the library is the only thing it touches (design recorded in
apps/mcp-server/src/tools.ts). - PI42 · ceecb1fEvery session starts from nothing: no record of what was played, what improved or what still fails
Every graded attempt is kept against the piece and its notes, and the app names the bars that keep failing (design recorded in
apps/desktop/src/renderer/lib/progress.ts). - PI41 · 97e2457A hard bar can only be practised by replaying the whole piece and waiting for it to arrive
A passage repeats a graded pass at a time, climbing after a clean one, dropping back after a failure, hands apart (design recorded in
apps/desktop/src/renderer/lib/drill.ts). - PI40 · 3df13ebOnly scores hand-authored with three arrangements are playable at beginner level
Five rules work out a simpler version of any score, and a hand-authored arrangement still wins wherever one exists (design recorded in
packages/score-format/src/reduce.ts). - PI39 · e613206Beginner, intermediate and advanced are only words: nothing says what changes between them
Each level names a tempo, the hands you take, the window and what a reduction keeps, and choosing one sets them without locking them (design recorded in
apps/desktop/src/renderer/lib/levels.ts). - PI38 · acb8236The player gets no feedback: nothing on screen says which note was right, wrong or late
The key and the note take the judgement's colour as each strike lands, and a note nobody played goes by outlined instead of vanishing.
- PI37 · e35e20cNothing says how the attempt went: wrong notes, late notes and missed notes all pass unremarked
An attempt is graded against a timing window the learner sets, and the report names the bars to go back to, with the touch apart (design recorded in
apps/desktop/src/renderer/lib/grading.ts). - PI36 · c234042Playback runs away from a beginner, who cannot keep up and has nothing to practise against
The transport holds at the next note the player owes, lights those keys, and carries on when they are struck (design recorded in
apps/desktop/src/renderer/lib/practice.ts). - PI35 · 5192df8Input and output latency are unknown, so a player who is in time gets graded late
Output latency comes from the platform and input latency from a dozen strikes against a click, kept per device and shown plainly (design recorded in
apps/desktop/src/renderer/lib/latency.ts). - PI34 · 8c92508Without a MIDI keyboard there is no way to play at all, so most people cannot try the app
Two rows of the typing keyboard play two octaves, shiftable and stated, and both inputs now sound the piano at the pitch pressed (design recorded in
apps/desktop/src/renderer/lib/keys-input.ts). - PI33 · 204cc96A MIDI keyboard plugged into the machine is invisible: nothing receives a single note
Web MIDI is granted in main and normalised in the renderer, with hot-plug, a remembered device and a monitor showing raw events (design recorded in
apps/desktop/src/renderer/lib/midi-input.ts). - PI32 · 3020e58Colours are chosen ad hoc, so the roll is unreadable in one theme and ambiguous under colour blindness
Note and practice colours are a measured lightness ladder in both themes, so they stay apart when hue does not help (design recorded in
apps/desktop/src/renderer/lib/contrast.ts). - PI31 · d8cd9b4Every part sounds and draws at once, so a learner cannot isolate one hand or one voice
A panel mutes, solos and hides each part and each hand, and the scheduler applies it per note as it hands one over (design recorded in
apps/desktop/src/renderer/lib/parts.ts). - PI30 · 5036c10There are no controls: playback cannot be started, scrubbed, slowed or transposed from the screen
One bar carries play, restart, loop, time, tempo, transpose, zoom, effects and full screen, showing what the transport did (design recorded in
apps/desktop/src/renderer/components/TransportBar.tsx). - PI29 · 9a3d794The roll has no structure: nothing shows where a bar starts or which bar is playing
Bar lines, beat lines and numbers come from the time signature map, the current bar is lit, and a drag across the field loops whole bars (design recorded in
apps/desktop/src/renderer/lib/bars.ts). - PI28 · d74e2e4Notes reach the keyboard and nothing happens: the moment of contact is invisible
A note reaching the keyboard throws a burst from a fixed pool and flashes its key, fired by the scheduler's own strike event (design recorded in
apps/desktop/src/renderer/lib/strikes.ts). - PI27 · 802dc0eA dense passage drops frames, so the roll stutters exactly when the music gets interesting
The roll binary-searches its visible notes and fills them by colour in one path, holding 0.2ms for the 95th frame of a dense fixture (design recorded in
apps/desktop/src/renderer/lib/roll-draw.ts). - PI26 · abb03aeNotes do not fall: there is nothing between the score and the screen
Notes fall onto the keyboard from the audio clock, each in its own key's column, and the keys they strike light up (design recorded in
apps/desktop/src/renderer/lib/roll.ts). - PI25 · 794b282There is no keyboard on screen: nothing shows which key a note belongs to or which is sounding
An 88-key keyboard lays black keys out as a real one does, sizes itself to the window, and shows a state per key from outside (design recorded in
apps/desktop/src/renderer/lib/keyboard-geometry.ts). - PI24 · 08e38e4Practising without a beat reference is guesswork, and playback starts with no warning
The transport clicks every beat of the meter with the downbeat accented, and counts in a bar at practice tempo it marks unscored (design recorded in
apps/desktop/src/renderer/audio/beats.ts). - PI23 · 99fcf1bThere is no transport: nothing plays, pauses, seeks, loops or changes tempo
A transport plays, pauses, seeks, loops, rescales tempo and transposes on the audio clock; each note sounds at the reported tick (design recorded in
apps/desktop/src/renderer/audio/transport.ts). - PI22 · c102637One sample per note sounds like a toy: no dynamics, no pedal resonance, no key release
Velocity picks and crossfades recordings, half pedal damps partially, and every key coming up plays its release recording (design recorded in
apps/desktop/src/renderer/audio/keyboard.ts). - PI21 · 57e3aafLoading hundreds of megabytes of samples before the first note makes the app feel broken
The app reads the installed pack through a checked bridge channel, loads it behind the synthesiser and credits it in the footer (design recorded in
apps/desktop/src/renderer/audio/pack-bank.ts). - PI20 · 31c5f15There is no piano sound: raw sample libraries are gigabytes of WAV, unusable as they ship
npm run pack:samples builds a reproducible 16 MB Opus pack from pinned, checksummed Salamander sources, with manifest and licence (design recorded in
packages/sample-pack/scripts/build-pack.mts). - PI19 · df7e474Notes scheduled from timers drift audibly: timing wanders and chords stop landing together
A look-ahead scheduler stamps each event with its exact audio time; chords share one time and late wake-ups miss no onset (design recorded in
apps/desktop/src/renderer/audio/scheduler.ts). - PI18 · 9572e08There is no way to produce sound, and no seam between the engine and everything that drives it
PianoEngine fronts a synthesised and a sampled engine that swap mid-piece, and lint keeps Web Audio inside renderer/audio (design recorded in
apps/desktop/src/renderer/audio/engine.ts). - PI17 · 871da3fThe format exchanges with nothing: MIDI files cannot come in and no score can go out
importMidi reads a MIDI file into a valid score and exportMidi writes one; both list what they dropped, and import marks what it guessed (design recorded in
packages/score-format/src/midi.ts). - PI56 · d4e6ae1npm run dev opens two identical windows, and an edit to main spawns another instead of replacing it
npm run dev opens one window and an edit to main replaces that process rather than adding a second, with dev and smoke runs on separate Electron profiles.
- PI16 · 72ef5a2There are no reference scores, so nothing proves a format change kept old files readable
Sixteen reference scores cover the hard cases, each round-trips identically after normalisation, and a checksum stops a frozen fixture being edited to suit its own migration.
- PI15 · c314d0eNothing validates a score, so a malformed file fails somewhere deep inside the audio engine
One zod schema produces the types, the runtime validator and the published JSON Schema, and a bad score is refused at the door naming the path and the value.
- PI14 · 190448dThe format has no version, so a file written today stops opening the moment a field changes
Every score declares a version that a migration chain walks forward, and a field the format does not define is an error naming the nearest real one.
- PI13 · 87d0718A score carries no title, composer, key or licence, so the library has nothing to list or filter
Every score carries title, composer, level, tags and provenance, so a library can search and sort it and a packaging check can refuse an unlicensed one.
- PI12 · 218b1adNothing in the format says what beginner, intermediate and advanced mean for a given piece
Three levels resolve from one note set as filters and overrides, so correcting a wrong note in the source corrects every level without a per-level edit.
- PI11 · 2967a4dThere is no way to name a passage, so nobody can ask to play the chorus or bars 12 to 20
A passage has a stable id that resolves to a tick range, and a bar range resolves through the time signature map to exactly the same ticks.
- PI10 · fbf6ec2Sustain pedal, dynamics and articulation are unrepresented, so playback sounds mechanical
Pedal is a timed event covering everything between down and up, and dynamics multiply written velocity so an accent stays above its neighbours through a crescendo.
- PI9 · d80040bA score is one flat list of notes, so the parts panel has nothing to mute, solo or colour
Notes group into named parts that can be muted, soloed or filtered by hand, and a score declaring no parts gets one implicit part and plays.
- PI8 · 93d6d6cThere is no note model: pitch, length, dynamics, hand and fingering have nowhere to live
A note carries pitch, spelling, tick length, velocity, voice, hand and finger through a round trip, and one voice overlapping itself is named as an error.
- PI7 · 6b8d98fThere is no time model: nothing says where a note starts, and seconds break the moment tempo changes
Positions are integer ticks converted through one tempo map, so a mid-piece tempo change lands on its bar and halving the tempo moves nothing.
- PI6 · 651272fThere is no installer: the app only runs in development mode, on the machine that built it
One command produces a 107 MB Windows installer whose packaged app starts and passes all twelve self-checks, with a CI matrix for macOS and Linux beside it.
- PI5 · c6270c5Nothing checks the code: with no tests, no lint and no gate, a regression only shows up while playing
Typecheck, lint and 35 tests run from one command, split so the fast half takes under a second and the half that starts Electron or a browser is a gate.
- PI4 · 4bf4c2eThere is no design system: every screen would invent its own colour, spacing and components
Tailwind and shadcn components render under a dark and a light theme, and every colour resolves to one token file that the React chrome and the canvas both read.
- PI3 · 4284732The renderer can reach Node and the file system, and IPC messages carry no type
The renderer reaches nothing but a named bridge, and every IPC channel is declared once and parsed on arrival in main, proven by a self-check inside a live Electron.
- PI2 · f8164d2The score format has to run inside the app and inside the MCP server, with no duplicated code
The score format lives in one package that the desktop app and the MCP server both import, and a guard plus one typecheck prove neither keeps a second copy.
- PI1 · 04a4bcfThere is no application: nothing opens a window, nothing compiles and there is no way to run it
The app builds and runs: npm run dev opens an Electron window with React reloading, and tsc -b typechecks main, preload, renderer and the build scripts.