mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
feat: 添加最近路径管理功能,包括删除路径的API和前端交互
This commit is contained in:
@@ -6,7 +6,7 @@ import { homedir } from 'os';
|
|||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { scanSessions } from './session-scanner.js';
|
import { scanSessions } from './session-scanner.js';
|
||||||
import { aggregateData } from './data-aggregator.js';
|
import { aggregateData } from './data-aggregator.js';
|
||||||
import { resolvePath, getRecentPaths, trackRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
|
import { resolvePath, getRecentPaths, trackRecentPath, removeRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
|
||||||
|
|
||||||
// Claude config file path
|
// Claude config file path
|
||||||
const CLAUDE_CONFIG_PATH = join(homedir(), '.claude.json');
|
const CLAUDE_CONFIG_PATH = join(homedir(), '.claude.json');
|
||||||
@@ -123,6 +123,19 @@ export async function startServer(options = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API: Remove a recent path
|
||||||
|
if (pathname === '/api/remove-recent-path' && req.method === 'POST') {
|
||||||
|
handlePostRequest(req, res, async (body) => {
|
||||||
|
const { path } = body;
|
||||||
|
if (!path) {
|
||||||
|
return { error: 'path is required', status: 400 };
|
||||||
|
}
|
||||||
|
const removed = removeRecentPath(path);
|
||||||
|
return { success: removed, paths: getRecentPaths() };
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// API: Get session detail data (context, summaries, impl-plan, review)
|
// API: Get session detail data (context, summaries, impl-plan, review)
|
||||||
if (pathname === '/api/session-detail') {
|
if (pathname === '/api/session-detail') {
|
||||||
const sessionPath = url.searchParams.get('path');
|
const sessionPath = url.searchParams.get('path');
|
||||||
@@ -610,58 +623,92 @@ async function getSessionDetailData(sessionPath, dataType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load explorations for lite tasks (exploration-*.json files)
|
// Load explorations (exploration-*.json files) - check .process/ first, then session root
|
||||||
if (dataType === 'context' || dataType === 'explorations' || dataType === 'all') {
|
if (dataType === 'context' || dataType === 'explorations' || dataType === 'all') {
|
||||||
result.explorations = { manifest: null, data: {} };
|
result.explorations = { manifest: null, data: {} };
|
||||||
|
|
||||||
// Look for explorations-manifest.json
|
// Try .process/ first (standard workflow sessions), then session root (lite tasks)
|
||||||
const manifestFile = join(normalizedPath, 'explorations-manifest.json');
|
const searchDirs = [
|
||||||
if (existsSync(manifestFile)) {
|
join(normalizedPath, '.process'),
|
||||||
try {
|
normalizedPath
|
||||||
result.explorations.manifest = JSON.parse(readFileSync(manifestFile, 'utf8'));
|
];
|
||||||
|
|
||||||
// Load each exploration file based on manifest
|
for (const searchDir of searchDirs) {
|
||||||
const explorations = result.explorations.manifest.explorations || [];
|
if (!existsSync(searchDir)) continue;
|
||||||
for (const exp of explorations) {
|
|
||||||
const expFile = join(normalizedPath, exp.file);
|
// Look for explorations-manifest.json
|
||||||
if (existsSync(expFile)) {
|
const manifestFile = join(searchDir, 'explorations-manifest.json');
|
||||||
try {
|
if (existsSync(manifestFile)) {
|
||||||
result.explorations.data[exp.angle] = JSON.parse(readFileSync(expFile, 'utf8'));
|
try {
|
||||||
} catch (e) {
|
result.explorations.manifest = JSON.parse(readFileSync(manifestFile, 'utf8'));
|
||||||
// Skip unreadable exploration files
|
|
||||||
|
// Load each exploration file based on manifest
|
||||||
|
const explorations = result.explorations.manifest.explorations || [];
|
||||||
|
for (const exp of explorations) {
|
||||||
|
const expFile = join(searchDir, exp.file);
|
||||||
|
if (existsSync(expFile)) {
|
||||||
|
try {
|
||||||
|
result.explorations.data[exp.angle] = JSON.parse(readFileSync(expFile, 'utf8'));
|
||||||
|
} catch (e) {
|
||||||
|
// Skip unreadable exploration files
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break; // Found manifest, stop searching
|
||||||
|
} catch (e) {
|
||||||
|
result.explorations.manifest = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: scan for exploration-*.json files directly
|
||||||
|
try {
|
||||||
|
const files = readdirSync(searchDir).filter(f => f.startsWith('exploration-') && f.endsWith('.json'));
|
||||||
|
if (files.length > 0) {
|
||||||
|
// Create synthetic manifest
|
||||||
|
result.explorations.manifest = {
|
||||||
|
exploration_count: files.length,
|
||||||
|
explorations: files.map((f, i) => ({
|
||||||
|
angle: f.replace('exploration-', '').replace('.json', ''),
|
||||||
|
file: f,
|
||||||
|
index: i + 1
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load each file
|
||||||
|
for (const file of files) {
|
||||||
|
const angle = file.replace('exploration-', '').replace('.json', '');
|
||||||
|
try {
|
||||||
|
result.explorations.data[angle] = JSON.parse(readFileSync(join(searchDir, file), 'utf8'));
|
||||||
|
} catch (e) {
|
||||||
|
// Skip unreadable files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break; // Found explorations, stop searching
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Directory read failed
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
result.explorations.manifest = null;
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// Fallback: scan for exploration-*.json files directly
|
}
|
||||||
try {
|
|
||||||
const files = readdirSync(normalizedPath).filter(f => f.startsWith('exploration-') && f.endsWith('.json'));
|
|
||||||
if (files.length > 0) {
|
|
||||||
// Create synthetic manifest
|
|
||||||
result.explorations.manifest = {
|
|
||||||
exploration_count: files.length,
|
|
||||||
explorations: files.map((f, i) => ({
|
|
||||||
angle: f.replace('exploration-', '').replace('.json', ''),
|
|
||||||
file: f,
|
|
||||||
index: i + 1
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load each file
|
// Load conflict resolution decisions (conflict-resolution-decisions.json)
|
||||||
for (const file of files) {
|
if (dataType === 'context' || dataType === 'conflict' || dataType === 'all') {
|
||||||
const angle = file.replace('exploration-', '').replace('.json', '');
|
result.conflictResolution = null;
|
||||||
try {
|
|
||||||
result.explorations.data[angle] = JSON.parse(readFileSync(join(normalizedPath, file), 'utf8'));
|
// Try .process/ first (standard workflow sessions)
|
||||||
} catch (e) {
|
const conflictFiles = [
|
||||||
// Skip unreadable files
|
join(normalizedPath, '.process', 'conflict-resolution-decisions.json'),
|
||||||
}
|
join(normalizedPath, 'conflict-resolution-decisions.json')
|
||||||
}
|
];
|
||||||
|
|
||||||
|
for (const conflictFile of conflictFiles) {
|
||||||
|
if (existsSync(conflictFile)) {
|
||||||
|
try {
|
||||||
|
result.conflictResolution = JSON.parse(readFileSync(conflictFile, 'utf8'));
|
||||||
|
break; // Found file, stop searching
|
||||||
|
} catch (e) {
|
||||||
|
// Skip unreadable file
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
// Directory read failed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,13 +118,57 @@ function refreshRecentPaths() {
|
|||||||
recentPaths.forEach(path => {
|
recentPaths.forEach(path => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'path-item' + (path === projectPath ? ' active' : '');
|
item.className = 'path-item' + (path === projectPath ? ' active' : '');
|
||||||
item.textContent = path;
|
|
||||||
item.dataset.path = path;
|
item.dataset.path = path;
|
||||||
item.addEventListener('click', () => selectPath(path));
|
|
||||||
|
// Path text
|
||||||
|
const pathText = document.createElement('span');
|
||||||
|
pathText.className = 'path-text';
|
||||||
|
pathText.textContent = path;
|
||||||
|
pathText.addEventListener('click', () => selectPath(path));
|
||||||
|
item.appendChild(pathText);
|
||||||
|
|
||||||
|
// Delete button (only for non-current paths)
|
||||||
|
if (path !== projectPath) {
|
||||||
|
const deleteBtn = document.createElement('button');
|
||||||
|
deleteBtn.className = 'path-delete-btn';
|
||||||
|
deleteBtn.innerHTML = '×';
|
||||||
|
deleteBtn.title = 'Remove from recent';
|
||||||
|
deleteBtn.addEventListener('click', async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
await removeRecentPathFromList(path);
|
||||||
|
});
|
||||||
|
item.appendChild(deleteBtn);
|
||||||
|
}
|
||||||
|
|
||||||
recentContainer.appendChild(item);
|
recentContainer.appendChild(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a path from recent paths list
|
||||||
|
*/
|
||||||
|
async function removeRecentPathFromList(path) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/remove-recent-path', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ path })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.success) {
|
||||||
|
recentPaths = data.paths;
|
||||||
|
refreshRecentPaths();
|
||||||
|
showRefreshToast('Path removed', 'success');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to remove path:', err);
|
||||||
|
showRefreshToast('Failed to remove path', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ========== File System Access ==========
|
// ========== File System Access ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,9 +12,28 @@ function initPathSelector() {
|
|||||||
recentPaths.forEach(path => {
|
recentPaths.forEach(path => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'path-item' + (path === projectPath ? ' active' : '');
|
item.className = 'path-item' + (path === projectPath ? ' active' : '');
|
||||||
item.textContent = path;
|
|
||||||
item.dataset.path = path;
|
item.dataset.path = path;
|
||||||
item.addEventListener('click', () => selectPath(path));
|
|
||||||
|
// Path text
|
||||||
|
const pathText = document.createElement('span');
|
||||||
|
pathText.className = 'path-text';
|
||||||
|
pathText.textContent = path;
|
||||||
|
pathText.addEventListener('click', () => selectPath(path));
|
||||||
|
item.appendChild(pathText);
|
||||||
|
|
||||||
|
// Delete button (only for non-current paths)
|
||||||
|
if (path !== projectPath) {
|
||||||
|
const deleteBtn = document.createElement('button');
|
||||||
|
deleteBtn.className = 'path-delete-btn';
|
||||||
|
deleteBtn.innerHTML = '×';
|
||||||
|
deleteBtn.title = 'Remove from recent';
|
||||||
|
deleteBtn.addEventListener('click', async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
await removeRecentPathFromList(path);
|
||||||
|
});
|
||||||
|
item.appendChild(deleteBtn);
|
||||||
|
}
|
||||||
|
|
||||||
recentContainer.appendChild(item);
|
recentContainer.appendChild(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -961,3 +961,137 @@ function renderConflictDetectionSection(conflictDetection) {
|
|||||||
|
|
||||||
return sections.join('');
|
return sections.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Session Context Tab Rendering (Standard Sessions)
|
||||||
|
// ==========================================
|
||||||
|
// Combines context-package, explorations, and conflict resolution
|
||||||
|
|
||||||
|
function renderSessionContextContent(context, explorations, conflictResolution) {
|
||||||
|
let sections = [];
|
||||||
|
|
||||||
|
// Render conflict resolution decisions if available
|
||||||
|
if (conflictResolution) {
|
||||||
|
sections.push(renderConflictResolutionContext(conflictResolution));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render explorations if available (from exploration-*.json files)
|
||||||
|
if (explorations && explorations.manifest) {
|
||||||
|
sections.push(renderExplorationContext(explorations));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render context-package.json content
|
||||||
|
if (context) {
|
||||||
|
const contextJson = JSON.stringify(context, null, 2);
|
||||||
|
window._currentContextJson = contextJson;
|
||||||
|
|
||||||
|
// Use existing renderContextContent for detailed rendering
|
||||||
|
sections.push(\`
|
||||||
|
<div class="session-context-section">
|
||||||
|
\${renderContextContent(context)}
|
||||||
|
</div>
|
||||||
|
\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have any sections, wrap them
|
||||||
|
if (sections.length > 0) {
|
||||||
|
return \`<div class="context-tab-content session-context-combined">\${sections.join('')}</div>\`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return \`
|
||||||
|
<div class="tab-empty-state">
|
||||||
|
<div class="empty-icon">📦</div>
|
||||||
|
<div class="empty-title">No Context Data</div>
|
||||||
|
<div class="empty-text">No context-package.json, exploration files, or conflict resolution data found for this session.</div>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Conflict Resolution Context Rendering
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
function renderConflictResolutionContext(conflictResolution) {
|
||||||
|
if (!conflictResolution) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
let sections = [];
|
||||||
|
|
||||||
|
// Header
|
||||||
|
sections.push(\`
|
||||||
|
<div class="conflict-resolution-header">
|
||||||
|
<h4>⚖️ Conflict Resolution Decisions</h4>
|
||||||
|
<div class="conflict-meta">
|
||||||
|
<span class="meta-item">Session: <strong>\${escapeHtml(conflictResolution.session_id || 'N/A')}</strong></span>
|
||||||
|
\${conflictResolution.resolved_at ? \`<span class="meta-item">Resolved: <strong>\${formatDate(conflictResolution.resolved_at)}</strong></span>\` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
\`);
|
||||||
|
|
||||||
|
// User decisions
|
||||||
|
if (conflictResolution.user_decisions && Object.keys(conflictResolution.user_decisions).length > 0) {
|
||||||
|
const decisions = Object.entries(conflictResolution.user_decisions);
|
||||||
|
|
||||||
|
sections.push(\`
|
||||||
|
<div class="conflict-decisions-section collapsible-section">
|
||||||
|
<div class="collapsible-header">
|
||||||
|
<span class="collapse-icon">▶</span>
|
||||||
|
<span class="section-label">🎯 User Decisions (\${decisions.length})</span>
|
||||||
|
</div>
|
||||||
|
<div class="collapsible-content collapsed">
|
||||||
|
<div class="decisions-list">
|
||||||
|
\${decisions.map(([key, decision]) => \`
|
||||||
|
<div class="decision-item">
|
||||||
|
<div class="decision-header">
|
||||||
|
<span class="decision-key">\${escapeHtml(key.replace(/_/g, ' '))}</span>
|
||||||
|
<span class="decision-choice">\${escapeHtml(decision.choice || 'N/A')}</span>
|
||||||
|
</div>
|
||||||
|
\${decision.description ? \`<p class="decision-description">\${escapeHtml(decision.description)}</p>\` : ''}
|
||||||
|
\${decision.implications && decision.implications.length > 0 ? \`
|
||||||
|
<div class="decision-implications">
|
||||||
|
<span class="implications-label">Implications:</span>
|
||||||
|
<ul class="implications-list">
|
||||||
|
\${decision.implications.map(impl => \`<li>\${escapeHtml(impl)}</li>\`).join('')}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
\` : ''}
|
||||||
|
</div>
|
||||||
|
\`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolved conflicts
|
||||||
|
if (conflictResolution.resolved_conflicts && conflictResolution.resolved_conflicts.length > 0) {
|
||||||
|
sections.push(\`
|
||||||
|
<div class="resolved-conflicts-section collapsible-section">
|
||||||
|
<div class="collapsible-header">
|
||||||
|
<span class="collapse-icon">▶</span>
|
||||||
|
<span class="section-label">✅ Resolved Conflicts (\${conflictResolution.resolved_conflicts.length})</span>
|
||||||
|
</div>
|
||||||
|
<div class="collapsible-content collapsed">
|
||||||
|
<div class="conflicts-list">
|
||||||
|
\${conflictResolution.resolved_conflicts.map(conflict => \`
|
||||||
|
<div class="resolved-conflict-item">
|
||||||
|
<div class="conflict-row">
|
||||||
|
<span class="conflict-id">\${escapeHtml(conflict.id || 'N/A')}</span>
|
||||||
|
<span class="conflict-category-badge">\${escapeHtml(conflict.category || 'General')}</span>
|
||||||
|
</div>
|
||||||
|
<div class="conflict-brief">\${escapeHtml(conflict.brief || '')}</div>
|
||||||
|
<div class="conflict-strategy">
|
||||||
|
<span class="strategy-label">Strategy:</span>
|
||||||
|
<span class="strategy-value">\${escapeHtml(conflict.strategy || 'N/A')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
\`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return \`<div class="conflict-resolution-context">\${sections.join('')}</div>\`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -408,12 +408,15 @@ async function loadAndRenderContextTab(session, contentArea) {
|
|||||||
contentArea.innerHTML = '<div class="tab-loading">Loading context data...</div>';
|
contentArea.innerHTML = '<div class="tab-loading">Loading context data...</div>';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try to load context-package.json from server
|
// Try to load context data from server (includes context, explorations, conflictResolution)
|
||||||
if (window.SERVER_MODE && session.path) {
|
if (window.SERVER_MODE && session.path) {
|
||||||
const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=context`);
|
const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=context`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
contentArea.innerHTML = renderContextContent(data.context);
|
contentArea.innerHTML = renderSessionContextContent(data.context, data.explorations, data.conflictResolution);
|
||||||
|
|
||||||
|
// Initialize collapsible sections for explorations
|
||||||
|
initCollapsibleSections(contentArea);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -437,6 +437,44 @@ body {
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.path-menu .path-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-menu .path-text {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-delete-btn {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s, background 0.15s, color 0.15s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-menu .path-item:hover .path-delete-btn {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-delete-btn:hover {
|
||||||
|
background: hsl(var(--destructive) / 0.1);
|
||||||
|
color: hsl(var(--destructive));
|
||||||
|
}
|
||||||
|
|
||||||
/* ===================================
|
/* ===================================
|
||||||
Session Detail Page
|
Session Detail Page
|
||||||
=================================== */
|
=================================== */
|
||||||
@@ -3807,6 +3845,207 @@ ol.step-commands code {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
Conflict Resolution Context Styles
|
||||||
|
========================================== */
|
||||||
|
.conflict-resolution-context {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conflict-resolution-header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 16px;
|
||||||
|
background: linear-gradient(135deg, var(--bg-warning, #fffbeb) 0%, var(--bg-secondary, #f9fafb) 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border-warning, #fcd34d);
|
||||||
|
}
|
||||||
|
|
||||||
|
.conflict-resolution-header h4 {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary, #111827);
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conflict-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary, #6b7280);
|
||||||
|
}
|
||||||
|
|
||||||
|
.conflict-meta .meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conflict-meta strong {
|
||||||
|
color: var(--text-primary, #111827);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decisions Section */
|
||||||
|
.conflict-decisions-section,
|
||||||
|
.resolved-conflicts-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border: 1px solid var(--border-color, #e5e7eb);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decisions-list,
|
||||||
|
.conflicts-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decision-item {
|
||||||
|
padding: 14px;
|
||||||
|
background: var(--bg-primary, #fff);
|
||||||
|
border: 1px solid var(--border-color, #e5e7eb);
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decision-item:hover {
|
||||||
|
border-color: var(--primary, #3b82f6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.decision-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decision-key {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary, #111827);
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decision-choice {
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: var(--primary-bg, #eff6ff);
|
||||||
|
color: var(--primary, #3b82f6);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decision-description {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary, #6b7280);
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decision-implications {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid var(--border-color, #e5e7eb);
|
||||||
|
}
|
||||||
|
|
||||||
|
.implications-label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted, #9ca3af);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.implications-list {
|
||||||
|
margin: 6px 0 0 0;
|
||||||
|
padding-left: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary, #6b7280);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.implications-list li {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolved Conflicts */
|
||||||
|
.resolved-conflict-item {
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--bg-secondary, #f9fafb);
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--border-color, #e5e7eb);
|
||||||
|
}
|
||||||
|
|
||||||
|
.conflict-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conflict-id {
|
||||||
|
font-family: var(--font-mono, monospace);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary, #111827);
|
||||||
|
padding: 2px 6px;
|
||||||
|
background: var(--bg-primary, #fff);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conflict-category-badge {
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: var(--primary-bg, #eff6ff);
|
||||||
|
color: var(--primary, #3b82f6);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conflict-brief {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-primary, #374151);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conflict-strategy {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-label {
|
||||||
|
color: var(--text-muted, #9ca3af);
|
||||||
|
}
|
||||||
|
|
||||||
|
.strategy-value {
|
||||||
|
color: var(--text-success, #065f46);
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: var(--bg-success, #d1fae5);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Session Context Combined */
|
||||||
|
.session-context-combined {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-context-section {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Plan Context Section in Lite Tasks */
|
/* Plan Context Section in Lite Tasks */
|
||||||
.plan-context-section {
|
.plan-context-section {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
|
|||||||
@@ -252,3 +252,28 @@ export function clearRecentPaths() {
|
|||||||
// Ignore errors
|
// Ignore errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a specific path from recent paths
|
||||||
|
* @param {string} pathToRemove - Path to remove
|
||||||
|
* @returns {boolean} - True if removed, false if not found
|
||||||
|
*/
|
||||||
|
export function removeRecentPath(pathToRemove) {
|
||||||
|
try {
|
||||||
|
const normalized = normalizePathForDisplay(resolvePath(pathToRemove));
|
||||||
|
let paths = getRecentPaths();
|
||||||
|
const originalLength = paths.length;
|
||||||
|
|
||||||
|
// Filter out the path to remove
|
||||||
|
paths = paths.filter(p => normalizePathForDisplay(p) !== normalized);
|
||||||
|
|
||||||
|
if (paths.length < originalLength) {
|
||||||
|
// Save updated list
|
||||||
|
writeFileSync(RECENT_PATHS_FILE, JSON.stringify({ paths }, null, 2), 'utf8');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user