next2d-development-mcp 1.1.5 → 1.1.6
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/index.js +1 -1
- package/dist/references/develop-specs.md +17 -3
- package/dist/references/framework-specs.md +102 -0
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { registerResources } from "./resources/index.js";
|
|
|
6
6
|
import { registerPrompts } from "./prompts/index.js";
|
|
7
7
|
const server = new McpServer({
|
|
8
8
|
"name": "next2d-development-mcp",
|
|
9
|
-
"version": "
|
|
9
|
+
"version": "1.1.6"
|
|
10
10
|
});
|
|
11
11
|
registerTools(server);
|
|
12
12
|
registerResources(server);
|
|
@@ -1626,12 +1626,26 @@ style-src 'self';
|
|
|
1626
1626
|
## Anti-Patterns
|
|
1627
1627
|
|
|
1628
1628
|
```typescript
|
|
1629
|
-
// NG: Viewでビジネスロジック
|
|
1629
|
+
// NG: Viewでビジネスロジック + addEventListener に async 関数を直接渡す
|
|
1630
|
+
// async () => {} を渡すと Promise が返り、@next2d/player がチャンネルを保持したまま
|
|
1631
|
+
// SPA 遷移で閉じられ "A listener indicated an asynchronous response by returning true,
|
|
1632
|
+
// but the message channel closed before a response was received" エラーが発生する。
|
|
1630
1633
|
class BadView extends View {
|
|
1631
1634
|
async initialize() {
|
|
1632
1635
|
btn.addEventListener(PointerEvent.POINTER_DOWN, async () => {
|
|
1633
|
-
const data = await Repository.get(); // NG
|
|
1634
|
-
this.processData(data); // NG
|
|
1636
|
+
const data = await Repository.get(); // NG: ビジネスロジック in View
|
|
1637
|
+
this.processData(data); // NG: ビジネスロジック in View
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// OK: 同期ハンドラ内で void IIFE を使い Promise を捨てる
|
|
1643
|
+
class GoodView extends View {
|
|
1644
|
+
async initialize() {
|
|
1645
|
+
btn.addEventListener(PointerEvent.POINTER_DOWN, () => { // 同期関数
|
|
1646
|
+
void (async () => {
|
|
1647
|
+
await this.vm.onClickButton(); // OK: VMに委譲
|
|
1648
|
+
})();
|
|
1635
1649
|
});
|
|
1636
1650
|
}
|
|
1637
1651
|
}
|
|
@@ -1730,6 +1730,108 @@ homeContentPointerDownEvent(event: PointerEvent): void
|
|
|
1730
1730
|
|
|
1731
1731
|
View内でイベント処理を完結させず、必ずViewModelに委譲します。
|
|
1732
1732
|
|
|
1733
|
+
### 4. `addEventListener` に `async` 関数を直接渡さない
|
|
1734
|
+
|
|
1735
|
+
**NG パターン(Promiseエラーの原因):**
|
|
1736
|
+
|
|
1737
|
+
```typescript
|
|
1738
|
+
btn.addEventListener(PointerEvent.POINTER_DOWN, async () =>
|
|
1739
|
+
{
|
|
1740
|
+
await this.vm.onClickSomething();
|
|
1741
|
+
});
|
|
1742
|
+
```
|
|
1743
|
+
|
|
1744
|
+
`async` 関数は Promise を返します。@next2d/player の内部イベントシステムは、
|
|
1745
|
+
ハンドラが truthy な値を返すと「非同期応答あり」と解釈し、Worker との
|
|
1746
|
+
メッセージチャンネルを保持します。SPA 遷移で `gotoView()` が呼ばれると
|
|
1747
|
+
チャンネルが閉じられ、以下のエラーが `Uncaught (in promise)` として発生します。
|
|
1748
|
+
|
|
1749
|
+
```
|
|
1750
|
+
A listener indicated an asynchronous response by returning true,
|
|
1751
|
+
but the message channel closed before a response was received
|
|
1752
|
+
```
|
|
1753
|
+
|
|
1754
|
+
**OK パターン(`void (async () => {})()` でラップ):**
|
|
1755
|
+
|
|
1756
|
+
```typescript
|
|
1757
|
+
btn.addEventListener(PointerEvent.POINTER_DOWN, () =>
|
|
1758
|
+
{
|
|
1759
|
+
if (!btn.enabled) { return; } // 同期ガードはここで
|
|
1760
|
+
btn.disable();
|
|
1761
|
+
|
|
1762
|
+
void (async () =>
|
|
1763
|
+
{
|
|
1764
|
+
try {
|
|
1765
|
+
await this.vm.onClickSomething();
|
|
1766
|
+
} catch (error) {
|
|
1767
|
+
btn.enable();
|
|
1768
|
+
console.error("[prefix]", "遷移に失敗", error);
|
|
1769
|
+
}
|
|
1770
|
+
})();
|
|
1771
|
+
});
|
|
1772
|
+
```
|
|
1773
|
+
|
|
1774
|
+
ポイント:
|
|
1775
|
+
- 外側のハンドラを **同期関数** にして `void` 以外の戻り値を返さない
|
|
1776
|
+
- `await` が必要な処理は内側の即時実行 async IIFE に閉じ込め、`void` で破棄
|
|
1777
|
+
- `enabled` チェックなど即時判断が必要なガードは **外側の同期ハンドラ** に置く
|
|
1778
|
+
|
|
1779
|
+
### 5. SPA 遷移時の「メッセージチャンネルクローズ」エラーを防ぐ
|
|
1780
|
+
|
|
1781
|
+
**エラーメッセージ:**
|
|
1782
|
+
```
|
|
1783
|
+
Uncaught (in promise) Error: A listener indicated an asynchronous response
|
|
1784
|
+
by returning true, but the message channel closed before a response was received
|
|
1785
|
+
```
|
|
1786
|
+
|
|
1787
|
+
**発生メカニズム(2つの独立した原因):**
|
|
1788
|
+
|
|
1789
|
+
| # | 原因 | 性質 | 対処 |
|
|
1790
|
+
|---|------|------|------|
|
|
1791
|
+
| ① | `addEventListener` に `async` 関数を直接渡した(Promise を返すため) | JavaScript Promise rejection | 設計原則4の `void (async () => {})()` パターンで修正 |
|
|
1792
|
+
| ② | @next2d/player が `app.initialize()` 完了後に DOM 追加する IME 用 `<textarea tabindex="-1">` を Chrome 拡張機能(パスワードマネージャー等)が検出し、SPA 遷移でチャンネルが閉じる | **Chrome 拡張機能のランタイムエラー**(JS Promise rejection ではない) | 初期化後に textarea へ `autocomplete="off"` 等を設定して拡張機能の検出を抑制 |
|
|
1793
|
+
|
|
1794
|
+
> **重要:** 原因②は JavaScript の Promise rejection ではなく Chrome 拡張機能のランタイムエラーです。
|
|
1795
|
+
> そのため `window.addEventListener("unhandledrejection", ...)` では**捕捉・抑制できません**。
|
|
1796
|
+
> `unhandledrejection` ハンドラは原因①(コード起因)の残存ケースに対する安全網として追加します。
|
|
1797
|
+
|
|
1798
|
+
**アプリエントリポイント (`index.ts`) への対処:**
|
|
1799
|
+
|
|
1800
|
+
```typescript
|
|
1801
|
+
// ① コード起因の Promise rejection が残存する場合の安全網(unhandledrejection で捕捉可能)
|
|
1802
|
+
// 原因②(Chrome 拡張起因)はここでは捕捉できないため、別途 textarea 対策が必要
|
|
1803
|
+
window.addEventListener("unhandledrejection", (event) =>
|
|
1804
|
+
{
|
|
1805
|
+
const reason = event.reason;
|
|
1806
|
+
const message = (typeof reason === "string"
|
|
1807
|
+
? reason
|
|
1808
|
+
: (reason as { message?: string } | null)?.message) ?? "";
|
|
1809
|
+
if (message.includes("A listener indicated an asynchronous response by returning true")) {
|
|
1810
|
+
event.preventDefault();
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
|
|
1814
|
+
const boot = async (): Promise<void> =>
|
|
1815
|
+
{
|
|
1816
|
+
await app.initialize(config, packages).run();
|
|
1817
|
+
|
|
1818
|
+
// ② @next2d/player の IME 用グローバル textarea に Chrome 拡張が干渉するのを防ぐ
|
|
1819
|
+
// tabIndex="-1" の textarea は @next2d/player が app.initialize() 完了時に DOM 追加する
|
|
1820
|
+
// Chrome の自動補完やパスワードマネージャーがこの textarea を検出すると
|
|
1821
|
+
// SPA 遷移時にメッセージチャンネルエラーが発生する(JS Promise rejection ではなく
|
|
1822
|
+
// Chrome 拡張のランタイムエラーのため unhandledrejection ハンドラでは捕捉不可)
|
|
1823
|
+
const internalTextarea = document.querySelector('textarea[tabindex="-1"]');
|
|
1824
|
+
if (internalTextarea) {
|
|
1825
|
+
internalTextarea.setAttribute("autocomplete", "off");
|
|
1826
|
+
internalTextarea.setAttribute("data-form-type", "other");
|
|
1827
|
+
internalTextarea.setAttribute("role", "presentation");
|
|
1828
|
+
internalTextarea.setAttribute("aria-hidden", "true");
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
await app.gotoView();
|
|
1832
|
+
};
|
|
1833
|
+
```
|
|
1834
|
+
|
|
1733
1835
|
## View/ViewModel作成のテンプレート
|
|
1734
1836
|
|
|
1735
1837
|
### View
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next2d-development-mcp",
|
|
3
3
|
"displayName": "Next2D Development MCP",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.6",
|
|
5
5
|
"description": "MCP server for Next2D application development assistance",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"author": "Toshiyuki Ienaga <ienaga@next2d.app>",
|
|
@@ -52,19 +52,19 @@
|
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@eslint/js": "^10.0.1",
|
|
55
|
-
"@types/node": "^25.6.
|
|
55
|
+
"@types/node": "^25.6.2",
|
|
56
56
|
"@types/vscode": "^1.115.0",
|
|
57
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
58
|
-
"@typescript-eslint/parser": "^8.
|
|
59
|
-
"@vscode/vsce": "^3.
|
|
60
|
-
"eslint": "^10.
|
|
57
|
+
"@typescript-eslint/eslint-plugin": "^8.59.2",
|
|
58
|
+
"@typescript-eslint/parser": "^8.59.2",
|
|
59
|
+
"@vscode/vsce": "^3.9.1",
|
|
60
|
+
"eslint": "^10.3.0",
|
|
61
61
|
"eslint-plugin-unused-imports": "^4.4.1",
|
|
62
|
-
"globals": "^17.
|
|
63
|
-
"typescript": "^6.0.
|
|
64
|
-
"vitest": "^4.1.
|
|
62
|
+
"globals": "^17.6.0",
|
|
63
|
+
"typescript": "^6.0.3",
|
|
64
|
+
"vitest": "^4.1.5"
|
|
65
65
|
},
|
|
66
66
|
"engines": {
|
|
67
67
|
"node": ">=22.0.0",
|
|
68
|
-
"vscode": "^1.
|
|
68
|
+
"vscode": "^1.115.0"
|
|
69
69
|
}
|
|
70
70
|
}
|