Compare commits
3 commits
df06be7bc7
...
f75c054e65
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f75c054e65 | ||
|
|
35983681e4 | ||
|
|
e04c67133b |
20
.beads/.gitignore
vendored
Normal file
20
.beads/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# SQLite databases
|
||||
*.db
|
||||
*.db-journal
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
|
||||
# Daemon runtime files
|
||||
daemon.lock
|
||||
daemon.log
|
||||
daemon.pid
|
||||
bd.sock
|
||||
|
||||
# Legacy database files
|
||||
db.sqlite
|
||||
bd.db
|
||||
|
||||
# Keep JSONL exports and config (source of truth for git)
|
||||
!*.jsonl
|
||||
!metadata.json
|
||||
!config.json
|
||||
56
.beads/config.yaml
Normal file
56
.beads/config.yaml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Beads Configuration File
|
||||
# This file configures default behavior for all bd commands in this repository
|
||||
# All settings can also be set via environment variables (BD_* prefix)
|
||||
# or overridden with command-line flags
|
||||
|
||||
# Issue prefix for this repository (used by bd init)
|
||||
# If not set, bd init will auto-detect from directory name
|
||||
# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc.
|
||||
# issue-prefix: ""
|
||||
|
||||
# Use no-db mode: load from JSONL, no SQLite, write back after each command
|
||||
# When true, bd will use .beads/issues.jsonl as the source of truth
|
||||
# instead of SQLite database
|
||||
# no-db: false
|
||||
|
||||
# Disable daemon for RPC communication (forces direct database access)
|
||||
# no-daemon: false
|
||||
|
||||
# Disable auto-flush of database to JSONL after mutations
|
||||
# no-auto-flush: false
|
||||
|
||||
# Disable auto-import from JSONL when it's newer than database
|
||||
# no-auto-import: false
|
||||
|
||||
# Enable JSON output by default
|
||||
# json: false
|
||||
|
||||
# Default actor for audit trails (overridden by BD_ACTOR or --actor)
|
||||
# actor: ""
|
||||
|
||||
# Path to database (overridden by BEADS_DB or --db)
|
||||
# db: ""
|
||||
|
||||
# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON)
|
||||
# auto-start-daemon: true
|
||||
|
||||
# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE)
|
||||
# flush-debounce: "5s"
|
||||
|
||||
# Multi-repo configuration (experimental - bd-307)
|
||||
# Allows hydrating from multiple repositories and routing writes to the correct JSONL
|
||||
# repos:
|
||||
# primary: "." # Primary repo (where this database lives)
|
||||
# additional: # Additional repos to hydrate from (read-only)
|
||||
# - ~/beads-planning # Personal planning repo
|
||||
# - ~/work-planning # Work planning repo
|
||||
|
||||
# Integration settings (access with 'bd config get/set')
|
||||
# These are stored in the database, not in this file:
|
||||
# - jira.url
|
||||
# - jira.project
|
||||
# - linear.url
|
||||
# - linear.api-key
|
||||
# - github.org
|
||||
# - github.repo
|
||||
# - sync.branch - Git branch for beads commits (use BEADS_SYNC_BRANCH env var or bd config set)
|
||||
5
.beads/metadata.json
Normal file
5
.beads/metadata.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"database": "beads.db",
|
||||
"version": "0.23.0",
|
||||
"jsonl_export": "beads.jsonl"
|
||||
}
|
||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Use bd merge for beads JSONL files
|
||||
.beads/beads.jsonl merge=beads
|
||||
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
|
||||
58
flake.lock
58
flake.lock
|
|
@ -1,5 +1,27 @@
|
|||
{
|
||||
"nodes": {
|
||||
"beads": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1762602968,
|
||||
"narHash": "sha256-3F7sv4mAKBQ4YBSABgp9bendgHopObNivO5NQhJYUek=",
|
||||
"owner": "steveyegge",
|
||||
"repo": "beads",
|
||||
"rev": "367bf077e10695976480bba097e2ae47593a4d82",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "steveyegge",
|
||||
"ref": "v0.23.0",
|
||||
"repo": "beads",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
|
|
@ -18,6 +40,24 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1760038930,
|
||||
|
|
@ -36,7 +76,8 @@
|
|||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"beads": "beads",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
|
|
@ -54,6 +95,21 @@
|
|||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
|
|
|||
|
|
@ -4,9 +4,13 @@
|
|||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
beads = {
|
||||
url = "github:steveyegge/beads/v0.23.0";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
outputs = { self, nixpkgs, flake-utils, beads }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
|
|
@ -25,6 +29,9 @@
|
|||
(python3.withPackages (ps: with ps; [ setuptools ]))
|
||||
pkg-config
|
||||
|
||||
# Beads - AI code review tool
|
||||
beads.packages.${system}.default
|
||||
|
||||
# Native build dependencies for node-pty and Electron
|
||||
gcc
|
||||
gnumake
|
||||
|
|
|
|||
31
index.html
31
index.html
|
|
@ -196,6 +196,37 @@
|
|||
</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>
|
||||
<div class="diff-viewer-actions">
|
||||
<button id="merge-to-parent-btn" class="btn-merge" disabled title="Merge worktree branch into parent">
|
||||
Merge to Parent
|
||||
</button>
|
||||
<button id="close-diff-viewer" class="diff-viewer-close" title="Close">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="diff-viewer-content">
|
||||
<!-- File List Sidebar -->
|
||||
<div class="diff-file-list" id="diff-file-list">
|
||||
<button id="toggle-diff-sidebar" class="diff-sidebar-toggle">
|
||||
<span class="toggle-expand">>></span>
|
||||
<span class="toggle-collapse"><<</span>
|
||||
</button>
|
||||
<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",
|
||||
|
|
|
|||
340
renderer.ts
340
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, MergeStatus, MergeResult} 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>
|
||||
<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,310 @@ document.getElementById("session-list")?.addEventListener("click", (e) => {
|
|||
closeMobileMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Diff Viewer
|
||||
let currentDiffFiles: FileDiff[] = [];
|
||||
let selectedFileIndex: number = -1;
|
||||
let currentDiffSessionId: string = '';
|
||||
let diffSidebarCollapsed: boolean = false;
|
||||
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");
|
||||
const mergeBtn = document.getElementById("merge-to-parent-btn") as HTMLButtonElement;
|
||||
|
||||
if (!modal || !fileList || !diffDisplay) return;
|
||||
|
||||
currentDiffSessionId = sessionId;
|
||||
|
||||
// Auto-collapse sidebar on mobile
|
||||
const isMobile = window.innerWidth < 768;
|
||||
diffSidebarCollapsed = isMobile;
|
||||
if (diffSidebarCollapsed) {
|
||||
fileList.classList.add("collapsed");
|
||||
} else {
|
||||
fileList.classList.remove("collapsed");
|
||||
}
|
||||
|
||||
// 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>';
|
||||
|
||||
if (mergeBtn) {
|
||||
mergeBtn.disabled = true;
|
||||
mergeBtn.textContent = "Merge to Parent";
|
||||
}
|
||||
|
||||
// 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 and merge status from server
|
||||
ipcAdapter.send("get-session-diff", sessionId);
|
||||
ipcAdapter.send("check-merge-status", sessionId);
|
||||
}
|
||||
|
||||
function renderDiffFileList() {
|
||||
const fileList = document.getElementById("diff-file-list");
|
||||
if (!fileList) return;
|
||||
|
||||
if (currentDiffFiles.length === 0) {
|
||||
fileList.innerHTML = '<button id="toggle-diff-sidebar" class="diff-sidebar-toggle"><span class="toggle-expand">>></span><span class="toggle-collapse"><<</span></button><div class="diff-loading">No changes found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
fileList.innerHTML = '<button id="toggle-diff-sidebar" class="diff-sidebar-toggle"><span class="toggle-expand">>></span><span class="toggle-collapse"><<</span></button>';
|
||||
|
||||
currentDiffFiles.forEach((file, index) => {
|
||||
const item = document.createElement("div");
|
||||
item.className = `diff-file-item ${index === selectedFileIndex ? 'active' : ''}`;
|
||||
|
||||
// Create file path div and set text content securely
|
||||
const pathDiv = document.createElement("div");
|
||||
pathDiv.className = "diff-file-path";
|
||||
pathDiv.textContent = file.path;
|
||||
|
||||
// Create stats div
|
||||
const statsDiv = document.createElement("div");
|
||||
statsDiv.className = "diff-file-stats";
|
||||
|
||||
// Create status badge
|
||||
const statusSpan = document.createElement("span");
|
||||
statusSpan.className = `diff-file-status ${file.status}`;
|
||||
statusSpan.textContent = file.status;
|
||||
statsDiv.appendChild(statusSpan);
|
||||
|
||||
// Add additions count if present
|
||||
if (file.additions > 0) {
|
||||
const additionsSpan = document.createElement("span");
|
||||
additionsSpan.className = "diff-file-additions";
|
||||
additionsSpan.textContent = `+${file.additions}`;
|
||||
statsDiv.appendChild(additionsSpan);
|
||||
}
|
||||
|
||||
// Add deletions count if present
|
||||
if (file.deletions > 0) {
|
||||
const deletionsSpan = document.createElement("span");
|
||||
deletionsSpan.className = "diff-file-deletions";
|
||||
deletionsSpan.textContent = `-${file.deletions}`;
|
||||
statsDiv.appendChild(deletionsSpan);
|
||||
}
|
||||
|
||||
item.appendChild(pathDiv);
|
||||
item.appendChild(statsDiv);
|
||||
|
||||
item.addEventListener("click", () => {
|
||||
selectedFileIndex = index;
|
||||
|
||||
// Auto-collapse sidebar on mobile when selecting a file
|
||||
const isMobile = window.innerWidth < 768;
|
||||
if (isMobile) {
|
||||
const fileListEl = document.getElementById("diff-file-list");
|
||||
if (fileListEl) {
|
||||
diffSidebarCollapsed = true;
|
||||
fileListEl.classList.add("collapsed");
|
||||
}
|
||||
}
|
||||
|
||||
renderDiffFileList();
|
||||
renderSelectedDiff();
|
||||
});
|
||||
|
||||
fileList.appendChild(item);
|
||||
});
|
||||
|
||||
// Re-attach toggle button event listener
|
||||
document.getElementById("toggle-diff-sidebar")?.replaceWith(document.getElementById("toggle-diff-sidebar")!.cloneNode(true));
|
||||
document.getElementById("toggle-diff-sidebar")?.addEventListener("click", () => {
|
||||
const fileListEl = document.getElementById("diff-file-list");
|
||||
if (!fileListEl) return;
|
||||
|
||||
diffSidebarCollapsed = !diffSidebarCollapsed;
|
||||
if (diffSidebarCollapsed) {
|
||||
fileListEl.classList.add("collapsed");
|
||||
} else {
|
||||
fileListEl.classList.remove("collapsed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 {
|
||||
// Check if file is binary
|
||||
if (file.binary) {
|
||||
diffDisplay.innerHTML = '<div class="diff-empty">Binary file changed</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
currentDiffSessionId = '';
|
||||
diffSidebarCollapsed = false;
|
||||
});
|
||||
|
||||
// Merge to parent button
|
||||
document.getElementById("merge-to-parent-btn")?.addEventListener("click", () => {
|
||||
if (!currentDiffSessionId) return;
|
||||
|
||||
const mergeBtn = document.getElementById("merge-to-parent-btn") as HTMLButtonElement;
|
||||
if (!mergeBtn || mergeBtn.disabled) return;
|
||||
|
||||
if (!confirm("Merge this worktree branch into the parent branch? This cannot be undone.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
mergeBtn.disabled = true;
|
||||
mergeBtn.textContent = "Merging...";
|
||||
|
||||
ipcAdapter.send("merge-to-parent", currentDiffSessionId);
|
||||
});
|
||||
|
||||
// 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>`;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle merge status from server
|
||||
ipcAdapter.on("merge-status-data", (_event, sessionId: string, status: MergeStatus) => {
|
||||
const mergeBtn = document.getElementById("merge-to-parent-btn") as HTMLButtonElement;
|
||||
if (!mergeBtn) return;
|
||||
|
||||
if (status.hasCommits) {
|
||||
mergeBtn.disabled = false;
|
||||
const commitText = status.commitsAhead === 1 ? '1 commit' : `${status.commitsAhead} commits`;
|
||||
mergeBtn.title = `Merge ${commitText} into parent branch`;
|
||||
if (status.hasUncommittedChanges) {
|
||||
mergeBtn.title += ' (has uncommitted changes)';
|
||||
}
|
||||
} else {
|
||||
mergeBtn.disabled = true;
|
||||
mergeBtn.title = "No commits to merge";
|
||||
}
|
||||
});
|
||||
|
||||
// Handle merge status error
|
||||
ipcAdapter.on("merge-status-error", (_event, sessionId: string, error: string) => {
|
||||
console.error("Merge status error:", error);
|
||||
const mergeBtn = document.getElementById("merge-to-parent-btn") as HTMLButtonElement;
|
||||
if (mergeBtn) {
|
||||
mergeBtn.disabled = true;
|
||||
mergeBtn.title = `Error checking merge status: ${error}`;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle merge result
|
||||
ipcAdapter.on("merge-result", (_event, sessionId: string, result: MergeResult) => {
|
||||
const mergeBtn = document.getElementById("merge-to-parent-btn") as HTMLButtonElement;
|
||||
|
||||
if (result.success) {
|
||||
alert(`✓ ${result.message}`);
|
||||
if (mergeBtn) {
|
||||
mergeBtn.disabled = true;
|
||||
mergeBtn.textContent = "Merged";
|
||||
}
|
||||
// Refresh diff and status
|
||||
ipcAdapter.send("get-session-diff", sessionId);
|
||||
ipcAdapter.send("check-merge-status", sessionId);
|
||||
} else {
|
||||
alert(`✗ Merge failed: ${result.message}`);
|
||||
if (mergeBtn) {
|
||||
mergeBtn.disabled = false;
|
||||
mergeBtn.textContent = "Merge to Parent";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
238
server.ts
238
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, MergeStatus, MergeResult } from "./types";
|
||||
import { isTerminalReady } from "./terminal-utils";
|
||||
import cors from "cors";
|
||||
|
||||
|
|
@ -234,6 +234,74 @@ async function removeGitBranch(projectDir: string, branchName: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function parsePatchToFileDiffs(patch: string): FileDiff[] {
|
||||
const fileDiffs: FileDiff[] = [];
|
||||
|
||||
// Split patch into individual file chunks
|
||||
// Each chunk starts with "diff --git"
|
||||
const fileChunks = patch.split(/(?=diff --git )/g).filter(chunk => chunk.trim());
|
||||
|
||||
for (const chunk of fileChunks) {
|
||||
const lines = chunk.split('\n');
|
||||
|
||||
// Extract file paths from "diff --git a/... b/..."
|
||||
const diffGitMatch = lines[0].match(/^diff --git a\/(.+?) b\/(.+?)$/);
|
||||
if (!diffGitMatch) continue;
|
||||
|
||||
let oldPath = diffGitMatch[1];
|
||||
let newPath = diffGitMatch[2];
|
||||
|
||||
// Determine status and paths
|
||||
let status: FileDiff['status'] = 'modified';
|
||||
let finalPath = newPath;
|
||||
let finalOldPath: string | undefined;
|
||||
let isBinary = false;
|
||||
|
||||
// Check for file mode changes and status indicators
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('new file mode')) {
|
||||
status = 'added';
|
||||
finalOldPath = undefined;
|
||||
} else if (line.startsWith('deleted file mode')) {
|
||||
status = 'deleted';
|
||||
finalPath = oldPath;
|
||||
finalOldPath = undefined;
|
||||
} else if (line.startsWith('rename from')) {
|
||||
status = 'renamed';
|
||||
finalOldPath = line.substring('rename from '.length).trim();
|
||||
} else if (line.startsWith('rename to')) {
|
||||
finalPath = line.substring('rename to '.length).trim();
|
||||
} else if (line.startsWith('Binary files')) {
|
||||
isBinary = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Count additions and deletions
|
||||
let additions = 0;
|
||||
let deletions = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('+') && !line.startsWith('+++')) {
|
||||
additions++;
|
||||
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
||||
deletions++;
|
||||
}
|
||||
}
|
||||
|
||||
fileDiffs.push({
|
||||
path: finalPath,
|
||||
oldPath: finalOldPath,
|
||||
status,
|
||||
diff: chunk,
|
||||
additions,
|
||||
deletions,
|
||||
binary: isBinary,
|
||||
});
|
||||
}
|
||||
|
||||
return fileDiffs;
|
||||
}
|
||||
|
||||
// Socket.IO handlers
|
||||
io.on("connection", (socket) => {
|
||||
console.log("Client connected:", socket.id);
|
||||
|
|
@ -495,6 +563,174 @@ 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;
|
||||
|
||||
console.log(`[${sessionId}] Generating unified diff for session, parent branch: ${parentBranch}`);
|
||||
console.log(`[${sessionId}] Worktree path: ${session.worktreePath}`);
|
||||
|
||||
// Get unified patch for tracked changes (includes staged, unstaged, and committed)
|
||||
const trackedPatch = await git.raw([
|
||||
'diff',
|
||||
parentBranch,
|
||||
'--patch',
|
||||
'--find-renames',
|
||||
'--binary'
|
||||
]);
|
||||
|
||||
// Get untracked files
|
||||
const statusSummary = await git.status();
|
||||
|
||||
// Generate patches for untracked files using --no-index
|
||||
const untrackedPatches: string[] = [];
|
||||
for (const untrackedFile of statusSummary.not_added) {
|
||||
try {
|
||||
const untrackedPatch = await git.raw([
|
||||
'diff',
|
||||
'--no-index',
|
||||
'--binary',
|
||||
'/dev/null',
|
||||
untrackedFile
|
||||
]);
|
||||
untrackedPatches.push(untrackedPatch);
|
||||
} catch (error: any) {
|
||||
// git diff --no-index exits with code 1 when there are differences, which is expected
|
||||
if (error.message && typeof error.message === 'string') {
|
||||
untrackedPatches.push(error.message);
|
||||
} else {
|
||||
console.error(`[${sessionId}] Error generating patch for untracked file ${untrackedFile}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combine all patches
|
||||
const combinedPatch = [trackedPatch, ...untrackedPatches].filter(p => p).join('\n');
|
||||
|
||||
console.log(`[${sessionId}] Combined patch length: ${combinedPatch.length} chars`);
|
||||
console.log(`[${sessionId}] Untracked files: ${statusSummary.not_added.length}`);
|
||||
|
||||
// Parse the combined patch into FileDiff array
|
||||
const fileDiffs: FileDiff[] = parsePatchToFileDiffs(combinedPatch);
|
||||
|
||||
console.log(`[${sessionId}] Parsed ${fileDiffs.length} file diffs`);
|
||||
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("check-merge-status", async (sessionId: string) => {
|
||||
try {
|
||||
const sessions = getPersistedSessions();
|
||||
const session = sessions.find(s => s.id === sessionId);
|
||||
|
||||
if (!session) {
|
||||
socket.emit("merge-status-error", sessionId, "Session not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const git = simpleGit(session.worktreePath);
|
||||
const parentBranch = session.config.parentBranch;
|
||||
const currentBranch = session.gitBranch || (await git.revparse(['--abbrev-ref', 'HEAD']));
|
||||
|
||||
// Check how many commits ahead of parent
|
||||
const revListOutput = await git.raw([
|
||||
'rev-list',
|
||||
'--count',
|
||||
`${parentBranch}..${currentBranch}`
|
||||
]);
|
||||
const commitsAhead = parseInt(revListOutput.trim(), 10);
|
||||
|
||||
// Check for uncommitted changes
|
||||
const statusSummary = await git.status();
|
||||
const hasUncommittedChanges = !statusSummary.isClean();
|
||||
|
||||
const mergeStatus: MergeStatus = {
|
||||
hasCommits: commitsAhead > 0,
|
||||
commitsAhead,
|
||||
hasUncommittedChanges
|
||||
};
|
||||
|
||||
console.log(`[${sessionId}] Merge status: ${JSON.stringify(mergeStatus)}`);
|
||||
socket.emit("merge-status-data", sessionId, mergeStatus);
|
||||
} catch (error) {
|
||||
console.error("Error checking merge status:", error);
|
||||
socket.emit("merge-status-error", sessionId, error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("merge-to-parent", async (sessionId: string) => {
|
||||
try {
|
||||
const sessions = getPersistedSessions();
|
||||
const session = sessions.find(s => s.id === sessionId);
|
||||
|
||||
if (!session) {
|
||||
socket.emit("merge-result", sessionId, {
|
||||
success: false,
|
||||
message: "Session not found"
|
||||
} as MergeResult);
|
||||
return;
|
||||
}
|
||||
|
||||
const projectDir = session.config.projectDir;
|
||||
const parentBranch = session.config.parentBranch;
|
||||
const worktreeBranch = session.gitBranch || (await simpleGit(session.worktreePath).revparse(['--abbrev-ref', 'HEAD']));
|
||||
|
||||
console.log(`[${sessionId}] Starting merge: ${worktreeBranch} -> ${parentBranch} in ${projectDir}`);
|
||||
|
||||
const git = simpleGit(projectDir);
|
||||
|
||||
// Checkout parent branch
|
||||
await git.checkout(parentBranch);
|
||||
console.log(`[${sessionId}] Checked out ${parentBranch}`);
|
||||
|
||||
// Attempt merge
|
||||
try {
|
||||
await git.merge([worktreeBranch]);
|
||||
console.log(`[${sessionId}] Merge successful`);
|
||||
|
||||
socket.emit("merge-result", sessionId, {
|
||||
success: true,
|
||||
message: `Successfully merged ${worktreeBranch} into ${parentBranch}`
|
||||
} as MergeResult);
|
||||
} catch (mergeError: any) {
|
||||
// Check if it's a merge conflict
|
||||
const status = await git.status();
|
||||
if (status.conflicted.length > 0) {
|
||||
console.log(`[${sessionId}] Merge conflicts detected: ${status.conflicted.join(', ')}`);
|
||||
|
||||
// Abort the merge
|
||||
await git.raw(['merge', '--abort']);
|
||||
|
||||
socket.emit("merge-result", sessionId, {
|
||||
success: false,
|
||||
conflicts: status.conflicted,
|
||||
message: `Merge conflicts in: ${status.conflicted.join(', ')}`
|
||||
} as MergeResult);
|
||||
} else {
|
||||
throw mergeError;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error merging to parent:", error);
|
||||
socket.emit("merge-result", sessionId, {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : String(error)
|
||||
} as MergeResult);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
// Clean up socket references for this client
|
||||
// Note: We don't kill the PTYs - they keep running for reconnection
|
||||
|
|
|
|||
167
styles.css
167
styles.css
|
|
@ -336,4 +336,171 @@
|
|||
@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-actions {
|
||||
@apply flex items-center gap-3;
|
||||
}
|
||||
|
||||
.btn-merge {
|
||||
@apply px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded font-medium transition-colors;
|
||||
}
|
||||
|
||||
.btn-merge:disabled {
|
||||
@apply bg-gray-700 text-gray-500 cursor-not-allowed;
|
||||
}
|
||||
|
||||
.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 transition-all duration-300 relative;
|
||||
}
|
||||
|
||||
.diff-file-list.collapsed {
|
||||
@apply w-12 overflow-hidden;
|
||||
}
|
||||
|
||||
.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-sidebar-toggle {
|
||||
@apply sticky top-0 left-0 w-full bg-gray-700 hover:bg-gray-600 text-gray-300 py-2 px-3 text-sm font-mono transition-colors z-10 border-b border-gray-600 flex items-center justify-center;
|
||||
}
|
||||
|
||||
.diff-file-list.collapsed .diff-sidebar-toggle {
|
||||
@apply px-0;
|
||||
}
|
||||
|
||||
.toggle-collapse {
|
||||
@apply inline-block;
|
||||
}
|
||||
|
||||
.diff-file-list.collapsed .toggle-collapse {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.toggle-expand {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.diff-file-list.collapsed .toggle-expand {
|
||||
@apply inline-block;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.diff-sidebar-toggle {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.diff-file-list.collapsed {
|
||||
@apply w-80;
|
||||
}
|
||||
}
|
||||
|
||||
.diff-display {
|
||||
@apply flex-1 overflow-auto bg-gray-900 p-4 relative;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
types.ts
22
types.ts
|
|
@ -18,3 +18,25 @@ export interface PersistedSession {
|
|||
mcpConfigPath?: string;
|
||||
gitBranch?: string;
|
||||
}
|
||||
|
||||
export interface FileDiff {
|
||||
path: string;
|
||||
oldPath?: string;
|
||||
status: 'added' | 'modified' | 'deleted' | 'renamed';
|
||||
diff: string;
|
||||
additions: number;
|
||||
deletions: number;
|
||||
binary?: boolean;
|
||||
}
|
||||
|
||||
export interface MergeStatus {
|
||||
hasCommits: boolean;
|
||||
commitsAhead: number;
|
||||
hasUncommittedChanges: boolean;
|
||||
}
|
||||
|
||||
export interface MergeResult {
|
||||
success: boolean;
|
||||
conflicts?: string[];
|
||||
message: string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue