---
title: 20260902_Deno Desktopで 最強のDiffツールを作ろう
tags: 
author: [とらのあなラボ](https://docswell.com/user/toranoana_lab)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/4EZL385273.jpg?width=480
description: toranoana.deno#26での登壇資料です。
published: September 04, 26
canonical: https://docswell.com/s/toranoana_lab/5Q24JR-2026-09-04-165205
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/4EZL385273.jpg)

Deno Desktopで
最強のDiffツールを作ろう
toranoana.deno | 虎の穴ラボ株式会社 藤原 佳顕
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
1


# Page. 2

![Page Image](https://bcdn.docswell.com/page/Y76WXPV67V.jpg)

本日の流れ
1. 自己紹介
2. Diffツール自作の動機
3. 作ったものの概要
4. Denoの活用について
5. まとめと今後
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
2


# Page. 3

![Page Image](https://bcdn.docswell.com/page/G75MXKD574.jpg)

自己紹介ページ (藤原)
藤原 佳顕（ふじわら よしあき） : yoshiaki fujiwara、8年目
‣
‣
新規事業担当（ Fantia、Creatia）、アーキテクトチーム (CSIRTも)、AI推進チーム
出身
‣
大学：情報系(数学より)
‣
前職：独立系ソフトウェア会社、主に GISとWeb、ライブラリ開発
‣ TypeScript、Ruby on Rails、C#、C++、React、Vue、Angular
‣
入社理由
‣
‣
自分がスキルアップできそうな場所に行きたい
オタク系の話ができるところに行きたい
好きなモノ
‣
‣
‣
シューティングゲーム、格闘ゲーム、アトラスのゲーム
SF小説
プログラミング
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
3


# Page. 4

![Page Image](https://bcdn.docswell.com/page/9J29XW8GER.jpg)

Diffツール自作の動機
●
●
●
コードを書く速度は爆速になっている
○ Claude Code, Cursor, Aider が一瞬で数百行のパッチを大量生成
○ 差分の確認をしている時間が長い
○ 直接PR作ってGitHub上などで確認することもあるが、手元で確認できるツールが欲
しい
○ 個人的にはWinMergeが好きだが、Macにはない
加えて、AI生成差分に適応したものにしたい:
○ 1. 大量のノイズ差分: インデント変更・コメント書き換えで重要ロジックが埋もれる
○ 2. サイレントな破壊的変更: シグネチャ変更、try-catch消去、APIキー直書き
○ 3. プロンプトコンテキストの喪失: 「この差分、何をお願いして作らせたんだっけ？」
結論: 「AIのコードを安全・快適に審査する専用Diffツール」を作りたい
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
4


# Page. 5

![Page Image](https://bcdn.docswell.com/page/DEY48L6GJM.jpg)

作ったものの概要
●
●
●
既存Diffツールのペイン:
○ WinMerge: 爆速＆最強キーバインドだが Windows専用
○ VS Code 内蔵Diff: ディレクトリ比較が重い。エディタ系の機能は別にいらない
○ git diff (CLI): リッチな3-Wayマージやインタラクティブ編集が難しい
求めたもの: WinMerge級のキー操作 × クロスプラットフォーム × AIレビュー支援
それを作る土台に Deno を選んだ理由（先出し）:
○ 大きい点：Deno2.9でDeno Desktopが追加されたので使いたかった
○ ✓ deno desktop で TypeScriptだけ でGUIアプリが書ける（Rust不要・Node不要）
○ ✓ ファイルI/O・プロセス起動・HTTPサーバーが ランタイム標準API で完結
○ ✓ fmt / lint / check / test が同梱 — ツール開発の立ち上がりが最速
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
5


# Page. 6

![Page Image](https://bcdn.docswell.com/page/VJNYQ4Z878.jpg)

作ったものの概要
CLIから一発起動:
● $ diffrex base.ts target.ts --prompt &quot;JWT認証を追加&quot;
● diffrexはアプリの名前
主な特徴:
● 🖥 Deno Desktop製: 軽量WebViewで高速起動、deno desktop -o で配布
● 🧠 AI Context Header / 🔇 Noise Filter / ⚠ Risk Heuristics
● ⌨ One-key Review: A（承認）・R（拒否）・E（編集）
● 📁 ディレクトリ比較 / 🔀 3-Wayマージ
● 🖼 画像・JSON・CSV Diff / 🌲 AST Semantic Diff
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
6


# Page. 7

![Page Image](https://bcdn.docswell.com/page/YE9PDQZXJ3.jpg)

作ったものの概要
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
7


# Page. 8

![Page Image](https://bcdn.docswell.com/page/GE8D3G19ED.jpg)

作ったものの概要
構成要素のほぼすべてが Denoランタイム標準 API +
JSR @std:
● CLI引数解析: jsr:@std/cli
● ディレクトリ走査 : jsr:@std/fs + Deno.stat
● 静的解析: web-tree-sitter (WASM)
● 安全なファイル I/O: Deno.writeFile /
Deno.rename
● OSネイティブ連携 : Deno.Command
● 配信 &amp; IPC: Deno.serve (HTTP + WebSocket)
● ウィンドウ: Deno.BrowserWindow
npmから借りたのは Preact と CodeMirror だけ
外部プロセスもフレームワークも無し
[CLI 実行: diffrex src/ dst/ --prompt &quot;...&quot;]
│
▼
┌───────────────────────────────────────────
───┐
│ Deno Backend（Deno標準API + JSR @std）
│
│ ├─ CLI引数解析 ....... jsr:@std/cli
│
│ ├─ ディレクトリ走査 .. jsr:@std/fs+Deno.stat│
│ ├─ 静的解析 .......... web-tree-sitter(WASM)│
│ ├─ ファイルI/O ....... Deno.writeFile/rename│
│ ├─ OS連携 ............ Deno.Command
│
│ └─ 配信 &amp; IPC ........ Deno.serve (HTTP+WS) │
└─────────────────────┬─────────────────────
───┘
│ WebSocket IPC (型付きJSON)
▼
┌───────────────────────────────────────────
───┐
│ Deno.BrowserWindow（OS標準WebView）
│
│ ├─ Model: DiffSessionModel (deno testable) │
│ ├─ View: Preact JSX + CodeMirror 6 MergeView│
│ └─ Controller: キーバインド調停 / マージ実行 │
└───────────────────────────────────────────
───┘
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
8


# Page. 9

![Page Image](https://bcdn.docswell.com/page/LELMVGQM7R.jpg)

Denoの活用について
なぜ Deno でデスクトップアプリを作るのか？
●
●
●
Electron / Tauri と比較したときの圧倒的気軽さ :
○ vs Electron: node_modulesの巨大さ・数百 MBの配布サイズ・ビルド待ちがゼロ
○ vs Tauri: 高速だがRustツールチェーンの学習と環境構築が必要
Deno Desktop (Deno v2.9+):
○ ✓ Rust も Node もインストール不要（ Deno本体さえあれば一発起動）
○ ✓ TypeScriptがビルド設定ゼロでそのまま動く
○ ✓ OS標準WebView（Windows: WebView2, macOS: WebKit）で超軽量・省メモリ
Denoランタイムが提供する強力な道具箱 :
○ 🛠 オールインワン : Linter, Formatter, Test, Check が最初から付属
○ 🔒 明示的パーミッション : 読み書きパスや実行コマンドの制限で安全性を担保
○ 📦 JSR / npm / WASM の併用: Web標準APIとエコシステムの全部取り
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
9


# Page. 10

![Page Image](https://bcdn.docswell.com/page/4JMYGQ16JW.jpg)

Denoの活用について :WebSocketの活用
●
●
●
deno desktop による起動部分 (Deno v2.9+):
○ Deno.BrowserWindow API が提供される
○ 画面立ち上げと同時にローカルWebSocket
サーバーを生成
Web標準 Import Attributes (with { type: &#039;text&#039; }):
○ DenoがネイティブサポートするWeb標準仕様
○ HTML, CSS, JSをバンドラーなしでそのままイ
ンポート可能
○ webpack等のアセットローダー設定が一切不
要
Deno.serve もランタイム標準:
○ ポート 0 指定で安全な空きポート自動割当
○ デスクトップアプリとする場合どのポートが使わ
れているかわからないため必要
// src/desktop/window.ts (抜粋)
import indexHtml from &quot;../ui/index.html&quot; with { type: &quot;text&quot; };
import stylesCss from &quot;../ui/styles.css&quot; with { type: &quot;text&quot; };
import bundleJs from &quot;../ui/bundle.js&quot; with { type: &quot;text&quot; };
export async function launchDesktopApp(session: DiffSessionData) {
// ① ローカルに一時的 WebSocketサーバーをポート 0 (自動割当 ) で起動
const server = Deno.serve({ port: 0 }, handleHttpAndWs);
const port = (server.addr as Deno.NetAddr).port;
// ② Deno Desktop ウィンドウを生成
// @ts-ignore: Deno.BrowserWindow is available under deno desktop
const win = new Deno.BrowserWindow({
title: &quot;Diffrex&quot;,
width: 1400,
height: 900,
});
win.loadURL(`http://localhost:${port}/?wsPort=${port}`);
}
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
10


# Page. 11

![Page Image](https://bcdn.docswell.com/page/PJR9Z8LN79.jpg)

Denoの活用について :WebSocketの活用
● Deno Backend（フルアクセス）:
○ ファイル読み書き / ディレクトリ走査
○ Diff計算 / Noise / Risk解析
○ OSコマンド実行 / プロセス終了コード管理
○ ただし画面を描けない
● WebView UI（サンドボックス）:
○ DOM / CodeMirror 6 描画、キー・マウス入力
○ ローカルファイルへの直接アクセスは不可❌
■ → 両者の能力を交換する専用線 = WebSocket
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
11


# Page. 12

![Page Image](https://bcdn.docswell.com/page/PEXQ98W8JX.jpg)

Denoの活用について :WebSocketの活用
●
●
●
具体的なWebSocketの目的:
○ 1. セッションデータの供給: 起動時のDiff結果・ファイルツリーをUIへプッシュ
○ 2. オンデマンド処理の依頼: 「このファイルのDiffをちょうだい」（重い計算の遅延実行）
○ 3. 特権操作の代行依頼: ファイル保存・ダイアログ表示・プロセス終了（Exit Code）
○ 4. 状態の即時通知: 保存結果・エラーを「サーバー側から」UIへ返す
なぜ HTTP（fetch）だけではダメ？:
○ 双方向性: dialog:result のように「バックエンド側の完了イベント」をプッシュしたい
○ 接続 = 生存監視: WebSocketの切断でUIが「バックエンド喪失」を即検知できる
○ 順序保証された1本の会話: セッションを持つアプリでは常時接続の方が単純
なぜ独自プロトコルではなくWebSocket？:
○ WebView側で追加ライブラリなしに使える唯一のWeb標準双方向通信
○ Deno側も Deno.upgradeWebSocket がランタイム標準 — 依存ゼロで両端が揃う
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
12


# Page. 13

![Page Image](https://bcdn.docswell.com/page/3EK9PKVLED.jpg)

Denoの活用について :WebSocketの活用
たった1つの Deno.serve ハンドラの中に:
① /ws → Deno.upgradeWebSocket でWS接続へ昇格
② / → index.html 配信
③ /bundle.js → UIバンドル配信
●
Expressもwsパッケージも不要！
●
接続管理:
○
接続中ソケットは Set&lt;WebSocket&gt; で管理
○
broadcast() で全クライアントへ一斉送信
○
readyState チェックで安全に送信
●
安全なポート設計:
○
ポート0 = OSに空きポートを自動割当させる
○
127.0.0.1 バインド = 外部マシンから接続不可
const handler = (req: Request): Response =&gt; {
const url = new URL(req.url);
if (url.pathname === &quot;/ws&quot;) { // ① WSアップグレード
const { socket, response } = Deno.upgradeWebSocket(req);
socket.onopen = () =&gt; activeSockets.add(socket);
socket.onmessage = async (event) =&gt; {
const parsed = parseIncomingMessage(String(event.data));
if (!parsed) return;
switch (parsed.type) { /* ui:ready, save:request... */ }
};
socket.onclose = () =&gt; activeSockets.delete(socket);
return response;
}
if (url.pathname === &quot;/&quot;) { /* ② index.html 配信 */ }
if (url.pathname === &quot;/bundle.js&quot;) { /* ③ JS配信 */ }
return new Response(&quot;Not Found&quot;, { status: 404 });
};
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
13


# Page. 14

![Page Image](https://bcdn.docswell.com/page/L73WXZM675.jpg)

Denoの活用について
Deno.Command でゼロ依存ネイティブダイアログ
export async function openDirectoryDialog(title = &quot;フォルダを選択 &quot;): Promise&lt;string |
null&gt; {
if (Deno.build.os === &quot;windows&quot;) { // OS判定もランタイム標準
課題: よくあるデスクトップアプリのようにエクスプローラーやファイ
ンダーでファイルやディレクトリを選びたい
解決: Deno.Command で各OS標準のシェル・コマンドを呼び出
し
●
Windows: PowerShell + WinForms
FolderBrowserDialog
●
macOS: osascript (AppleScript)
●
Linux: zenity
const psScript = `
Add-Type -AssemblyName System.Windows.Forms
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
$dialog.Description = &quot;${title}&quot;
$topForm = New-Object System.Windows.Forms.Form; $topForm.TopMost = $true
if ($dialog.ShowDialog($topForm) -eq [System.Windows.Forms.DialogResult]::OK) {
Write-Output $dialog.SelectedPath
}
`;
Denoの効きどころ:
●
OS判定も Deno.build.os でランタイム標準
●
--allow-run で実行可能コマンドを絞れる（パーミッションモ
デル）
●
追加のnpmパッケージ・ネイティブアドオンはゼロ
const cmd = new Deno.Command(&quot;powershell&quot;, {
args: [&quot;-NoProfile&quot;, &quot;-NonInteractive&quot;, &quot;-WindowStyle&quot;, &quot;Hidden&quot;, &quot;-Command&quot;,
psScript],
stdout: &quot;piped&quot;,
});
const { stdout } = await cmd.output();
Windowsでの工夫:
●
-WindowStyle Hidden で黒いコンソール窓を非表示化
●
TopMost = $true でダイアログが背面に隠れるのを防止
const path = new TextDecoder().decode(stdout).trim();
return path.length &gt; 0 ? path : null;
}
// macOS は osascript、Linux は zenity で実行 ...
}
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
14


# Page. 15

![Page Image](https://bcdn.docswell.com/page/87DK2R6MJG.jpg)

Denoの活用について :Diff表示について
基本のDiff表示については CodeMirrorを利用
● CodeMirrorのDiffは文字オフセット基準・エ
ディタ内専用 → バックエンド解析には使えな
い
● Noise/Risk解析や3-Wayマージは
「行の集合としての Hunk」を入力にしたい
● Denoバックエンドで動く依存ゼロの純関数な
ら、deno test で網羅的に検証できる
どちらも心臓部は同じ「 Myers Diffアルゴリズム」
┌───────────────────────────────────────────┐
│ 系統①: 表示用 Diff
│
│
CodeMirror 6 @codemirror/merge
│
│
│
│ 目的: エディタ内の差分ハイライト・
│
│
左右アライメント
│
│ 流れ: Prefix/Suffix刈り取り
│
│
→ 文字単位Myers → presentableDiff
│
│ (5,000行中数行の変更なら99%を探索前に
│
│ 除外 → 数十msで完了)
│
└───────────────────────────────────────────┘
┌───────────────────────────────────────────┐
│ 系統②: 解析用 Diff
│
│
自前実装 src/core/diff.ts (Deno側)
│
│
│
│ 目的: Noise判定・Risk解析・3-Wayマージの │
│
「意味の単位」を作る
│
│ 流れ: splitLines
│
│
→ computeLineDiff (行単位Myers)
│
│
→ extractHunks (Hunk化)
│
│
→ 解析エンジンへ
│
└───────────────────────────────────────────┘
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
15


# Page. 16

![Page Image](https://bcdn.docswell.com/page/VJPK1WYQE8.jpg)

Denoの活用について :Diff表示について
Myers Diffについて
問題設定: 配列Aを配列Bに変換する
最小の編集手順（ delete/insertの列）を求める
編集グラフ: 横軸にA、縦軸にBを置いた
グリッド上の経路探索と等価
Myersの貪欲法（1986）:
- 編集コスト d = 0, 1, 2, ... を1つずつ増やし
ながら、各対角線 k = x - y 上で
「どこまで到達できるか」 (frontier) だけを記録
- 到達点から貪欲に対角線を滑り降りる
（一致行はタダで進める）
- ゴール到達時の d が編集距離
- 計算量 O((N+M)・D)
嬉しい性質: 「差分が少ないほど速い」
→ Diffツールの実利用（大半は小さな変更）に最適
B: c a t ▶
A ┌─┬─┬─┬─┐
c │↘│ │ │ │
→ 右移動 = Aの行を delete
a │ │↘│ │ │
↓ 下移動 = Bの行を insert
r │ │ │ │ │
↘ 対角線 = 行が一致
t │ │ │ │↘│
(equal, コスト0!)
▼ └─┴─┴─┴─┘
ゴール（右下）までの経路のうち
「斜めを最大限使う」経路 = 最短のDiff
d=0: 斜めだけでどこまで行ける？
d=1: 削除or挿入を1回使うとどこまで？
d=2: 2回使うと…（ゴールに着いたら終了）
D = 差分の量。変更が少ないファイルほど
探索が早く終わる → AIレビューの現場と好相性
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
16


# Page. 17

![Page Image](https://bcdn.docswell.com/page/2EVVY8GPEQ.jpg)

Denoの活用について :Diff表示について
// src/core/analysis/noise.ts (抜粋)
export function analyzeHunkNoise(
leftLines: string[],
Diff表示上の工夫
🔇 ノイズ差分の自動折りたたみ :
●
AIが勝手に変えたインデント・コメントだけの差
分を自動検知
● 空白正規化 + コメント除去後に一致すれば「ノイ
ズ」→ 起動時に折りたたみ
⚠ 破壊的変更を暴く Risk Heuristics:
● 🚨 Danger: 10行以上の連続コード削除（実装
の丸ごと蒸発）
● 🚨 Danger: 関数 / クラス / 型のシグネチャ変
更（後方互換性破壊）
● ⚠ Warning: try-catch や .catch() の削除（エ
ラー握りつぶし）
● ⚠ Warning: sk-..., ghp_..., AKIA... の追加
（シークレット混入）
Denoポイント:
● 解析エンジンは純関数の集まり → deno test で
網羅的にテスト
rightLines: string[],
): NoiseAnalysisResult {
// ① 空白・インデント・コメントを除去して正規化
const normLeft = leftLines.map(stripCommentsAndWhitespace).join(&quot;\n&quot;);
const normRight = rightLines.map(stripCommentsAndWhitespace).join(&quot;\n&quot;);
// ② 正規化後の一致判定
if (normLeft === normRight) {
return {
isNoise: true,
category: &quot;whitespace_or_comment&quot;,
foldByDefault: true, // 起動時に自動折りたたみ！
};
}
return { isNoise: false, category: &quot;logic_change&quot;, foldByDefault: false };
}
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
17


# Page. 18

![Page Image](https://bcdn.docswell.com/page/57GL958QEL.jpg)

// src/core/analysis/ast/ast_parser.ts (抜粋・簡略 )
Denoの活用について :Diff表示について
import Parser from &quot;web-tree-sitter&quot;;
// npm: 指定で Denoが直接解決
export function detectLanguageFromFilename(filename: string) {
●
●
●
●
行ベース Diffの限界: AIリファクタで最頻出の2パターンが「大
量の削除＋追加」に見えてしまう
○
関数を末尾に移動しただけ → Myers的には全行削
除＋全行追加
○
変数を x → deltaX に一括リネームしただけ → 触れ
た行が全部差分
Tree-sitter とは: GitHub発のインクリメンタル・エラー耐性
パーサー基盤
○
文法ごとにパーサーをWASMバイナリ として配布 →
どのランタイムでも同じ挙動
Diffrexでの構成（ Denoの効きどころ） :
○
npm:web-tree-sitter を deno.json の1行で導入
○
6言語（TypeScript / JavaScript / Python / Rust /
Go / Ruby）のWASMを vendor/tree-sitter/ にオフラ
イン同梱
○
deno task setup:wasmsで一括ダウンロード — 実行
時はネットワーク不要
Graceful Fallback（段階的縮退） :
○
WASM不在・未対応言語・パース失敗 → すべて
null を返して行 Diffのみで動作継続
if (lower.endsWith(&quot;.ts&quot;) || lower.endsWith(&quot;.tsx&quot;)) return &quot;typescript&quot;;
if (lower.endsWith(&quot;.py&quot;)) return &quot;python&quot;;
// .js / .rs / .go / .rb ... 6言語に対応。未対応は
null
}
export async function loadLanguage(lang: SupportedLanguage) {
if (loadedLanguages.has(lang)) return loadedLanguages.get(lang)!; // キャッシュ
// WASMをローカルから読込
const bytes = await Deno.readFile(
join(Deno.cwd(), &quot;vendor&quot;, &quot;tree-sitter&quot;, `tree-sitter-${lang}.wasm`));
const language = await Parser.Language.load(bytes); // WASMバイナリをロード
loadedLanguages.set(lang, language);
return language;
}
export async function parseCodeToAst(code: string, langOrFilename: string) {
const language = await loadLanguage(lang);
if (!language) return null;
// ★ Graceful Fallback: 落とさず縮退
const parser = new Parser();
parser.setLanguage(language);
return { tree: parser.parse(code), parser, language: lang };
}
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
18


# Page. 19

![Page Image](https://bcdn.docswell.com/page/4EQYWZ9WJP.jpg)

Denoの活用について :Diff表示について
●
●
●
Step 1: `extractBlockNodes` — ASTから関数・クラス・メソッドを抽出し、 3つの指紋を付与
○ normalizedText: コメント・空白を除去した本文 (内容が同じか )
○ syntaxSignature: ノード型名だけを深さ 8まで列挙（識別子名を含まない ）(構造が同じか )
○ identifiers: 出現順の識別子リスト (名前がどう変わったか )
Move検出: `normalizedText` が一致 &amp; 行位置が異なるペア → 「移動しただけ」
○ findMoveForHunk`がHunkの行範囲と突き合わせ、該当 Hunkに **[Move]** バッジを注釈
Rename検出: `syntaxSignature` 一致 &amp; 識別子数一致 → 置換マップを検証
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
19


# Page. 20

![Page Image](https://bcdn.docswell.com/page/KJ4WK3X171.jpg)

// src/core/analysis/ast/rename_detector.ts (抜粋・簡略)
export function detectBlockRename(leftNode: ASTBlockNode, rightNode: ASTBlockNode) {
// ① 構文構造が完全一致しているか（識別子名は見ない）
if (leftNode.syntaxSignature !== rightNode.syntaxSignature) return null;
// ② 識別子の個数が一致しているか
if (leftNode.identifiers.length !== rightNode.identifiers.length) return null;
// ③ 出現位置の対応で置換マップを構築 + 双方向の一貫性チェック
const renameMap = {}; const reverseMap = {}; let renameCount = 0;
for (let i = 0; i &lt; leftNode.identifiers.length; i++) {
const l = leftNode.identifiers[i], r = rightNode.identifiers[i];
if (l === r) continue;
if (renameMap[l] &amp;&amp; renameMap[l] !== r) return null;
// 非一貫 → ロジック変更かも
if (reverseMap[r] &amp;&amp; reverseMap[r] !== l) return null; // 逆写像も検証
renameMap[l] = r; reverseMap[r] = l; renameCount++;
}
if (renameCount === 0 || renameCount &gt; 5) return null;
// 過剰リネームは疑う
return { renameMap, summaryTag: `[Rename] x -&gt; deltaX, y -&gt; deltaY` };
}
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
20


# Page. 21

![Page Image](https://bcdn.docswell.com/page/LE1YX1N57G.jpg)

Denoの活用について : GUI MVC
UI部分の実装は MVCで実装
ReactもReduxも使わず、50行のピュアTS Observerで
状態管理:
● Modelが能動的なSubjectとしてViewへ通知す
る三角形構造
なぜMVCなのか:
1. CodeMirror 6 の内部Stateと衝突しない
○ 仮想DOMの全体再描画を回避
2. 外部依存ゼロ — RxJS / MobX 不要
3. deno test でそのままテストできる
○ ModelとControllerはDOM非依存
○ ブラウザも jsdom も不要で高速に単体テ
スト！
○ deno task test 一発、セットアップゼロ
【現代の Web MVC（リクエスト-レスポンス型）】
ユーザー ─(HTTP Req)─► Controller ─► Model(受動的/DB)
─► View(HTML生成) ─► 破棄
※ 一方向に流れて終わり。リクエスト毎に使い捨て
【原初GUI MVC（Smalltalk-80 / 1970年代の
デスクトップGUIの原型）】
ユーザー入力（キー・マウス・IPC）
│
▼
Controller ──(更新指示)──► Model
│
(Active Domain State)
│
│
│ (エディタ直接操作)
▼ Observer Patternで
│
│ 自己通知
└──────────────────► View（永続的 / Observer）
※ 三角形の循環フロー。全員が生き続け、
Modelが変更を「自分から」知らせる
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
21


# Page. 22

![Page Image](https://bcdn.docswell.com/page/GEWGN84WJ2.jpg)

// src/ui/model/observable.ts — 外部依存ゼロのリアクティブ基盤
export class Observable&lt;T&gt; {
Denoの活用について : GUI MVC
Observable（全51行・依存ゼロ） :
● subscribe が購読解除関数を返す
○ クリーンアップが素直に書ける
● notify は try-catch で1つのObserverの失敗が全
体を止めないよう防御
private observers: Set&lt;(value: T) =&gt; void&gt; = new Set();
subscribe(observer: (value: T) =&gt; void): () =&gt; void {
this.observers.add(observer);
return () =&gt; this.observers.delete(observer);
}
notify(value: T): void {
for (const o of this.observers) {
Active Model = 状態 + ロジック + 自己通知:
● 状態変更メソッドの最後で必ず notify(this)
try { o(value); } catch (e) { console.error(e); }
}
}
useModel = 原初MVCとPreactの橋渡し:
● 通知が来たらダミー stateをインクリメントして再描
画をトリガーするだけ
}
// Model が能動的に View へ通知する（ Smalltalk-80 スタイル）
export class DiffSessionModel extends Observable&lt;DiffSessionModel&gt; {
private activeHunkIndex = 0;
Viewは「参照して描くだけ」 :
● const model = useModel(sessionModel);
selectNextHunk(): void {
this.activeHunkIndex++;
通知粒度は「Model全体」と割り切り :
● 差分検知はPreactの仮想DOMに任せて実装を最
小化
this.notify(this); // 変更された自分自身を Viewへ能動通知！
}
}
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
22


# Page. 23

![Page Image](https://bcdn.docswell.com/page/47ZL3822J3.jpg)

Denoでデスクトップ GUIを作って得られた知見
⚠ ハマりどころ &amp; 工夫した点
⭕ ここが最高だった（メリット）
●
●
●
●
●
Electron特有の起動時のモッサリ感が皆無
clone して deno task dev:desktop だけで
開発開始
TextEncoder, crypto.randomUUID,
ReadableStream, WebSocket がバックエン
ドでもそのまま動く
deno test でバックエンドもフロントの Model
も一瞬でテスト完結
デスクトップアプリの UI調整がWebフロント
並みの速度で回る
WebView内のダイアログ制限 :
● &lt;input type=&#039;file&#039;&gt; のパス隠蔽を
Deno.Command で迂回
Deno Desktop APIはまだ発展途上 (v2.9時
点):
● トレイ常駐や細かいウィンドウ制御は今
後の進化に期待
ビルドの割り切り :
● UIだけ esbuild (+deno-loader) で
bundle.js に1ファイル化
● Import Attributes で埋め込み
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
23


# Page. 24

![Page Image](https://bcdn.docswell.com/page/YJ6WXK66JV.jpg)

まとめ
●
●
●
●
🦖 Deno Desktop はデスクトップツール開発の「新しい強力な選択肢」
○ Rust不要・Node不要・ビルド設定不要。 TypeScriptとWeb標準APIだけで実用GUIが作れた
🛡 Denoの効きどころ:
○ Deno.BrowserWindow + Import Attributes / Deno.serve + WebSocket IPC
○ Deno.rename の原子的保存 / Deno.Command のゼロ依存OS連携
○ Deno.stat &amp; ストリームの Tiered Diﬃng / JSR × npm × WASM 混載
○ --hmr 開発から deno desktop -o 配布まで
💡 Denoの「オールインワン」はデスクトップ開発でこそ光る :
○ fmt / lint / check / test / task / compile — deno コマンドだけで完結
🤖 AIコーディング時代だからこそ、人間に優しいツールを、軽量なランタイムで :
○ AIレビューの認知負荷を下げるツールを、 Denoなら最速で形にできる！
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
24


# Page. 25

![Page Image](https://bcdn.docswell.com/page/GJ5MXP65J4.jpg)

ご清聴ありがとうございました！
リポジトリ: https://github.com/zonuko/diffrex
Copyright (C) 2026 Toranoana Lab Inc. All Rights Reserved.
25


