Skip to content

Commit d009fab

Browse files
grauxmusicclaude
andcommitted
build(win): fresh Windows VST3+installer from current Source, v1.0.8
- Commit current Source (PluginProcessor, CompanionLink, LocalDetector, etc.) so CI builds CURRENT code, not the stale May-27 HEAD. - Unify version to 1.0.8 across CMakeLists, package.json, .jucer. - CI: build-windows + build-installer-win on windows-latest with timeouts; versioned Windows artifact name (was hardcoded 1.0.0); version resolved from CMakeLists; build-mac moved to macos-14 so runs stop timing out at 24h. - Add CompanionWin/assets (icon.ico) + package-lock.json for a real app icon and reproducible npm install. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3fd3ee8 commit d009fab

16 files changed

Lines changed: 4186 additions & 71 deletions

.github/workflows/build-plugin.yml

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ permissions:
1717

1818
jobs:
1919
# ── Mac (AU + VST3, Universal) ─────────────────────────────────────────────
20+
# macos-13 runners frequently never get allocated and time out at 24h, which
21+
# cancels the whole run and visually masks the Windows jobs that DID succeed.
22+
# macos-14 (Apple silicon) is allocated reliably and still cross-builds the
23+
# universal binary via CMAKE_OSX_ARCHITECTURES below.
2024
build-mac:
21-
runs-on: macos-13
25+
runs-on: macos-14
2226
steps:
2327
- uses: actions/checkout@v4
2428

@@ -155,10 +159,22 @@ jobs:
155159

156160
# ── Windows (VST3, x64) ────────────────────────────────────────────────────
157161
build-windows:
158-
runs-on: windows-2022
162+
runs-on: windows-latest
163+
timeout-minutes: 30
164+
outputs:
165+
version: ${{ steps.ver.outputs.version }}
159166
steps:
160167
- uses: actions/checkout@v4
161168

169+
- name: Resolve version (single source of truth = CMakeLists.txt)
170+
id: ver
171+
shell: bash
172+
run: |
173+
VER=$(grep -Eo 'project\(Morph VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+')
174+
if [ -z "$VER" ]; then echo "Could not parse version from CMakeLists.txt"; exit 1; fi
175+
echo "version=$VER" >> "$GITHUB_OUTPUT"
176+
echo "Resolved version: $VER"
177+
162178
- name: Clone JUCE
163179
run: git clone --depth=1 --branch=8.0.12 https://github.com/juce-framework/JUCE.git ../JUCE
164180

@@ -196,7 +212,8 @@ jobs:
196212

197213
# ── Windows installer (VST3 + Electron companion, bundled) ────────────────
198214
build-installer-win:
199-
runs-on: windows-2022
215+
runs-on: windows-latest
216+
timeout-minutes: 20
200217
needs: build-windows
201218
defaults:
202219
run:
@@ -222,11 +239,20 @@ jobs:
222239
env:
223240
CSC_IDENTITY_AUTO_DISCOVERY: false
224241

