update
This commit is contained in:
parent
35983681e4
commit
f75c054e65
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -201,12 +201,21 @@
|
|||
<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>
|
||||
|
||||
|
|
|
|||
124
renderer.ts
124
renderer.ts
|
|
@ -1,6 +1,6 @@
|
|||
import {FitAddon} from "@xterm/addon-fit";
|
||||
import {Terminal} from "xterm";
|
||||
import {PersistedSession, SessionConfig, FileDiff} from "./types";
|
||||
import {PersistedSession, SessionConfig, FileDiff, MergeStatus, MergeResult} from "./types";
|
||||
import {isClaudeSessionReady} from "./terminal-utils";
|
||||
import {ipcAdapter} from "./ipc-adapter";
|
||||
|
||||
|
|
@ -1522,6 +1522,8 @@ document.getElementById("session-list")?.addEventListener("click", (e) => {
|
|||
// 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;
|
||||
|
|
@ -1530,14 +1532,31 @@ 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 {
|
||||
|
|
@ -1560,8 +1579,9 @@ async function openDiffViewer(sessionId: string) {
|
|||
}
|
||||
}
|
||||
|
||||
// Request diff from server
|
||||
// Request diff and merge status from server
|
||||
ipcAdapter.send("get-session-diff", sessionId);
|
||||
ipcAdapter.send("check-merge-status", sessionId);
|
||||
}
|
||||
|
||||
function renderDiffFileList() {
|
||||
|
|
@ -1569,11 +1589,11 @@ function renderDiffFileList() {
|
|||
if (!fileList) return;
|
||||
|
||||
if (currentDiffFiles.length === 0) {
|
||||
fileList.innerHTML = '<div class="diff-loading">No changes found</div>';
|
||||
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 = '';
|
||||
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");
|
||||
|
|
@ -1615,12 +1635,37 @@ function renderDiffFileList() {
|
|||
|
||||
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() {
|
||||
|
|
@ -1681,12 +1726,33 @@ async function renderSelectedDiff() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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
|
||||
|
|
@ -1709,3 +1775,53 @@ ipcAdapter.on("session-diff-error", (_event, sessionId: string, error: string) =
|
|||
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";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
103
server.ts
103
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, FileDiff } from "./types";
|
||||
import { PersistedSession, SessionConfig, FileDiff, MergeStatus, MergeResult } from "./types";
|
||||
import { isTerminalReady } from "./terminal-utils";
|
||||
import cors from "cors";
|
||||
|
||||
|
|
@ -630,6 +630,107 @@ io.on("connection", (socket) => {
|
|||
}
|
||||
});
|
||||
|
||||
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
|
||||
|
|
|
|||
54
styles.css
54
styles.css
|
|
@ -349,6 +349,18 @@
|
|||
@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;
|
||||
}
|
||||
|
|
@ -358,7 +370,11 @@
|
|||
}
|
||||
|
||||
.diff-file-list {
|
||||
@apply w-80 border-r border-gray-700 overflow-y-auto bg-gray-800;
|
||||
@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 {
|
||||
|
|
@ -409,8 +425,42 @@
|
|||
@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;
|
||||
@apply flex-1 overflow-auto bg-gray-900 p-4 relative;
|
||||
}
|
||||
|
||||
.diff-empty {
|
||||
|
|
|
|||
12
types.ts
12
types.ts
|
|
@ -28,3 +28,15 @@ export interface FileDiff {
|
|||
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