mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-14 02:42:04 +08:00
Add exploration field rendering helpers for dynamic content display
- Implemented `renderExpField` to handle various data types for exploration fields. - Created `renderExpArray` to format arrays, including support for objects with specific properties. - Developed `renderExpObject` for recursive rendering of object values, filtering out private keys. - Introduced HTML escaping for safe rendering of user-generated content.
This commit is contained in:
@@ -58,8 +58,9 @@ const MODULE_FILES = [
|
|||||||
'components/notifications.js',
|
'components/notifications.js',
|
||||||
'components/mcp-manager.js',
|
'components/mcp-manager.js',
|
||||||
'components/hook-manager.js',
|
'components/hook-manager.js',
|
||||||
'components/tabs-context.js',
|
'components/_exp_helpers.js',
|
||||||
'components/tabs-other.js',
|
'components/tabs-other.js',
|
||||||
|
'components/tabs-context.js',
|
||||||
'components/task-drawer-core.js',
|
'components/task-drawer-core.js',
|
||||||
'components/task-drawer-renderers.js',
|
'components/task-drawer-renderers.js',
|
||||||
'components/flowchart.js',
|
'components/flowchart.js',
|
||||||
|
|||||||
52
ccw/src/templates/dashboard-js/components/_exp_helpers.js
Normal file
52
ccw/src/templates/dashboard-js/components/_exp_helpers.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Helper: Render exploration field with smart type detection
|
||||||
|
function renderExpField(label, value) {
|
||||||
|
if (value === null || value === undefined) return '';
|
||||||
|
let rendered;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
rendered = `<p>${escapeHtml(value)}</p>`;
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
rendered = renderExpArray(value);
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
rendered = renderExpObject(value);
|
||||||
|
} else {
|
||||||
|
rendered = `<p>${escapeHtml(String(value))}</p>`;
|
||||||
|
}
|
||||||
|
return `<div class="exp-field"><label>${escapeHtml(label)}</label>${rendered}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Render array values
|
||||||
|
function renderExpArray(arr) {
|
||||||
|
if (!arr.length) return '<p>-</p>';
|
||||||
|
if (typeof arr[0] === 'object' && arr[0] !== null) {
|
||||||
|
return `<div class="exp-array-objects">${arr.map(item => {
|
||||||
|
if (item.question) {
|
||||||
|
return `<div class="clarification-item">
|
||||||
|
<div class="clarification-question">${escapeHtml(item.question)}</div>
|
||||||
|
${item.impact ? `<div class="clarification-impact">Impact: ${escapeHtml(item.impact)}</div>` : ''}
|
||||||
|
${item.priority ? `<span class="priority-badge priority-${item.priority}">${item.priority}</span>` : ''}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
return `<div class="exp-object-item">${renderExpObject(item)}</div>`;
|
||||||
|
}).join('')}</div>`;
|
||||||
|
}
|
||||||
|
return `<ul class="exp-list">${arr.map(item => `<li>${escapeHtml(String(item))}</li>`).join('')}</ul>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Render object values recursively
|
||||||
|
function renderExpObject(obj) {
|
||||||
|
if (!obj || typeof obj !== 'object') return '';
|
||||||
|
const entries = Object.entries(obj).filter(([k]) => !k.startsWith('_'));
|
||||||
|
if (!entries.length) return '<p>-</p>';
|
||||||
|
return `<div class="exp-object">${entries.map(([key, val]) => {
|
||||||
|
const label = key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
||||||
|
if (val === null || val === undefined) return '';
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
return `<div class="exp-obj-field"><span class="exp-obj-key">${escapeHtml(label)}:</span> <span class="exp-obj-val">${escapeHtml(val)}</span></div>`;
|
||||||
|
} else if (Array.isArray(val)) {
|
||||||
|
return `<div class="exp-obj-field"><span class="exp-obj-key">${escapeHtml(label)}:</span>${renderExpArray(val)}</div>`;
|
||||||
|
} else if (typeof val === 'object') {
|
||||||
|
return `<div class="exp-obj-nested"><span class="exp-obj-key">${escapeHtml(label)}</span>${renderExpObject(val)}</div>`;
|
||||||
|
}
|
||||||
|
return `<div class="exp-obj-field"><span class="exp-obj-key">${escapeHtml(label)}:</span> <span class="exp-obj-val">${escapeHtml(String(val))}</span></div>`;
|
||||||
|
}).join('')}</div>`;
|
||||||
|
}
|
||||||
@@ -986,25 +986,25 @@ function renderSessionContextContent(context, explorations, conflictResolution)
|
|||||||
window._currentContextJson = contextJson;
|
window._currentContextJson = contextJson;
|
||||||
|
|
||||||
// Use existing renderContextContent for detailed rendering
|
// Use existing renderContextContent for detailed rendering
|
||||||
sections.push(\`
|
sections.push(`
|
||||||
<div class="session-context-section">
|
<div class="session-context-section">
|
||||||
\${renderContextContent(context)}
|
${renderContextContent(context)}
|
||||||
</div>
|
</div>
|
||||||
\`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have any sections, wrap them
|
// If we have any sections, wrap them
|
||||||
if (sections.length > 0) {
|
if (sections.length > 0) {
|
||||||
return \`<div class="context-tab-content session-context-combined">\${sections.join('')}</div>\`;
|
return `<div class="context-tab-content session-context-combined">${sections.join('')}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return \`
|
return `
|
||||||
<div class="tab-empty-state">
|
<div class="tab-empty-state">
|
||||||
<div class="empty-icon">📦</div>
|
<div class="empty-icon">📦</div>
|
||||||
<div class="empty-title">No Context Data</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 class="empty-text">No context-package.json, exploration files, or conflict resolution data found for this session.</div>
|
||||||
</div>
|
</div>
|
||||||
\`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -1019,79 +1019,79 @@ function renderConflictResolutionContext(conflictResolution) {
|
|||||||
let sections = [];
|
let sections = [];
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
sections.push(\`
|
sections.push(`
|
||||||
<div class="conflict-resolution-header">
|
<div class="conflict-resolution-header">
|
||||||
<h4>⚖️ Conflict Resolution Decisions</h4>
|
<h4>⚖️ Conflict Resolution Decisions</h4>
|
||||||
<div class="conflict-meta">
|
<div class="conflict-meta">
|
||||||
<span class="meta-item">Session: <strong>\${escapeHtml(conflictResolution.session_id || 'N/A')}</strong></span>
|
<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>\` : ''}
|
${conflictResolution.resolved_at ? `<span class="meta-item">Resolved: <strong>${formatDate(conflictResolution.resolved_at)}</strong></span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
\`);
|
`);
|
||||||
|
|
||||||
// User decisions
|
// User decisions
|
||||||
if (conflictResolution.user_decisions && Object.keys(conflictResolution.user_decisions).length > 0) {
|
if (conflictResolution.user_decisions && Object.keys(conflictResolution.user_decisions).length > 0) {
|
||||||
const decisions = Object.entries(conflictResolution.user_decisions);
|
const decisions = Object.entries(conflictResolution.user_decisions);
|
||||||
|
|
||||||
sections.push(\`
|
sections.push(`
|
||||||
<div class="conflict-decisions-section collapsible-section">
|
<div class="conflict-decisions-section collapsible-section">
|
||||||
<div class="collapsible-header">
|
<div class="collapsible-header">
|
||||||
<span class="collapse-icon">▶</span>
|
<span class="collapse-icon">▶</span>
|
||||||
<span class="section-label">🎯 User Decisions (\${decisions.length})</span>
|
<span class="section-label">🎯 User Decisions (${decisions.length})</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapsible-content collapsed">
|
<div class="collapsible-content collapsed">
|
||||||
<div class="decisions-list">
|
<div class="decisions-list">
|
||||||
\${decisions.map(([key, decision]) => \`
|
${decisions.map(([key, decision]) => `
|
||||||
<div class="decision-item">
|
<div class="decision-item">
|
||||||
<div class="decision-header">
|
<div class="decision-header">
|
||||||
<span class="decision-key">\${escapeHtml(key.replace(/_/g, ' '))}</span>
|
<span class="decision-key">${escapeHtml(key.replace(/_/g, ' '))}</span>
|
||||||
<span class="decision-choice">\${escapeHtml(decision.choice || 'N/A')}</span>
|
<span class="decision-choice">${escapeHtml(decision.choice || 'N/A')}</span>
|
||||||
</div>
|
</div>
|
||||||
\${decision.description ? \`<p class="decision-description">\${escapeHtml(decision.description)}</p>\` : ''}
|
${decision.description ? `<p class="decision-description">${escapeHtml(decision.description)}</p>` : ''}
|
||||||
\${decision.implications && decision.implications.length > 0 ? \`
|
${decision.implications && decision.implications.length > 0 ? `
|
||||||
<div class="decision-implications">
|
<div class="decision-implications">
|
||||||
<span class="implications-label">Implications:</span>
|
<span class="implications-label">Implications:</span>
|
||||||
<ul class="implications-list">
|
<ul class="implications-list">
|
||||||
\${decision.implications.map(impl => \`<li>\${escapeHtml(impl)}</li>\`).join('')}
|
${decision.implications.map(impl => `<li>${escapeHtml(impl)}</li>`).join('')}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
\` : ''}
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
\`).join('')}
|
`).join('')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
\`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolved conflicts
|
// Resolved conflicts
|
||||||
if (conflictResolution.resolved_conflicts && conflictResolution.resolved_conflicts.length > 0) {
|
if (conflictResolution.resolved_conflicts && conflictResolution.resolved_conflicts.length > 0) {
|
||||||
sections.push(\`
|
sections.push(`
|
||||||
<div class="resolved-conflicts-section collapsible-section">
|
<div class="resolved-conflicts-section collapsible-section">
|
||||||
<div class="collapsible-header">
|
<div class="collapsible-header">
|
||||||
<span class="collapse-icon">▶</span>
|
<span class="collapse-icon">▶</span>
|
||||||
<span class="section-label">✅ Resolved Conflicts (\${conflictResolution.resolved_conflicts.length})</span>
|
<span class="section-label">✅ Resolved Conflicts (${conflictResolution.resolved_conflicts.length})</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapsible-content collapsed">
|
<div class="collapsible-content collapsed">
|
||||||
<div class="conflicts-list">
|
<div class="conflicts-list">
|
||||||
\${conflictResolution.resolved_conflicts.map(conflict => \`
|
${conflictResolution.resolved_conflicts.map(conflict => `
|
||||||
<div class="resolved-conflict-item">
|
<div class="resolved-conflict-item">
|
||||||
<div class="conflict-row">
|
<div class="conflict-row">
|
||||||
<span class="conflict-id">\${escapeHtml(conflict.id || 'N/A')}</span>
|
<span class="conflict-id">${escapeHtml(conflict.id || 'N/A')}</span>
|
||||||
<span class="conflict-category-badge">\${escapeHtml(conflict.category || 'General')}</span>
|
<span class="conflict-category-badge">${escapeHtml(conflict.category || 'General')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="conflict-brief">\${escapeHtml(conflict.brief || '')}</div>
|
<div class="conflict-brief">${escapeHtml(conflict.brief || '')}</div>
|
||||||
<div class="conflict-strategy">
|
<div class="conflict-strategy">
|
||||||
<span class="strategy-label">Strategy:</span>
|
<span class="strategy-label">Strategy:</span>
|
||||||
<span class="strategy-value">\${escapeHtml(conflict.strategy || 'N/A')}</span>
|
<span class="strategy-value">${escapeHtml(conflict.strategy || 'N/A')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
\`).join('')}
|
`).join('')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
\`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return \`<div class="conflict-resolution-context">\${sections.join('')}</div>\`;
|
return `<div class="conflict-resolution-context">${sections.join('')}</div>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,14 +281,9 @@ function renderExplorationContext(explorations) {
|
|||||||
function renderExplorationAngle(angle, data) {
|
function renderExplorationAngle(angle, data) {
|
||||||
let content = [];
|
let content = [];
|
||||||
|
|
||||||
// Project structure (architecture)
|
// Project structure - handle string or object
|
||||||
if (data.project_structure) {
|
if (data.project_structure) {
|
||||||
content.push(`
|
content.push(renderExpField('Project Structure', data.project_structure));
|
||||||
<div class="exp-field">
|
|
||||||
<label>Project Structure</label>
|
|
||||||
<p>${escapeHtml(data.project_structure)}</p>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relevant files
|
// Relevant files
|
||||||
@@ -310,69 +305,30 @@ function renderExplorationAngle(angle, data) {
|
|||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patterns
|
// Patterns - handle string or object
|
||||||
if (data.patterns) {
|
if (data.patterns) {
|
||||||
content.push(`
|
content.push(renderExpField('Patterns', data.patterns));
|
||||||
<div class="exp-field">
|
|
||||||
<label>Patterns</label>
|
|
||||||
<p class="patterns-text">${escapeHtml(data.patterns)}</p>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dependencies
|
// Dependencies - handle string or object
|
||||||
if (data.dependencies) {
|
if (data.dependencies) {
|
||||||
content.push(`
|
content.push(renderExpField('Dependencies', data.dependencies));
|
||||||
<div class="exp-field">
|
|
||||||
<label>Dependencies</label>
|
|
||||||
<p>${escapeHtml(data.dependencies)}</p>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Integration points
|
// Integration points - handle string or object
|
||||||
if (data.integration_points) {
|
if (data.integration_points) {
|
||||||
content.push(`
|
content.push(renderExpField('Integration Points', data.integration_points));
|
||||||
<div class="exp-field">
|
|
||||||
<label>Integration Points</label>
|
|
||||||
<p>${escapeHtml(data.integration_points)}</p>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constraints
|
// Constraints - handle string or object
|
||||||
if (data.constraints) {
|
if (data.constraints) {
|
||||||
content.push(`
|
content.push(renderExpField('Constraints', data.constraints));
|
||||||
<div class="exp-field">
|
|
||||||
<label>Constraints</label>
|
|
||||||
<p>${escapeHtml(data.constraints)}</p>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clarification needs
|
// Clarification needs - handle array or object
|
||||||
if (data.clarification_needs && data.clarification_needs.length) {
|
if (data.clarification_needs) {
|
||||||
content.push(`
|
content.push(renderExpField('Clarification Needs', data.clarification_needs));
|
||||||
<div class="exp-field">
|
|
||||||
<label>Clarification Needs</label>
|
|
||||||
<div class="clarification-list">
|
|
||||||
${data.clarification_needs.map(c => `
|
|
||||||
<div class="clarification-item">
|
|
||||||
<div class="clarification-question">${escapeHtml(c.question)}</div>
|
|
||||||
${c.options && c.options.length ? `
|
|
||||||
<div class="clarification-options">
|
|
||||||
${c.options.map((opt, i) => `
|
|
||||||
<span class="option-badge ${i === c.recommended ? 'recommended' : ''}">${escapeHtml(opt)}</span>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
</div>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = content.join('') || '<p>No data available</p>';
|
return content.join('') || '<p>No data available</p>';
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3704,7 +3704,109 @@ ol.step-commands code {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Relevant Files Grid */
|
/* Relevant Files Grid */
|
||||||
|
/* Exploration Object Rendering */
|
||||||
|
.exp-object {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--bg-secondary, #f9fafb);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color, #e5e7eb);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exp-obj-field {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: flex-start;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exp-obj-key {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary, #6b7280);
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exp-obj-val {
|
||||||
|
color: var(--text-primary, #374151);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exp-obj-nested {
|
||||||
|
margin-left: 16px;
|
||||||
|
padding: 8px;
|
||||||
|
border-left: 2px solid var(--border-color, #e5e7eb);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exp-obj-nested > .exp-obj-key {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--primary, #3b82f6);
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exp-list {
|
||||||
|
margin: 4px 0 0 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exp-list li {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-primary, #374151);
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exp-array-objects {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exp-object-item {
|
||||||
|
padding: 10px;
|
||||||
|
background: var(--bg-primary, #fff);
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--border-color, #e5e7eb);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clarification-impact {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted, #9ca3af);
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-badge.priority-high {
|
||||||
|
background: var(--bg-danger, #fee2e2);
|
||||||
|
color: var(--text-danger, #dc2626);
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-badge.priority-medium {
|
||||||
|
background: var(--bg-warning, #fef3c7);
|
||||||
|
color: var(--text-warning, #d97706);
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-badge.priority-low {
|
||||||
|
background: var(--bg-success, #d1fae5);
|
||||||
|
color: var(--text-success, #059669);
|
||||||
|
}
|
||||||
.relevant-files-list {
|
.relevant-files-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user