murasaki 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/log.js CHANGED
@@ -12,7 +12,7 @@ export function printBanner(winTitle, winSize) {
12
12
  out(` ${c(DIM)}-${c(RESET)} ${c(DIM)}Mode ${c(RESET)}development ${c(DIM)}(HMR active)${c(RESET)}\n\n`);
13
13
  }
14
14
  export function printShortcuts() {
15
- out(` ${c(DIM)}Shortcuts ${c(RESET)}${c(BOLD)}o${c(RESET)} ${c(DIM)}open${c(RESET)} ${c(BOLD)}r${c(RESET)} ${c(DIM)}restart${c(RESET)} ${c(BOLD)}q${c(RESET)} ${c(DIM)}quit${c(RESET)}\n\n`);
15
+ out(` ${c(DIM)}Shortcuts ${c(RESET)}${c(BOLD)}r${c(RESET)} ${c(DIM)}reload${c(RESET)} ${c(BOLD)}R${c(RESET)} ${c(DIM)}restart${c(RESET)} ${c(BOLD)}q${c(RESET)} ${c(DIM)}quit${c(RESET)}\n\n`);
16
16
  }
17
17
  export const printStarting = () => out(` ${c(DIM)}○${c(RESET)} Starting...\n`);
18
18
  export const printReady = (ms) => out(` ${c(GREEN)}${c(BOLD)}✓${c(RESET)} ${c(BOLD)}Ready${c(RESET)} ${c(DIM)}in ${ms}ms${c(RESET)}\n`);