242+
# win.artifactName in package.json is the unversioned Water_Morph_Windows.exe.
243+
# Copy it to a versioned filename so every build is traceable.
244+
- name: Version the installer filename
245+
shell: bash
246+
run: |
247+
VER="${{ needs.build-windows.outputs.version }}"
248+
cp "dist/Water_Morph_Windows.exe" "dist/Water_Morph_${VER}_Windows.exe"
249+
ls -la dist/*.exe
250+
225251
- name: Upload Windows installer
226252
uses: actions/upload-artifact@v4
227253
with:
228-
name: Water_Morph_1.0.0_Windows
229-
path: CompanionWin/dist/*.exe
254+
name: Water_Morph_${{ needs.build-windows.outputs.version }}_Windows
255+
path: CompanionWin/dist/Water_Morph_${{ needs.build-windows.outputs.version }}_Windows.exe
230256
retention-days: 14
231257

232258
- name: Publish Windows to GitHub Release
@@ -236,4 +262,4 @@ jobs:
236262
name: "Water Morph — Latest Build"
237263
prerelease: true
238264
make_latest: true
239-
files: CompanionWin/dist/*.exe
265+
files: CompanionWin/dist/Water_Morph_${{ needs.build-windows.outputs.version }}_Windows.exe

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
cmake_minimum_required(VERSION 3.22)
2-
project(Morph VERSION 1.0.7)
2+
project(Morph VERSION 1.0.8)
33

44
# JUCE_PATH can be overridden via -DJUCE_PATH=... on the command line
55
if(NOT DEFINED JUCE_PATH)

CompanionWin/assets/icon.icns

293 KB
Binary file not shown.

CompanionWin/assets/icon.ico

32.9 KB
Binary file not shown.

CompanionWin/installer.nsh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,19 @@
33
; Standard user VST3 path: %APPDATA%\VST3\ — recognized by FL Studio, Ableton,
44
; Bitwig, Studio One, Reaper, and most modern DAWs on Windows.
55

6+
!macro customInit
7+
; Kill any running Water Morph process BEFORE NSIS pages load.
8+
; This prevents the "cannot be closed" dialog entirely.
9+
; /F = force, /T = kill child processes (Electron spawns GPU/renderer helpers).
10+
nsExec::ExecToLog 'cmd /C taskkill /F /T /IM "Water Morph.exe" 2>nul & exit 0'
11+
; Wait for OS to fully release file handles.
12+
Sleep 1200
13+
!macroend
14+
615
!macro customInstall
16+
; Safety net: kill again in case the process restarted (e.g. autostart).
17+
nsExec::ExecToLog 'cmd /C taskkill /F /T /IM "Water Morph.exe" 2>nul & exit 0'
18+
Sleep 500
719
; $APPDATA = C:\Users\<user>\AppData\Roaming (no admin required)
820
CreateDirectory "$APPDATA\VST3"
921
nsExec::ExecToLog 'cmd /C xcopy /E /I /Y "$INSTDIR\resources\Morph.vst3" "$APPDATA\VST3\Morph.vst3"'

CompanionWin/main.js

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const os = require('os')
1010
// ── Constants ────────────────────────────────────────────────────────────────
1111

1212
const kPort = 59812
13-
const kBaseURL = 'https://water.95ent.ai'
13+
const kBaseURL = 'https://water.grauxmusic.com'
1414
const kUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) WaterMorphCompanion/1.0'
1515

1616
// ── Single-instance guard ────────────────────────────────────────────────────
@@ -36,12 +36,15 @@ function createWindow() {
3636
// custom chrome, which keeps a native draggable area but lets us color it.
3737
const isMac = process.platform === 'darwin'
3838
win = new BrowserWindow({
39-
width: 420,
40-
height: 700,
41-
minWidth: 340,
42-
minHeight: 480,
39+
width: 400,
40+
height: 800,
41+
minWidth: 400,
42+
maxWidth: 400,
43+
minHeight: 400,
44+
show: false,
4345
frame: true,
44-
titleBarStyle: isMac ? 'hidden' : 'hiddenInset',
46+
titleBarStyle: isMac ? 'hiddenInset' : 'hiddenInset',
47+
trafficLightPosition: isMac ? { x: 12, y: 10 } : undefined,
4548
titleBarOverlay: isMac ? false : {
4649
color: '#0a0a0a',
4750
symbolColor: '#8B5CF6', // mauve — Water brand
@@ -63,11 +66,10 @@ function createWindow() {
6366
// and the web app renders the full browser UI instead of the companion UI.
6467
win.webContents.session.setUserAgent(kUA)
6568

66-
// Clear service worker + cache on every launch so stale Next.js HTML never
67-
// causes React hydration mismatch (#418) between cached server HTML and new JS.
68-
win.webContents.session.clearStorageData({
69-
storages: ['serviceworkers', 'cachestorage']
70-
}).catch(() => {}).finally(() => {
69+
// Clear only serviceworkers — lets the HTTP cache speed up loads.
70+
// HTTP cache cleared = blank flash on every launch (do not add clearCache() here).
71+
win.webContents.session.clearStorageData({ storages: ['serviceworkers'] })
72+
.catch(() => {}).finally(() => {
7173
win.loadURL(`${kBaseURL}/discover?companion=windows`, {
7274
userAgent: kUA,
7375
extraHeaders: 'X-Water-Companion: windows\n'
@@ -85,8 +87,9 @@ function createWindow() {
8587
setTimeout(() => win?.loadURL(`${kBaseURL}/discover?companion=windows`, { userAgent: kUA }), 3000)
8688
})
8789

88-
// Send current DAW state and kill web chrome as soon as the page is interactive
90+
// Show window only after page is ready — eliminates the blank flash on open
8991
win.webContents.on('did-finish-load', () => {
92+
win.show()
9093
injectCompanionCSS()
9194
win.webContents.send('daw-state', {
9295
connected: dawState.isConnected,
@@ -98,6 +101,26 @@ function createWindow() {
98101
// Re-inject on SPA navigation (Next.js history.pushState)
99102
win.webContents.on('did-navigate-in-page', () => { injectCompanionCSS() })
100103

104+
// Splice rule: plans/billing/auth always open in the external browser.
105+
// The companion stays on /discover — it never becomes a checkout page.
106+
const EXTERNAL_PATHS = ['/plans', '/billing', '/auth', '/start', '/legal', '/settings']
107+
win.webContents.on('will-navigate', (event, url) => {
108+
try {
109+
const path = new URL(url).pathname
110+
if (EXTERNAL_PATHS.some(p => path.startsWith(p))) {
111+
event.preventDefault()
112+
shell.openExternal(url)
113+
}
114+
} catch {}
115+
})
116+
117+
// Auto-save downloads to WATER_TMP — prevents macOS "Save As" dialog
118+
win.webContents.session.on('will-download', (_event, item) => {
119+
const filename = item.getFilename()
120+
const savePath = path.join(WATER_TMP, filename)
121+
item.setSavePath(savePath)
122+
})
123+
101124
win.on('close', (e) => {
102125
// If we're doing a real quit (update install or tray Quit), let it through.
103126
if (isQuitting) { win = null; return }
@@ -113,7 +136,7 @@ function createTray() {
113136
// Use a 16x16 transparent icon — replace icon.ico with a real icon in production
114137
const icon = nativeImage.createEmpty()
115138
tray = new Tray(icon)
116-
tray.setToolTip('Water Morph')
139+
tray.setToolTip('Water Studio')
117140
updateTrayMenu()
118141
tray.on('click', () => {
119142
if (win?.isVisible()) { win.focus() } else { win?.show() }
@@ -148,6 +171,15 @@ function registerAutostart() {
148171

149172
// ── Companion CSS injection — kills web chrome, re-injects on SPA navigation ──
150173

174+
function injectPluginStatus(connected, bpm, key) {
175+
if (!win?.webContents) return
176+
const script = `(function(){
177+
const el = document.getElementById('__wm_plugin_status');
178+
if (el) { el.dataset.connected = ${JSON.stringify(String(connected))}; el.dataset.bpm = ${JSON.stringify(String(bpm))}; el.dataset.key = ${JSON.stringify(String(key))}; }
179+
})()`
180+
win.webContents.executeJavaScript(script).catch(() => {})
181+
}
182+
151183
function injectCompanionCSS() {
152184
if (!win?.webContents) return
153185
const js = `
@@ -171,7 +203,7 @@ function injectCompanionCSS() {
171203
if(!document.getElementById('__wmCSS')){
172204
var s=document.createElement('style');
173205
s.id='__wmCSS';
174-
s.textContent='[data-bottom-nav]{display:none!important;visibility:hidden!important;height:0!important;overflow:hidden!important;pointer-events:none!important;}[data-mobile-spacer]{display:none!important;height:0!important;}#__wm_plugin_status{display:none!important;}';
206+
s.textContent='[data-bottom-nav]{display:none!important;visibility:hidden!important;height:0!important;overflow:hidden!important;pointer-events:none!important;}[data-mobile-spacer]{display:none!important;height:0!important;}#__wm_plugin_status{display:none!important;}[data-companion-header]{-webkit-app-region:drag}[data-companion-header] button,[data-companion-header] input,[data-companion-header] a{-webkit-app-region:no-drag}[data-sidebar="sidebar"]{display:none!important;}[data-sidebar="rail"]{display:none!important;}';
175207
document.head.appendChild(s);
176208
}
177209
var _kill=function(){
@@ -350,6 +382,11 @@ function handleLine(conn, line) {
350382

351383
// ── IPC from web app ──────────────────────────────────────────────────────────
352384

385+
// Dedicated temp dir — never rely on os.tmpdir() which inherits TMPDIR from the
386+
// launching shell (e.g. Claude Code sets TMPDIR to its session folder).
387+
const WATER_TMP = path.join(os.homedir(), 'Library', 'Caches', 'water-studio', 'downloads')
388+
fs.mkdirSync(WATER_TMP, { recursive: true })
389+
353390
// Track cache: id → absolute local path (populated by fetch interception + morphCopy)
354391
const trackCache = new Map()
355392

@@ -457,6 +494,12 @@ function setupAutoUpdater() {
457494
// ── App lifecycle ─────────────────────────────────────────────────────────────
458495

459496
app.whenReady().then(() => {
497+
if (app.dock) app.dock.hide()
498+
// Standard Edit menu so Cmd+C/V/X/A work in the WebView
499+
Menu.setApplicationMenu(Menu.buildFromTemplate([
500+
{ role: 'appMenu' },
501+
{ role: 'editMenu' },
502+
]))
460503
createWindow()
461504
createTray()
462505
startTCPServer()
@@ -478,6 +521,21 @@ app.on('open-url', (_event, url) => {
478521
function handleProtocolUrl(url) {
479522
if (!url.startsWith('watermorph://')) return
480523
if (win) { win.show(); win.focus() }
524+
525+
const parsed = new URL(url)
526+
527+
// watermorph://auth?access_token=X&refresh_token=Y
528+
// Navigates the WebView to a URL with the Supabase auth fragment.
529+
// The Supabase client on that page auto-picks up the tokens and
530+
// creates the session — same mechanism as email magic links.
531+
if (parsed.hostname === 'auth') {
532+
const at = parsed.searchParams.get('access_token')
533+
const rt = parsed.searchParams.get('refresh_token')
534+
if (at && rt && win?.webContents) {
535+
const authUrl = `${kBaseURL}/api/plugin/v1/auth-callback?access_token=${encodeURIComponent(at)}&refresh_token=${encodeURIComponent(rt)}`
536+
win.webContents.loadURL(authUrl, { userAgent: kUA, extraHeaders: 'X-Water-Companion: windows\n' })
537+
}
538+
}
481539
}
482540

483541
app.on('before-quit', () => { isQuitting = true })

0 commit comments

Comments
 (0)