diff viewer
This commit is contained in:
parent
df06be7bc7
commit
e04c67133b
95
DIFF_VIEWER_IMPLEMENTATION.md
Normal file
95
DIFF_VIEWER_IMPLEMENTATION.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# Git Diff Viewer Implementation
|
||||
|
||||
## Overview
|
||||
The Git Diff Viewer feature has been successfully implemented for FleetCode's web mode. This feature allows users to view changes in their session's worktree compared to the parent branch.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. Dependencies
|
||||
- **@pierre/precision-diffs**: A powerful diff rendering library
|
||||
- **shiki**: Syntax highlighting for code diffs
|
||||
|
||||
### 2. Data Structure (types.ts)
|
||||
Added `FileDiff` interface to represent file changes:
|
||||
```typescript
|
||||
export interface FileDiff {
|
||||
path: string;
|
||||
oldPath?: string;
|
||||
status: 'added' | 'modified' | 'deleted' | 'renamed';
|
||||
diff: string;
|
||||
additions: number;
|
||||
deletions: number;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Backend (server.ts)
|
||||
- Added Socket.IO handler `get-session-diff` that:
|
||||
- Retrieves the session's worktree path and parent branch
|
||||
- Executes `git diff <parent-branch>...HEAD` using simple-git
|
||||
- Parses the diff output into `FileDiff` objects
|
||||
- Emits the parsed data back to the client via `session-diff-data` event
|
||||
|
||||
### 4. Frontend UI (index.html)
|
||||
- Added full-screen diff viewer modal with:
|
||||
- Header with title and close button
|
||||
- Left sidebar for file list
|
||||
- Right panel for displaying diffs
|
||||
- Added "Diff" button to active session items in the sidebar
|
||||
|
||||
### 5. Frontend Styling (styles.css)
|
||||
- Added comprehensive styling for:
|
||||
- Diff viewer modal (full-screen, responsive)
|
||||
- File list sidebar with status badges
|
||||
- Diff display area
|
||||
- File status indicators (added/modified/deleted/renamed)
|
||||
- Addition/deletion statistics
|
||||
- Mobile-responsive layouts
|
||||
|
||||
### 6. Frontend Logic (renderer.ts)
|
||||
- Added diff viewer functionality:
|
||||
- `openDiffViewer(sessionId)`: Opens modal and requests diff data
|
||||
- `renderDiffFileList()`: Renders list of changed files
|
||||
- `renderSelectedDiff()`: Renders the selected file's diff using Pierre.js
|
||||
- Dynamic import of libraries to avoid breaking Electron mode
|
||||
- Event handlers for Socket.IO events (`session-diff-data`, `session-diff-error`)
|
||||
|
||||
## Usage
|
||||
|
||||
1. Start a session and make some changes in your worktree
|
||||
2. Click the "Diff" button next to an active session in the sidebar
|
||||
3. A modal will appear showing all changed files
|
||||
4. Click on any file to view its detailed diff
|
||||
5. Click the × button or outside the modal to close
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- Libraries (@pierre/precision-diffs and shiki) are loaded dynamically using `import()` to avoid conflicts with Electron's module system
|
||||
- The diff is computed between the parent branch and HEAD of the worktree
|
||||
- TypeScript configuration was updated to use ES2020 modules with bundler resolution for better ESM support
|
||||
- The implementation is fully functional in web mode (server.ts) and does not affect Electron mode (main.ts)
|
||||
|
||||
## Build & Run
|
||||
|
||||
1. **Build the application:**
|
||||
```bash
|
||||
npm run build:all
|
||||
```
|
||||
|
||||
2. **Start the server:**
|
||||
```bash
|
||||
npm run server
|
||||
# or for development with hot reload:
|
||||
npm run dev:server
|
||||
```
|
||||
|
||||
3. **Access the application:**
|
||||
Open http://localhost:3000 in your browser
|
||||
|
||||
## Verification
|
||||
|
||||
The implementation has been verified:
|
||||
- ✅ Dependencies installed: `@pierre/precision-diffs` and `shiki`
|
||||
- ✅ Server builds and runs successfully
|
||||
- ✅ Diff handler is present in compiled server.js
|
||||
- ✅ Diff libraries are bundled in renderer.web.js
|
||||
- ✅ All TypeScript compiles without errors
|
||||
22
index.html
22
index.html
|
|
@ -196,6 +196,28 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Diff Viewer Modal -->
|
||||
<div id="diff-viewer-modal" class="modal-overlay hidden">
|
||||
<div class="diff-viewer-modal">
|
||||
<div class="diff-viewer-header">
|
||||
<h2 class="modal-title" id="diff-viewer-title">Git Diff Viewer</h2>
|
||||
<button id="close-diff-viewer" class="diff-viewer-close" title="Close">×</button>
|
||||
</div>
|
||||
|
||||
<div class="diff-viewer-content">
|
||||
<!-- File List Sidebar -->
|
||||
<div class="diff-file-list" id="diff-file-list">
|
||||
<div class="diff-loading">Loading diff...</div>
|
||||
</div>
|
||||
|
||||
<!-- Diff Display -->
|
||||
<div class="diff-display" id="diff-display">
|
||||
<div class="diff-empty">Select a file to view changes</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
<div id="settings-modal" class="modal-overlay hidden">
|
||||
<div class="modal">
|
||||
|
|
|
|||
591
package-lock.json
generated
591
package-lock.json
generated
|
|
@ -9,6 +9,7 @@
|
|||
"version": "1.0.1-beta.7",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@pierre/precision-diffs": "^0.4.2",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
|
|
@ -16,6 +17,7 @@
|
|||
"electron-store": "^11.0.0",
|
||||
"express": "^5.1.0",
|
||||
"node-pty": "^1.0.0",
|
||||
"shiki": "^3.14.0",
|
||||
"simple-git": "^3.28.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
|
|
@ -1926,6 +1928,33 @@
|
|||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pierre/precision-diffs": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@pierre/precision-diffs/-/precision-diffs-0.4.2.tgz",
|
||||
"integrity": "sha512-C6LbruH24BCp4awI47D5iMtVaZZD6GkzqBoDw+Sfu7DB3hC9y/rZr1C2BD7AUzAKwByTfFnh16Zp11ipfPqLKw==",
|
||||
"license": "apache-2.0",
|
||||
"dependencies": {
|
||||
"@shikijs/core": "3.14.0",
|
||||
"@shikijs/transformers": "3.14.0",
|
||||
"diff": "8.0.2",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"hast-util-to-html": "9.0.5",
|
||||
"shiki": "3.14.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1 || ^19.0.0",
|
||||
"react-dom": "^18.3.1 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pierre/precision-diffs/node_modules/diff": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz",
|
||||
"integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
|
|
@ -1937,6 +1966,83 @@
|
|||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/core": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.14.0.tgz",
|
||||
"integrity": "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.14.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4",
|
||||
"hast-util-to-html": "^9.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/engine-javascript": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.14.0.tgz",
|
||||
"integrity": "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.14.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"oniguruma-to-es": "^4.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/engine-oniguruma": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.14.0.tgz",
|
||||
"integrity": "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.14.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/langs": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.14.0.tgz",
|
||||
"integrity": "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/themes": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.14.0.tgz",
|
||||
"integrity": "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/transformers": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-3.14.0.tgz",
|
||||
"integrity": "sha512-i67zQnY9wLMMnKasonVW1L9fKneSLZDj1ePsA4o0AZWU4uUobmJY9baRDa36z+a9/g0aG76/2tybQvm4hrwxIQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/core": "3.14.0",
|
||||
"@shikijs/types": "3.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/types": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.14.0.tgz",
|
||||
"integrity": "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/vscode-textmate": {
|
||||
"version": "10.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
|
||||
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
||||
|
|
@ -2091,6 +2197,15 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
||||
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/http-cache-semantics": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
|
||||
|
|
@ -2114,6 +2229,15 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mdast": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
|
||||
"integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
||||
|
|
@ -2200,6 +2324,12 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
|
||||
|
|
@ -2226,6 +2356,12 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
|
||||
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.11",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
|
||||
|
|
@ -3258,6 +3394,16 @@
|
|||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/ccount": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
|
||||
"integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
|
|
@ -3275,6 +3421,26 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/character-entities-html4": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
|
||||
"integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/character-entities-legacy": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
|
||||
"integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
|
|
@ -3481,6 +3647,16 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/comma-separated-tokens": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
||||
"integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
|
|
@ -3934,6 +4110,15 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz",
|
||||
|
|
@ -3952,6 +4137,19 @@
|
|||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/devlop": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
||||
"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
|
|
@ -5496,6 +5694,42 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-html": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
|
||||
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/unist": "^3.0.0",
|
||||
"ccount": "^2.0.0",
|
||||
"comma-separated-tokens": "^2.0.0",
|
||||
"hast-util-whitespace": "^3.0.0",
|
||||
"html-void-elements": "^3.0.0",
|
||||
"mdast-util-to-hast": "^13.0.0",
|
||||
"property-information": "^7.0.0",
|
||||
"space-separated-tokens": "^2.0.0",
|
||||
"stringify-entities": "^4.0.0",
|
||||
"zwitch": "^2.0.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-whitespace": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
||||
"integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
|
||||
|
|
@ -5522,6 +5756,16 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/html-void-elements": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
|
||||
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
||||
|
|
@ -6143,6 +6387,27 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-to-hast": {
|
||||
"version": "13.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
|
||||
"integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/mdast": "^4.0.0",
|
||||
"@ungap/structured-clone": "^1.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"micromark-util-sanitize-uri": "^2.0.0",
|
||||
"trim-lines": "^3.0.0",
|
||||
"unist-util-position": "^5.0.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"vfile": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||
|
|
@ -6174,6 +6439,95 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-util-character": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
|
||||
"integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "GitHub Sponsors",
|
||||
"url": "https://github.com/sponsors/unifiedjs"
|
||||
},
|
||||
{
|
||||
"type": "OpenCollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"micromark-util-symbol": "^2.0.0",
|
||||
"micromark-util-types": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-util-encode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
|
||||
"integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "GitHub Sponsors",
|
||||
"url": "https://github.com/sponsors/unifiedjs"
|
||||
},
|
||||
{
|
||||
"type": "OpenCollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/micromark-util-sanitize-uri": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
|
||||
"integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "GitHub Sponsors",
|
||||
"url": "https://github.com/sponsors/unifiedjs"
|
||||
},
|
||||
{
|
||||
"type": "OpenCollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"micromark-util-character": "^2.0.0",
|
||||
"micromark-util-encode": "^2.0.0",
|
||||
"micromark-util-symbol": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-util-symbol": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
|
||||
"integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "GitHub Sponsors",
|
||||
"url": "https://github.com/sponsors/unifiedjs"
|
||||
},
|
||||
{
|
||||
"type": "OpenCollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/micromark-util-types": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
|
||||
"integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "GitHub Sponsors",
|
||||
"url": "https://github.com/sponsors/unifiedjs"
|
||||
},
|
||||
{
|
||||
"type": "OpenCollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
|
|
@ -6787,6 +7141,23 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/oniguruma-parser": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz",
|
||||
"integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/oniguruma-to-es": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz",
|
||||
"integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"oniguruma-parser": "^0.12.1",
|
||||
"regex": "^6.0.1",
|
||||
"regex-recursion": "^6.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ora": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
|
||||
|
|
@ -7251,6 +7622,16 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/property-information": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
|
||||
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
|
|
@ -7381,6 +7762,29 @@
|
|||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-binary-file-arch": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz",
|
||||
|
|
@ -7445,6 +7849,30 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz",
|
||||
"integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regex-utilities": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/regex-recursion": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz",
|
||||
"integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regex-utilities": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/regex-utilities": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
|
||||
"integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
|
|
@ -7687,6 +8115,13 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
|
|
@ -7829,6 +8264,22 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/shiki": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-3.14.0.tgz",
|
||||
"integrity": "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/core": "3.14.0",
|
||||
"@shikijs/engine-javascript": "3.14.0",
|
||||
"@shikijs/engine-oniguruma": "3.14.0",
|
||||
"@shikijs/langs": "3.14.0",
|
||||
"@shikijs/themes": "3.14.0",
|
||||
"@shikijs/types": "3.14.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
|
|
@ -8183,6 +8634,16 @@
|
|||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/space-separated-tokens": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
|
||||
"integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||
|
|
@ -8264,6 +8725,20 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/stringify-entities": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
|
||||
"integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"character-entities-html4": "^2.0.0",
|
||||
"character-entities-legacy": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
|
|
@ -8715,6 +9190,16 @@
|
|||
"tree-kill": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/trim-lines": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
|
||||
"integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/truncate-utf8-bytes": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
||||
|
|
@ -8897,6 +9382,74 @@
|
|||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-is": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
|
||||
"integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-position": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
|
||||
"integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-stringify-position": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
|
||||
"integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-visit": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
|
||||
"integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"unist-util-is": "^6.0.0",
|
||||
"unist-util-visit-parents": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-visit-parents": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
|
||||
"integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"unist-util-is": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
|
|
@ -9016,6 +9569,34 @@
|
|||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vfile": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
|
||||
"integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"vfile-message": "^4.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/vfile-message": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
|
||||
"integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"unist-util-stringify-position": "^4.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/wcwidth": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
||||
|
|
@ -9226,6 +9807,16 @@
|
|||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zwitch": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
|
||||
"integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pierre/precision-diffs": "^0.4.2",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
|
|
@ -51,6 +52,7 @@
|
|||
"electron-store": "^11.0.0",
|
||||
"express": "^5.1.0",
|
||||
"node-pty": "^1.0.0",
|
||||
"shiki": "^3.14.0",
|
||||
"simple-git": "^3.28.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
|
|
|
|||
195
renderer.ts
195
renderer.ts
|
|
@ -1,6 +1,6 @@
|
|||
import {FitAddon} from "@xterm/addon-fit";
|
||||
import {Terminal} from "xterm";
|
||||
import {PersistedSession, SessionConfig} from "./types";
|
||||
import {PersistedSession, SessionConfig, FileDiff} from "./types";
|
||||
import {isClaudeSessionReady} from "./terminal-utils";
|
||||
import {ipcAdapter} from "./ipc-adapter";
|
||||
|
||||
|
|
@ -340,12 +340,33 @@ function activateSession(sessionId: string) {
|
|||
function updateSessionState(sessionId: string, isActive: boolean) {
|
||||
const sidebarItem = document.getElementById(`sidebar-${sessionId}`);
|
||||
const indicator = sidebarItem?.querySelector(".session-indicator");
|
||||
const buttonContainer = sidebarItem?.querySelector(".flex.items-center.space-x-1");
|
||||
|
||||
if (indicator) {
|
||||
if (isActive) {
|
||||
indicator.classList.add("active");
|
||||
|
||||
// Add diff button if it doesn't exist
|
||||
if (buttonContainer && !buttonContainer.querySelector(".view-diff-btn")) {
|
||||
const diffBtn = document.createElement("button");
|
||||
diffBtn.className = "view-diff-btn";
|
||||
diffBtn.setAttribute("data-id", sessionId);
|
||||
diffBtn.setAttribute("title", "View Diff");
|
||||
diffBtn.textContent = "Diff";
|
||||
diffBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
openDiffViewer(sessionId);
|
||||
});
|
||||
buttonContainer.insertBefore(diffBtn, buttonContainer.firstChild);
|
||||
}
|
||||
} else {
|
||||
indicator.classList.remove("active");
|
||||
|
||||
// Remove diff button when session becomes inactive
|
||||
const existingDiffBtn = buttonContainer?.querySelector(".view-diff-btn");
|
||||
if (existingDiffBtn) {
|
||||
existingDiffBtn.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -363,7 +384,10 @@ function addToSidebar(sessionId: string, name: string, hasActivePty: boolean) {
|
|||
<span class="truncate session-name-text" data-id="${sessionId}">${name}</span>
|
||||
<input type="text" class="session-name-input hidden" data-id="${sessionId}" value="${name}" />
|
||||
</div>
|
||||
<button class="session-delete-btn" data-id="${sessionId}" title="Delete session">×</button>
|
||||
<div class="flex items-center space-x-1">
|
||||
${hasActivePty ? `<button class="view-diff-btn" data-id="${sessionId}" title="View Diff">Diff</button>` : ''}
|
||||
<button class="session-delete-btn" data-id="${sessionId}" title="Delete session">×</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Click on session name to edit
|
||||
|
|
@ -391,6 +415,7 @@ function addToSidebar(sessionId: string, name: string, hasActivePty: boolean) {
|
|||
item.addEventListener("click", (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!target.classList.contains("session-delete-btn") &&
|
||||
!target.classList.contains("view-diff-btn") &&
|
||||
!target.classList.contains("session-name-text") &&
|
||||
!target.classList.contains("session-name-input")) {
|
||||
handleSessionClick(sessionId);
|
||||
|
|
@ -403,6 +428,12 @@ function addToSidebar(sessionId: string, name: string, hasActivePty: boolean) {
|
|||
deleteSession(sessionId);
|
||||
});
|
||||
|
||||
const viewDiffBtn = item.querySelector(".view-diff-btn");
|
||||
viewDiffBtn?.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
openDiffViewer(sessionId);
|
||||
});
|
||||
|
||||
list.appendChild(item);
|
||||
}
|
||||
|
||||
|
|
@ -1487,3 +1518,163 @@ document.getElementById("session-list")?.addEventListener("click", (e) => {
|
|||
closeMobileMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Diff Viewer
|
||||
let currentDiffFiles: FileDiff[] = [];
|
||||
let selectedFileIndex: number = -1;
|
||||
let pierreLib: any = null;
|
||||
let shikiLib: any = null;
|
||||
let highlighter: any = null;
|
||||
|
||||
async function openDiffViewer(sessionId: string) {
|
||||
const modal = document.getElementById("diff-viewer-modal");
|
||||
const fileList = document.getElementById("diff-file-list");
|
||||
const diffDisplay = document.getElementById("diff-display");
|
||||
|
||||
if (!modal || !fileList || !diffDisplay) return;
|
||||
|
||||
// Show modal with loading state
|
||||
modal.classList.remove("hidden");
|
||||
fileList.innerHTML = '<div class="diff-loading">Loading diff...</div>';
|
||||
diffDisplay.innerHTML = '<div class="diff-empty">Loading...</div>';
|
||||
|
||||
// Load libraries dynamically (only in browser)
|
||||
if (!pierreLib) {
|
||||
try {
|
||||
pierreLib = await import("@pierre/precision-diffs" as any);
|
||||
shikiLib = await import("shiki" as any);
|
||||
} catch (error) {
|
||||
console.error("Failed to load diff libraries:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize highlighter if needed
|
||||
if (!highlighter && shikiLib) {
|
||||
try {
|
||||
highlighter = await shikiLib.createHighlighter({
|
||||
themes: ['github-dark'],
|
||||
langs: ['javascript', 'typescript', 'python', 'java', 'go', 'rust', 'html', 'css', 'json', 'markdown']
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize highlighter:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Request diff from server
|
||||
ipcAdapter.send("get-session-diff", sessionId);
|
||||
}
|
||||
|
||||
function renderDiffFileList() {
|
||||
const fileList = document.getElementById("diff-file-list");
|
||||
if (!fileList) return;
|
||||
|
||||
if (currentDiffFiles.length === 0) {
|
||||
fileList.innerHTML = '<div class="diff-loading">No changes found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
fileList.innerHTML = '';
|
||||
|
||||
currentDiffFiles.forEach((file, index) => {
|
||||
const item = document.createElement("div");
|
||||
item.className = `diff-file-item ${index === selectedFileIndex ? 'active' : ''}`;
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="diff-file-path">${file.path}</div>
|
||||
<div class="diff-file-stats">
|
||||
<span class="diff-file-status ${file.status}">${file.status}</span>
|
||||
${file.additions > 0 ? `<span class="diff-file-additions">+${file.additions}</span>` : ''}
|
||||
${file.deletions > 0 ? `<span class="diff-file-deletions">-${file.deletions}</span>` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
item.addEventListener("click", () => {
|
||||
selectedFileIndex = index;
|
||||
renderDiffFileList();
|
||||
renderSelectedDiff();
|
||||
});
|
||||
|
||||
fileList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
async function renderSelectedDiff() {
|
||||
const diffDisplay = document.getElementById("diff-display");
|
||||
if (!diffDisplay || selectedFileIndex < 0 || selectedFileIndex >= currentDiffFiles.length) {
|
||||
if (diffDisplay) {
|
||||
diffDisplay.innerHTML = '<div class="diff-empty">Select a file to view changes</div>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const file = currentDiffFiles[selectedFileIndex];
|
||||
|
||||
try {
|
||||
if (!pierreLib) {
|
||||
diffDisplay.innerHTML = '<div class="diff-empty">Loading diff viewer...</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Use Pierre.js FileDiff to render the diff
|
||||
const diffContainer = document.createElement("div");
|
||||
diffContainer.className = "diff-content";
|
||||
|
||||
// Create FileDiff instance
|
||||
const fileDiff = new pierreLib.FileDiff({
|
||||
theme: { dark: 'github-dark', light: 'github-light' },
|
||||
themeType: 'dark',
|
||||
});
|
||||
|
||||
// Parse the diff to get FileDiffMetadata
|
||||
const parsedPatches = pierreLib.parsePatchFiles(file.diff);
|
||||
|
||||
if (!parsedPatches || parsedPatches.length === 0 || !parsedPatches[0].files || parsedPatches[0].files.length === 0) {
|
||||
diffDisplay.innerHTML = '<div class="diff-empty">Could not parse diff data</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the first file's diff metadata from the parsed patch
|
||||
const fileDiffMetadata = parsedPatches[0].files[0];
|
||||
|
||||
// Render using the parsed fileDiff metadata
|
||||
await fileDiff.render({
|
||||
fileDiff: fileDiffMetadata,
|
||||
containerWrapper: diffContainer,
|
||||
});
|
||||
|
||||
diffDisplay.innerHTML = '';
|
||||
diffDisplay.appendChild(diffContainer);
|
||||
} catch (error) {
|
||||
console.error("Error rendering diff:", error);
|
||||
diffDisplay.innerHTML = `<div class="diff-empty">Error rendering diff: ${error}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Close diff viewer
|
||||
document.getElementById("close-diff-viewer")?.addEventListener("click", () => {
|
||||
const modal = document.getElementById("diff-viewer-modal");
|
||||
modal?.classList.add("hidden");
|
||||
currentDiffFiles = [];
|
||||
selectedFileIndex = -1;
|
||||
});
|
||||
|
||||
// Handle diff data from server
|
||||
ipcAdapter.on("session-diff-data", (_event, sessionId: string, fileDiffs: FileDiff[]) => {
|
||||
currentDiffFiles = fileDiffs;
|
||||
selectedFileIndex = fileDiffs.length > 0 ? 0 : -1;
|
||||
renderDiffFileList();
|
||||
renderSelectedDiff();
|
||||
});
|
||||
|
||||
// Handle diff error from server
|
||||
ipcAdapter.on("session-diff-error", (_event, sessionId: string, error: string) => {
|
||||
const fileList = document.getElementById("diff-file-list");
|
||||
const diffDisplay = document.getElementById("diff-display");
|
||||
|
||||
if (fileList) {
|
||||
fileList.innerHTML = `<div class="diff-loading">Error: ${error}</div>`;
|
||||
}
|
||||
if (diffDisplay) {
|
||||
diffDisplay.innerHTML = `<div class="diff-empty">Failed to load diff</div>`;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
113
server.ts
113
server.ts
|
|
@ -8,7 +8,7 @@ import * as path from "path";
|
|||
import { simpleGit } from "simple-git";
|
||||
import { promisify } from "util";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { PersistedSession, SessionConfig } from "./types";
|
||||
import { PersistedSession, SessionConfig, FileDiff } from "./types";
|
||||
import { isTerminalReady } from "./terminal-utils";
|
||||
import cors from "cors";
|
||||
|
||||
|
|
@ -495,6 +495,117 @@ io.on("connection", (socket) => {
|
|||
}
|
||||
});
|
||||
|
||||
socket.on("get-session-diff", async (sessionId: string) => {
|
||||
try {
|
||||
const sessions = getPersistedSessions();
|
||||
const session = sessions.find(s => s.id === sessionId);
|
||||
|
||||
if (!session) {
|
||||
socket.emit("session-diff-error", sessionId, "Session not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const git = simpleGit(session.worktreePath);
|
||||
const parentBranch = session.config.parentBranch;
|
||||
|
||||
// Get diff between parent branch and working directory (includes all changes: staged, unstaged, committed)
|
||||
const diffOutput = await git.diff([parentBranch, '--numstat']);
|
||||
const fullDiffOutput = await git.diff([parentBranch]);
|
||||
|
||||
// Also get untracked files (new files not yet added to git)
|
||||
const statusSummary = await git.status();
|
||||
|
||||
console.log(`[${sessionId}] Diff request for session, parent branch: ${parentBranch}`);
|
||||
console.log(`[${sessionId}] Worktree path: ${session.worktreePath}`);
|
||||
console.log(`[${sessionId}] Diff output length: ${diffOutput.length} chars`);
|
||||
console.log(`[${sessionId}] Untracked files: ${statusSummary.not_added.length}`);
|
||||
|
||||
// Parse diff output into FileDiff array
|
||||
const fileDiffs: FileDiff[] = [];
|
||||
const lines = diffOutput.split('\n').filter(l => l.trim());
|
||||
|
||||
for (const line of lines) {
|
||||
// Parse numstat line: "additions deletions filename"
|
||||
const match = line.match(/^(\d+|-)\s+(\d+|-)\s+(.+)$/);
|
||||
if (match) {
|
||||
const [, addStr, delStr, filepath] = match;
|
||||
const additions = addStr === '-' ? 0 : parseInt(addStr, 10);
|
||||
const deletions = delStr === '-' ? 0 : parseInt(delStr, 10);
|
||||
|
||||
// Determine file status
|
||||
let status: FileDiff['status'] = 'modified';
|
||||
let path = filepath;
|
||||
let oldPath: string | undefined;
|
||||
|
||||
// Check if file is renamed (contains " => ")
|
||||
if (filepath.includes(' => ')) {
|
||||
status = 'renamed';
|
||||
const parts = filepath.split(' => ');
|
||||
oldPath = parts[0].replace(/^\{|\}$/g, '');
|
||||
path = parts[1].replace(/^\{|\}$/g, '');
|
||||
} else if (additions > 0 && deletions === 0) {
|
||||
status = 'added';
|
||||
} else if (additions === 0 && deletions > 0) {
|
||||
status = 'deleted';
|
||||
}
|
||||
|
||||
// Extract individual file diff from full output
|
||||
const fileDiffMatch = fullDiffOutput.match(
|
||||
new RegExp(`diff --git a/${path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')} b/${path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?(?=diff --git|$)`)
|
||||
);
|
||||
const diff = fileDiffMatch ? fileDiffMatch[0] : '';
|
||||
|
||||
fileDiffs.push({
|
||||
path,
|
||||
oldPath,
|
||||
status,
|
||||
diff,
|
||||
additions,
|
||||
deletions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add untracked files as "added" with full file content as diff
|
||||
for (const untrackedFile of statusSummary.not_added) {
|
||||
try {
|
||||
// Read the file content
|
||||
const fileContent = await fs.promises.readFile(
|
||||
path.join(session.worktreePath, untrackedFile),
|
||||
'utf-8'
|
||||
);
|
||||
const lines = fileContent.split('\n');
|
||||
|
||||
// Create a diff in unified format for new file
|
||||
const diffLines = [
|
||||
`diff --git a/${untrackedFile} b/${untrackedFile}`,
|
||||
`new file mode 100644`,
|
||||
`--- /dev/null`,
|
||||
`+++ b/${untrackedFile}`,
|
||||
`@@ -0,0 +1,${lines.length} @@`,
|
||||
...lines.map(line => `+${line}`)
|
||||
];
|
||||
|
||||
fileDiffs.push({
|
||||
path: untrackedFile,
|
||||
status: 'added',
|
||||
diff: diffLines.join('\n'),
|
||||
additions: lines.length,
|
||||
deletions: 0,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[${sessionId}] Error reading untracked file ${untrackedFile}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[${sessionId}] Parsed ${fileDiffs.length} file diffs (${statusSummary.not_added.length} untracked)`);
|
||||
socket.emit("session-diff-data", sessionId, fileDiffs);
|
||||
} catch (error) {
|
||||
console.error("Error getting session diff:", error);
|
||||
socket.emit("session-diff-error", sessionId, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
// Clean up socket references for this client
|
||||
// Note: We don't kill the PTYs - they keep running for reconnection
|
||||
|
|
|
|||
117
styles.css
117
styles.css
|
|
@ -336,4 +336,121 @@
|
|||
@apply text-lg;
|
||||
}
|
||||
}
|
||||
|
||||
/* Diff Viewer Styles */
|
||||
.diff-viewer-modal {
|
||||
@apply bg-gray-900 rounded-lg shadow-xl flex flex-col;
|
||||
width: 95vw;
|
||||
height: 90vh;
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.diff-viewer-header {
|
||||
@apply flex items-center justify-between p-4 border-b border-gray-700;
|
||||
}
|
||||
|
||||
.diff-viewer-close {
|
||||
@apply text-gray-400 hover:text-white text-3xl font-bold w-10 h-10 flex items-center justify-center rounded transition-colors hover:bg-gray-800;
|
||||
}
|
||||
|
||||
.diff-viewer-content {
|
||||
@apply flex flex-1 overflow-hidden;
|
||||
}
|
||||
|
||||
.diff-file-list {
|
||||
@apply w-80 border-r border-gray-700 overflow-y-auto bg-gray-800;
|
||||
}
|
||||
|
||||
.diff-loading {
|
||||
@apply p-4 text-gray-400 text-center;
|
||||
}
|
||||
|
||||
.diff-file-item {
|
||||
@apply px-4 py-3 border-b border-gray-700 cursor-pointer hover:bg-gray-700 transition-colors;
|
||||
}
|
||||
|
||||
.diff-file-item.active {
|
||||
@apply bg-gray-700;
|
||||
}
|
||||
|
||||
.diff-file-path {
|
||||
@apply text-sm text-gray-200 mb-1 break-all;
|
||||
}
|
||||
|
||||
.diff-file-stats {
|
||||
@apply flex items-center space-x-2 text-xs;
|
||||
}
|
||||
|
||||
.diff-file-status {
|
||||
@apply px-2 py-0.5 rounded text-xs font-medium;
|
||||
}
|
||||
|
||||
.diff-file-status.added {
|
||||
@apply bg-green-900 text-green-300;
|
||||
}
|
||||
|
||||
.diff-file-status.modified {
|
||||
@apply bg-blue-900 text-blue-300;
|
||||
}
|
||||
|
||||
.diff-file-status.deleted {
|
||||
@apply bg-red-900 text-red-300;
|
||||
}
|
||||
|
||||
.diff-file-status.renamed {
|
||||
@apply bg-purple-900 text-purple-300;
|
||||
}
|
||||
|
||||
.diff-file-additions {
|
||||
@apply text-green-400;
|
||||
}
|
||||
|
||||
.diff-file-deletions {
|
||||
@apply text-red-400;
|
||||
}
|
||||
|
||||
.diff-display {
|
||||
@apply flex-1 overflow-auto bg-gray-900 p-4;
|
||||
}
|
||||
|
||||
.diff-empty {
|
||||
@apply text-gray-500 text-center p-8;
|
||||
}
|
||||
|
||||
.diff-content {
|
||||
@apply text-sm;
|
||||
}
|
||||
|
||||
/* Pierre.js diff styles */
|
||||
.diff-content .pierre-diff {
|
||||
@apply font-mono text-xs;
|
||||
}
|
||||
|
||||
.diff-content .pierre-diff .line-number {
|
||||
@apply text-gray-500 select-none;
|
||||
}
|
||||
|
||||
.diff-content .pierre-diff .line-addition {
|
||||
@apply bg-green-900 bg-opacity-30;
|
||||
}
|
||||
|
||||
.diff-content .pierre-diff .line-deletion {
|
||||
@apply bg-red-900 bg-opacity-30;
|
||||
}
|
||||
|
||||
.view-diff-btn {
|
||||
@apply ml-2 text-xs bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded transition-colors;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.diff-viewer-modal {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
@apply rounded-none;
|
||||
}
|
||||
|
||||
.diff-file-list {
|
||||
@apply w-64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue