mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat: 添加最近路径管理功能,包括删除路径的API和前端交互
This commit is contained in:
@@ -6,7 +6,7 @@ import { homedir } from 'os';
|
||||
import { createHash } from 'crypto';
|
||||
import { scanSessions } from './session-scanner.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
|
||||
const CLAUDE_CONFIG_PATH = join(homedir(), '.claude.json');
|
||||
@@ -123,6 +123,19 @@ export async function startServer(options = {}) {
|
||||
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)
|
||||
if (pathname === '/api/session-detail') {
|
||||
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') {
|
||||
result.explorations = { manifest: null, data: {} };
|
||||
|
||||
// Look for explorations-manifest.json
|
||||
const manifestFile = join(normalizedPath, 'explorations-manifest.json');
|
||||
if (existsSync(manifestFile)) {
|
||||
try {
|
||||
result.explorations.manifest = JSON.parse(readFileSync(manifestFile, 'utf8'));
|
||||
// Try .process/ first (standard workflow sessions), then session root (lite tasks)
|
||||
const searchDirs = [
|
||||
join(normalizedPath, '.process'),
|
||||
normalizedPath
|
||||
];
|
||||
|
||||
// Load each exploration file based on manifest
|
||||
const explorations = result.explorations.manifest.explorations || [];
|
||||
for (const exp of explorations) {
|
||||
const expFile = join(normalizedPath, exp.file);
|
||||
if (existsSync(expFile)) {
|
||||
try {
|
||||
result.explorations.data[exp.angle] = JSON.parse(readFileSync(expFile, 'utf8'));
|
||||
} catch (e) {
|
||||
// Skip unreadable exploration files
|
||||
for (const searchDir of searchDirs) {
|
||||
if (!existsSync(searchDir)) continue;
|
||||
|
||||
// Look for explorations-manifest.json
|
||||
const manifestFile = join(searchDir, 'explorations-manifest.json');
|
||||
if (existsSync(manifestFile)) {
|
||||
try {
|
||||
result.explorations.manifest = JSON.parse(readFileSync(manifestFile, 'utf8'));
|
||||
|
||||
// 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
|
||||
for (const file of files) {
|
||||
const angle = file.replace('exploration-', '').replace('.json', '');
|
||||
try {
|
||||
result.explorations.data[angle] = JSON.parse(readFileSync(join(normalizedPath, file), 'utf8'));
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
// Load conflict resolution decisions (conflict-resolution-decisions.json)
|
||||
if (dataType === 'context' || dataType === 'conflict' || dataType === 'all') {
|
||||
result.conflictResolution = null;
|
||||
|
||||
// Try .process/ first (standard workflow sessions)
|
||||
const conflictFiles = [
|
||||
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 => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'path-item' + (path === projectPath ? ' active' : '');
|
||||
item.textContent = 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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ==========
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,9 +12,28 @@ function initPathSelector() {
|
||||
recentPaths.forEach(path => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'path-item' + (path === projectPath ? ' active' : '');
|
||||
item.textContent = 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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -961,3 +961,137 @@ function renderConflictDetectionSection(conflictDetection) {
|
||||
|
||||
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>';
|
||||
|
||||
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) {
|
||||
const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=context`);
|
||||
if (response.ok) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,6 +437,44 @@ body {
|
||||
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
|
||||
=================================== */
|
||||
@@ -3807,6 +3845,207 @@ ol.step-commands code {
|
||||
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 {
|
||||
margin-top: 16px;
|
||||
|
||||
@@ -252,3 +252,28 @@ export function clearRecentPaths() {
|
||||
// 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