@@ -1 +1 @@
1
- {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/cli/log.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/cli/log.ts"],"names":[],"mappings":"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"}
package/dist/dev.js CHANGED
@@ -1,38 +1,41 @@
1
- // src/dev.tsx
2
- // Murasaki dev runner Next.js-like file-based routing without Next.js.
3
- //
4
- // Reads the consumer's src/ directory:
5
- // src/layout.tsx (optional, can export `metadata`)
6
- // src/app.tsx (required)
7
- // src/globals.css (optional, auto-injected)
8
- //
9
- // Renders <Layout><App /></Layout> with React, ships HTML to the WebView,
10
- // and reloads in place on file change.
11
- import { printBanner, printBye, printOpened, printReady, printShortcuts, printStarting, } from "./cli/log.js";
1
+ // src/dev.tsx — boot entry for `murasaki dev`.
2
+ import { printBanner, printBye, printReady, printShortcuts, printStarting } from "./cli/log.js";
12
3
  import { setupHmr } from "./runtime/hmr.js";
13
- import { setupShortcuts, teardownStdin } from "./runtime/shortcuts.js";
14
- import { closeWindow, exitApp, getConfig, openWindow, reloadWindow, runApp, } from "./runtime/window.js";
4
+ import { setupAppMenu } from "./runtime/menu.js";
5
+ import { setupShortcuts } from "./runtime/shortcuts.js";
6
+ import { app, closeWindow, exitApp, getConfig, openWindow, reloadWindow, runApp, } from "./runtime/window.js";
15
7
  const startAt = Date.now();
16
- // Boot: render once (applies metadata) → banner → ready
17
8
  await openWindow();
18
9
  printBanner(getConfig().title, getConfig());
19
10
  printShortcuts();
20
11
  printStarting();
21
12
  printReady(Date.now() - startAt);
22
- setupShortcuts({
23
- onOpen: () => {
24
- void openWindow().then(printOpened);
13
+ const quit = () => {
14
+ printBye();
15
+ exitApp();
16
+ process.exit(0);
17
+ };
18
+ // Native menu — Cmd+R reload, Cmd+Shift+R restart, Cmd+Q quit (built-in)
19
+ setupAppMenu(app, {
20
+ onReload: () => {
21
+ void reloadWindow('menu reload');
25
22
  },
26
23
  onRestart: () => {
27
24
  closeWindow();
28
- void openWindow().then(printOpened);
25
+ void openWindow();
26
+ },
27
+ onQuit: quit,
28
+ });
29
+ // Terminal single-key shortcuts (r / R / q) + signals.
30
+ setupShortcuts({
31
+ onReload: () => {
32
+ void reloadWindow('manual');
29
33
  },
30
- onQuit: () => {
31
- printBye();
32
- teardownStdin();
33
- exitApp();
34
- process.exit(0);
34
+ onRestart: () => {
35
+ closeWindow();
36
+ void openWindow();
35
37
  },
38
+ onQuit: quit,
36
39
  });
37
40
  setupHmr((file) => {
38
41
  void reloadWindow(file);
package/dist/dev.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"dev.js","sourceRoot":"","sources":["../src/dev.tsx"],"names":[],"mappings":"AAAA,cAAc;AACd,yEAAyE;AACzE,EAAE;AACF,uCAAuC;AACvC,uDAAuD;AACvD,gCAAgC;AAChC,+CAA+C;AAC/C,EAAE;AACF,0EAA0E;AAC1E,uCAAuC;AAEvC,OAAO,EACL,WAAW,EACX,QAAQ,EACR,WAAW,EACX,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACtE,OAAO,EACL,WAAW,EACX,OAAO,EACP,SAAS,EACT,UAAU,EACV,YAAY,EACZ,MAAM,GACP,MAAM,qBAAqB,CAAA;AAE5B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;AAE1B,wDAAwD;AACxD,MAAM,UAAU,EAAE,CAAA;AAClB,WAAW,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;AAC3C,cAAc,EAAE,CAAA;AAChB,aAAa,EAAE,CAAA;AACf,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAA;AAEhC,cAAc,CAAC;IACb,MAAM,EAAE,GAAG,EAAE;QACX,KAAK,UAAU,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACrC,CAAC;IACD,SAAS,EAAE,GAAG,EAAE;QACd,WAAW,EAAE,CAAA;QACb,KAAK,UAAU,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACrC,CAAC;IACD,MAAM,EAAE,GAAG,EAAE;QACX,QAAQ,EAAE,CAAA;QACV,aAAa,EAAE,CAAA;QACf,OAAO,EAAE,CAAA;QACT,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;CACF,CAAC,CAAA;AAEF,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE;IAChB,KAAK,YAAY,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC,CAAC,CAAA;AAEF,MAAM,EAAE,CAAA"}
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../src/dev.tsx"],"names":[],"mappings":"AAAA,+CAA+C;AAE/C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC/F,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EACL,GAAG,EACH,WAAW,EACX,OAAO,EACP,SAAS,EACT,UAAU,EACV,YAAY,EACZ,MAAM,GACP,MAAM,qBAAqB,CAAA;AAE5B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;AAE1B,MAAM,UAAU,EAAE,CAAA;AAClB,WAAW,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;AAC3C,cAAc,EAAE,CAAA;AAChB,aAAa,EAAE,CAAA;AACf,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAA;AAEhC,MAAM,IAAI,GAAG,GAAG,EAAE;IAChB,QAAQ,EAAE,CAAA;IACV,OAAO,EAAE,CAAA;IACT,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAA;AAED,yEAAyE;AACzE,YAAY,CAAC,GAAG,EAAE;IAChB,QAAQ,EAAE,GAAG,EAAE;QACb,KAAK,YAAY,CAAC,aAAa,CAAC,CAAA;IAClC,CAAC;IACD,SAAS,EAAE,GAAG,EAAE;QACd,WAAW,EAAE,CAAA;QACb,KAAK,UAAU,EAAE,CAAA;IACnB,CAAC;IACD,MAAM,EAAE,IAAI;CACb,CAAC,CAAA;AAEF,uDAAuD;AACvD,cAAc,CAAC;IACb,QAAQ,EAAE,GAAG,EAAE;QACb,KAAK,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC7B,CAAC;IACD,SAAS,EAAE,GAAG,EAAE;QACd,WAAW,EAAE,CAAA;QACb,KAAK,UAAU,EAAE,CAAA;IACnB,CAAC;IACD,MAAM,EAAE,IAAI;CACb,CAAC,CAAA;AAEF,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE;IAChB,KAAK,YAAY,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC,CAAC,CAAA;AAEF,MAAM,EAAE,CAAA"}
@@ -1,2 +1,3 @@
1
1
  export declare function setupHmr(onChange: (filename: string) => void): void;
2
+ export declare function teardownHmr(): void;
2
3
  //# sourceMappingURL=hmr.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../../src/runtime/hmr.ts"],"names":[],"mappings":"AAMA,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAmBnE"}
1
+ {"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../../src/runtime/hmr.ts"],"names":[],"mappings":"AAeA,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAuBnE;AAED,wBAAgB,WAAW,IAAI,IAAI,CAOlC"}
@@ -1,7 +1,15 @@
1
1
  // File watcher for src/ — debounces multi-event saves and triggers reload.
2
+ //
3
+ // macOS note: Node's fs.watch wraps FSEvents, which keeps the libuv loop
4
+ // alive AND, observed in practice, somehow blocks the OS close button on
5
+ // the webview window from completing. We:
6
+ // 1. unref() the watcher so it doesn't prop the process up on its own
7
+ // 2. close() it explicitly on app/window close (via teardown)
8
+ // That keeps HMR responsive while letting the red close button work.
2
9
  import { existsSync, watch } from 'node:fs';
3
10
  import { printHint } from "../cli/log.js";
4
11
  import { SRC_DIR } from "../env.js";
12
+ let activeWatcher = null;
5
13
  export function setupHmr(onChange) {
6
14
  if (!existsSync(SRC_DIR)) {
7
15
  printHint('src/ directory not found — nothing to watch');
@@ -10,7 +18,7 @@ export function setupHmr(onChange) {
10
18
  let debounce = null;
11
19
  let lastFile = '';
12
20
  try {
13
- watch(SRC_DIR, { recursive: true }, (_event, filename) => {
21
+ const w = watch(SRC_DIR, { recursive: true }, (_event, filename) => {
14
22
  if (!filename)
15
23
  return;
16
24
  if (debounce)
@@ -20,9 +28,22 @@ export function setupHmr(onChange) {
20
28
  onChange(lastFile);
21
29
  }, 80);
22
30
  });
31
+ // Don't let the watcher alone keep the process alive — the Cocoa main
32
+ // loop in @webviewjs/webview is the real keep-alive.
33
+ w.unref?.();
34
+ activeWatcher = w;
23
35
  }
24
36
  catch (e) {
25
37
  printHint(`HMR watcher failed: ${e.message}`);
26
38
  }
27
39
  }
40
+ export function teardownHmr() {
41
+ if (activeWatcher) {
42
+ try {
43
+ activeWatcher.close();
44
+ }
45
+ catch { }
46
+ activeWatcher = null;
47
+ }
48
+ }
28
49
  //# sourceMappingURL=hmr.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hmr.js","sourceRoot":"","sources":["../../src/runtime/hmr.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,MAAM,UAAU,QAAQ,CAAC,QAAoC;IAC3D,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,SAAS,CAAC,6CAA6C,CAAC,CAAA;QACxD,OAAM;IACR,CAAC;IACD,IAAI,QAAQ,GAA0B,IAAI,CAAA;IAC1C,IAAI,QAAQ,GAAG,EAAE,CAAA;IACjB,IAAI,CAAC;QACH,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE;YACvD,IAAI,CAAC,QAAQ;gBAAE,OAAM;YACrB,IAAI,QAAQ;gBAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;YACpC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAA;YAC9B,QAAQ,GAAG,UAAU,CAAC,GAAG,EAAE;gBACzB,QAAQ,CAAC,QAAQ,CAAC,CAAA;YACpB,CAAC,EAAE,EAAE,CAAC,CAAA;QACR,CAAC,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,SAAS,CAAC,uBAAuB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/C,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"hmr.js","sourceRoot":"","sources":["../../src/runtime/hmr.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,0CAA0C;AAC1C,wEAAwE;AACxE,gEAAgE;AAChE,qEAAqE;AAErE,OAAO,EAAkB,UAAU,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,IAAI,aAAa,GAAqB,IAAI,CAAA;AAE1C,MAAM,UAAU,QAAQ,CAAC,QAAoC;IAC3D,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,SAAS,CAAC,6CAA6C,CAAC,CAAA;QACxD,OAAM;IACR,CAAC;IACD,IAAI,QAAQ,GAA0B,IAAI,CAAA;IAC1C,IAAI,QAAQ,GAAG,EAAE,CAAA;IACjB,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE;YACjE,IAAI,CAAC,QAAQ;gBAAE,OAAM;YACrB,IAAI,QAAQ;gBAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;YACpC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAA;YAC9B,QAAQ,GAAG,UAAU,CAAC,GAAG,EAAE;gBACzB,QAAQ,CAAC,QAAQ,CAAC,CAAA;YACpB,CAAC,EAAE,EAAE,CAAC,CAAA;QACR,CAAC,CAAC,CAAA;QACF,sEAAsE;QACtE,qDAAqD;QACrD,CAAC,CAAC,KAAK,EAAE,EAAE,CAAA;QACX,aAAa,GAAG,CAAC,CAAA;IACnB,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,SAAS,CAAC,uBAAuB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,IAAI,aAAa,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,aAAa,CAAC,KAAK,EAAE,CAAA;QACvB,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACV,aAAa,GAAG,IAAI,CAAA;IACtB,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Application } from '@webviewjs/webview';
2
+ export type MenuHandlers = {
3
+ onReload: () => void;
4
+ onRestart: () => void;
5
+ onQuit: () => void;
6
+ };
7
+ export declare function setupAppMenu(app: Application, handlers: MenuHandlers): void;
8
+ //# sourceMappingURL=menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../src/runtime/menu.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB,CAAA;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,CAwF3E"}
@@ -0,0 +1,100 @@
1
+ // Native application menu — replaces the old stdin keypress shortcuts.
2
+ //
3
+ // Setting the menu via app.setMenu() goes through Cocoa/Win32 native UI
4
+ // (not stdin), so it doesn't conflict with the close-button pipeline.
5
+ //
6
+ // Standard macOS bar:
7
+ // Murasaki | File | Edit | View
8
+ // Accelerators land on the page automatically (Cmd+R reload etc).
9
+ export function setupAppMenu(app, handlers) {
10
+ try {
11
+ ;
12
+ app.setMenu?.({
13
+ items: [
14
+ {
15
+ // First item is conventionally the app's own menu on macOS.
16
+ label: 'Murasaki',
17
+ submenu: {
18
+ items: [
19
+ { role: 'about' },
20
+ { role: 'separator' },
21
+ { role: 'services' },
22
+ { role: 'separator' },
23
+ { role: 'hide' },
24
+ { role: 'hideOthers' },
25
+ { role: 'showAll' },
26
+ { role: 'separator' },
27
+ { role: 'quit' },
28
+ ],
29
+ },
30
+ },
31
+ {
32
+ // Use "Develop" to avoid mixing dev commands into a standard "View" bar.
33
+ label: 'Develop',
34
+ submenu: {
35
+ items: [
36
+ {
37
+ id: 'murasaki:reload',
38
+ label: 'Reload',
39
+ accelerator: 'CmdOrCtrl+R',
40
+ },
41
+ {
42
+ id: 'murasaki:restart',
43
+ // Cmd+Shift+R is "hard reload" in Safari/Chrome — avoid the clash.
44
+ label: 'Restart Window',
45
+ accelerator: 'CmdOrCtrl+Alt+R',
46
+ },
47
+ ],
48
+ },
49
+ },
50
+ {
51
+ label: 'View',
52
+ submenu: {
53
+ items: [{ role: 'fullscreen' }],
54
+ },
55
+ },
56
+ {
57
+ label: 'Edit',
58
+ submenu: {
59
+ items: [
60
+ { role: 'undo' },
61
+ { role: 'redo' },
62
+ { role: 'separator' },
63
+ { role: 'cut' },
64
+ { role: 'copy' },
65
+ { role: 'paste' },
66
+ { role: 'selectAll' },
67
+ ],
68
+ },
69
+ },
70
+ {
71
+ label: 'Window',
72
+ submenu: {
73
+ items: [{ role: 'minimize' }, { role: 'zoom' }],
74
+ },
75
+ },
76
+ ],
77
+ });
78
+ }
79
+ catch {
80
+ // Older webview versions may not support setMenu — fail silent.
81
+ }
82
+ try {
83
+ ;
84
+ app.on?.('custom-menu-click', (payload) => {
85
+ const id = payload?.customMenuEvent?.id;
86
+ if (!id)
87
+ return;
88
+ if (id === 'murasaki:reload')
89
+ handlers.onReload();
90
+ else if (id === 'murasaki:restart')
91
+ handlers.onRestart();
92
+ });
93
+ }
94
+ catch { }
95
+ // Catch Cmd+Q / role: 'quit' that goes through application-close-requested,
96
+ // so a clean quit + handler-side shutdown still happens.
97
+ // (The existing application-close-requested handler in window.ts already
98
+ // calls handlers.onQuit equivalent path.)
99
+ }
100
+ //# sourceMappingURL=menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.js","sourceRoot":"","sources":["../../src/runtime/menu.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,EAAE;AACF,wEAAwE;AACxE,sEAAsE;AACtE,EAAE;AACF,sBAAsB;AACtB,kCAAkC;AAClC,kEAAkE;AAUlE,MAAM,UAAU,YAAY,CAAC,GAAgB,EAAE,QAAsB;IACnE,IAAI,CAAC;QACH,CAAC;QAAC,GAA+C,CAAC,OAAO,EAAE,CAAC;YAC1D,KAAK,EAAE;gBACL;oBACE,4DAA4D;oBAC5D,KAAK,EAAE,UAAU;oBACjB,OAAO,EAAE;wBACP,KAAK,EAAE;4BACL,EAAE,IAAI,EAAE,OAAO,EAAE;4BACjB,EAAE,IAAI,EAAE,WAAW,EAAE;4BACrB,EAAE,IAAI,EAAE,UAAU,EAAE;4BACpB,EAAE,IAAI,EAAE,WAAW,EAAE;4BACrB,EAAE,IAAI,EAAE,MAAM,EAAE;4BAChB,EAAE,IAAI,EAAE,YAAY,EAAE;4BACtB,EAAE,IAAI,EAAE,SAAS,EAAE;4BACnB,EAAE,IAAI,EAAE,WAAW,EAAE;4BACrB,EAAE,IAAI,EAAE,MAAM,EAAE;yBACjB;qBACF;iBACF;gBACD;oBACE,yEAAyE;oBACzE,KAAK,EAAE,SAAS;oBAChB,OAAO,EAAE;wBACP,KAAK,EAAE;4BACL;gCACE,EAAE,EAAE,iBAAiB;gCACrB,KAAK,EAAE,QAAQ;gCACf,WAAW,EAAE,aAAa;6BAC3B;4BACD;gCACE,EAAE,EAAE,kBAAkB;gCACtB,mEAAmE;gCACnE,KAAK,EAAE,gBAAgB;gCACvB,WAAW,EAAE,iBAAiB;6BAC/B;yBACF;qBACF;iBACF;gBACD;oBACE,KAAK,EAAE,MAAM;oBACb,OAAO,EAAE;wBACP,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;qBAChC;iBACF;gBACD;oBACE,KAAK,EAAE,MAAM;oBACb,OAAO,EAAE;wBACP,KAAK,EAAE;4BACL,EAAE,IAAI,EAAE,MAAM,EAAE;4BAChB,EAAE,IAAI,EAAE,MAAM,EAAE;4BAChB,EAAE,IAAI,EAAE,WAAW,EAAE;4BACrB,EAAE,IAAI,EAAE,KAAK,EAAE;4BACf,EAAE,IAAI,EAAE,MAAM,EAAE;4BAChB,EAAE,IAAI,EAAE,OAAO,EAAE;4BACjB,EAAE,IAAI,EAAE,WAAW,EAAE;yBACtB;qBACF;iBACF;gBACD;oBACE,KAAK,EAAE,QAAQ;oBACf,OAAO,EAAE;wBACP,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;qBAChD;iBACF;aACF;SACF,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,gEAAgE;IAClE,CAAC;IAED,IAAI,CAAC;QACH,CAAC;QAAC,GAAwE,CAAC,EAAE,EAAE,CAC7E,mBAAmB,EACnB,CAAC,OAAO,EAAE,EAAE;YACV,MAAM,EAAE,GAAI,OAAiD,EAAE,eAAe,EAAE,EAAE,CAAA;YAClF,IAAI,CAAC,EAAE;gBAAE,OAAM;YACf,IAAI,EAAE,KAAK,iBAAiB;gBAAE,QAAQ,CAAC,QAAQ,EAAE,CAAA;iBAC5C,IAAI,EAAE,KAAK,kBAAkB;gBAAE,QAAQ,CAAC,SAAS,EAAE,CAAA;QAC1D,CAAC,CACF,CAAA;IACH,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAEV,4EAA4E;IAC5E,yDAAyD;IACzD,yEAAyE;IACzE,2CAA2C;AAC7C,CAAC"}
@@ -1,6 +1,6 @@
1
1
  export type ShortcutHandlers = {
2
- onOpen: () => void;
3
2
  onRestart: () => void;
3
+ onReload: () => void;
4
4
  onQuit: () => void;
5
5
  };
6
6
  export declare function setupShortcuts(handlers: ShortcutHandlers): void;
@@ -1 +1 @@
1
- {"version":3,"file":"shortcuts.d.ts","sourceRoot":"","sources":["../../src/runtime/shortcuts.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB,CAAA;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAW/D;AAED,wBAAgB,aAAa,IAAI,IAAI,CAIpC"}
1
+ {"version":3,"file":"shortcuts.d.ts","sourceRoot":"","sources":["../../src/runtime/shortcuts.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB,CAAA;AAID,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CA0B/D;AAED,wBAAgB,aAAa,IAAI,IAAI,CASpC"}
@@ -1,29 +1,53 @@
1
- // Terminal keyboard shortcuts (raw mode stdin).
1
+ // Terminal single-key shortcuts (Turbopack-style).
2
2
  //
3
- // o open the window
4
- // r restart (close + open)
3
+ // r reload
4
+ // R restart window
5
5
  // q quit
6
6
  // Ctrl+C quit
7
+ //
8
+ // History: an earlier attempt at stdin listening caused the OS close button
9
+ // to hang. The fix is `process.stdin.unref()` — stdin remains attached, but
10
+ // it no longer keeps the libuv loop tied to a handle that the macOS Cocoa
11
+ // close pipeline blocks on. With unref, the close button completes; with
12
+ // it set, the listener still receives keystrokes.
13
+ let stdinAttached = false;
7
14
  export function setupShortcuts(handlers) {
15
+ // Signal handling first — always works, never touches stdin polling.
16
+ const sig = () => handlers.onQuit();
17
+ process.on('SIGINT', sig);
18
+ process.on('SIGTERM', sig);
19
+ // Stdin keypress shortcuts — only on a real TTY.
8
20
  if (!process.stdin.isTTY)
9
21
  return;
10
22
  process.stdin.setRawMode(true);
11
- process.stdin.resume();
12
23
  process.stdin.setEncoding('utf8');
24
+ // CRITICAL: unref BEFORE adding the data listener. Without unref, the
25
+ // attached stdin handle prevents the OS close button from completing the
26
+ // window-close pipeline (observed on macOS WKWebView).
27
+ process.stdin.unref();
28
+ process.stdin.resume();
29
+ stdinAttached = true;
13
30
  process.stdin.on('data', (key) => {
14
31
  const k = key.toString();
15
- if (k === 'o' || k === 'O')
16
- handlers.onOpen();
17
- else if (k === 'r' || k === 'R')
32
+ if (k === 'r')
33
+ handlers.onReload();
34
+ else if (k === 'R')
18
35
  handlers.onRestart();
19
36
  else if (k === 'q' || k === 'Q' || k === '\x03' /* Ctrl+C */)
20
37
  handlers.onQuit();
21
38
  });
22
39
  }
23
40
  export function teardownStdin() {
41
+ if (!stdinAttached)
42
+ return;
24
43
  try {
25
44
  process.stdin.setRawMode(false);
26
45
  }
27
46
  catch { }
47
+ try {
48
+ process.stdin.pause();
49
+ }
50
+ catch { }
51
+ stdinAttached = false;
28
52
  }
29
53
  //# sourceMappingURL=shortcuts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shortcuts.js","sourceRoot":"","sources":["../../src/runtime/shortcuts.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,wBAAwB;AACxB,+BAA+B;AAC/B,aAAa;AACb,kBAAkB;AAQlB,MAAM,UAAU,cAAc,CAAC,QAA0B;IACvD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK;QAAE,OAAM;IAChC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;IAC9B,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAA;IACtB,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;IACjC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;QAC/B,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAA;QACxB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG;YAAE,QAAQ,CAAC,MAAM,EAAE,CAAA;aACxC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG;YAAE,QAAQ,CAAC,SAAS,EAAE,CAAA;aAChD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,MAAM,CAAC,YAAY;YAAE,QAAQ,CAAC,MAAM,EAAE,CAAA;IACjF,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;IACjC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;AACZ,CAAC"}
1
+ {"version":3,"file":"shortcuts.js","sourceRoot":"","sources":["../../src/runtime/shortcuts.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,EAAE;AACF,eAAe;AACf,uBAAuB;AACvB,aAAa;AACb,kBAAkB;AAClB,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,0EAA0E;AAC1E,yEAAyE;AACzE,kDAAkD;AAQlD,IAAI,aAAa,GAAG,KAAK,CAAA;AAEzB,MAAM,UAAU,cAAc,CAAC,QAA0B;IACvD,qEAAqE;IACrE,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAA;IACnC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAA;IACzB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;IAE1B,iDAAiD;IACjD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK;QAAE,OAAM;IAEhC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;IAC9B,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;IAEjC,sEAAsE;IACtE,yEAAyE;IACzE,uDAAuD;IACvD,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;IACrB,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAA;IAEtB,aAAa,GAAG,IAAI,CAAA;IAEpB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;QAC/B,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAA;QACxB,IAAI,CAAC,KAAK,GAAG;YAAE,QAAQ,CAAC,QAAQ,EAAE,CAAA;aAC7B,IAAI,CAAC,KAAK,GAAG;YAAE,QAAQ,CAAC,SAAS,EAAE,CAAA;aACnC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,MAAM,CAAC,YAAY;YAAE,QAAQ,CAAC,MAAM,EAAE,CAAA;IACjF,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,aAAa;QAAE,OAAM;IAC1B,IAAI,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;IACjC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IACV,IAAI,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;IACvB,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IACV,aAAa,GAAG,KAAK,CAAA;AACvB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../../src/runtime/window.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAe,MAAM,oBAAoB,CAAA;AAI7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAU/C,eAAO,MAAM,GAAG,aAAqD,CAAA;AA+BrE,wBAAgB,SAAS,IAAI,QAAQ,CAAC,YAAY,CAAC,CAElD;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAmBhD;AAED,wBAAgB,WAAW,IAAI,IAAI,CAOlC;AAED,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASrE;AAED,wBAAgB,MAAM,IAAI,IAAI,CAE7B;AAED,wBAAgB,OAAO,IAAI,IAAI,CAI9B"}
1
+ {"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../../src/runtime/window.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAIhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAgB/C,eAAO,MAAM,GAAG,aAAoB,CAAA;AAoDpC,wBAAgB,SAAS,IAAI,QAAQ,CAAC,YAAY,CAAC,CAElD;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAmBhD;AAED,wBAAgB,WAAW,IAAI,IAAI,CAKlC;AAED,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASrE;AAED,wBAAgB,MAAM,IAAI,IAAI,CAE7B;AAED,wBAAgB,OAAO,IAAI,IAAI,CAI9B"}
@@ -1,36 +1,60 @@
1
1
  // Owns the Application + BrowserWindow lifecycle.
2
- //
3
- // The EventLoop is created exactly once (winit/tao limitation). ControlFlow.Wait
4
- // keeps the loop alive even when no windows are open, so the user can press
5
- // `o` in the terminal to re-open the window.
6
- import { Application, ControlFlow } from '@webviewjs/webview';
7
- import { printClosed, printError, printHint, printReloaded } from "../cli/log.js";
2
+ import { Application } from '@webviewjs/webview';
3
+ import { printBye, printClosed, printError, printHint, printReloaded } from "../cli/log.js";
8
4
  import { DEFAULT_WIN_SIZE, DEFAULT_WIN_TITLE, projectRoot } from "../env.js";
5
+ import { teardownHmr } from "./hmr.js";
9
6
  import { nativeBridge } from "./native.js";
10
7
  import { renderApp } from "./render.js";
8
+ import { teardownStdin } from "./shortcuts.js";
11
9
  const config = {
12
10
  title: DEFAULT_WIN_TITLE,
13
11
  width: DEFAULT_WIN_SIZE.width,
14
12
  height: DEFAULT_WIN_SIZE.height,
15
13
  };
16
- export const app = new Application({ controlFlow: ControlFlow.Wait });
14
+ // Default event-loop mode (pump_events at ~60 FPS). ControlFlow.Wait used to
15
+ // be set here to allow `o` re-open after close, but it caused the close
16
+ // button to hang waiting for the next pump tick, which macOS interpreted
17
+ // as "not responding". Plain run-loop is responsive and just as cheap.
18
+ export const app = new Application();
17
19
  let win = null;
18
20
  let webview = null;
21
+ // winit/wry distinguishes two close events:
22
+ // - window-close-requested → one window's red button
23
+ // - application-close-requested → last window closed → app should exit
24
+ // We MUST handle the latter and call app.exit(), otherwise the OS sees
25
+ // the process as alive but unresponsive (no pending GUI work, no exit).
19
26
  app.onEvent((event) => {
20
27
  const kind = event && (event.kind || event.event);
21
28
  if (kind === 'window-close-requested') {
22
- // Dispose so the OS close completes — without this the close button hangs.
23
- if (win) {
24
- try {
25
- win.dispose();
26
- }
27
- catch { }
28
- }
29
- win = null;
30
29
  webview = null;
30
+ win = null;
31
31
  printClosed();
32
+ return;
33
+ }
34
+ if (kind === 'application-close-requested') {
35
+ teardownHmr();
36
+ teardownStdin();
37
+ printBye();
38
+ try {
39
+ app.exit();
40
+ }
41
+ catch { }
42
+ process.exit(0);
32
43
  }
33
44
  });
45
+ // Manual close path (the `r` shortcut for restart). We reopen immediately
46
+ // after, so explicit dispose is desirable.
47
+ function disposeSafely(target) {
48
+ if (!target)
49
+ return;
50
+ const sym = target[Symbol.dispose];
51
+ if (typeof sym === 'function') {
52
+ try {
53
+ sym.call(target);
54
+ }
55
+ catch { }
56
+ }
57
+ }
34
58
  function applyMetadata(metadata) {
35
59
  if (!metadata)
36
60
  return;
@@ -68,14 +92,10 @@ export async function openWindow() {
68
92
  }
69
93
  }
70
94
  export function closeWindow() {
71
- if (!win)
72
- return;
73
- try {
74
- win.dispose();
75
- }
76
- catch { }
77
- win = null;
95
+ disposeSafely(webview);
96
+ disposeSafely(win);
78
97
  webview = null;
98
+ win = null;
79
99
  }
80
100
  export async function reloadWindow(triggerFile) {
81
101
  if (!webview)
@@ -1 +1 @@
1
- {"version":3,"file":"window.js","sourceRoot":"","sources":["../../src/runtime/window.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"window.js","sourceRoot":"","sources":["../../src/runtime/window.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "murasaki",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "The desktop framework for Next.js developers. Node-powered. WebView-thin. No Rust. No Chromium.",
5
5
  "keywords": [
6
6
  "desktop",