pixelcrew 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/package.json +1 -1
- package/src/dashboard/app.js +428 -14
- package/src/dashboard/index.html +46 -1
- package/src/dashboard/styles.css +453 -0
- package/src/orchestrator/engine.js +234 -33
- package/src/server/server.js +31 -3
package/README.md
CHANGED
|
@@ -63,13 +63,13 @@ No global installations or complex configuration required. Run directly in your
|
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
65
|
# 1. Initialize and adapt PixelCrew to your project stack
|
|
66
|
-
npx
|
|
66
|
+
npx pixelcrew init
|
|
67
67
|
|
|
68
68
|
# 2. Launch the orchestrator daemon and real-time visual office
|
|
69
|
-
npx
|
|
69
|
+
npx pixelcrew start
|
|
70
70
|
|
|
71
71
|
# 3. (Optional) Run an instant multi-agent simulated sprint demo
|
|
72
|
-
npx
|
|
72
|
+
npx pixelcrew demo
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
The interactive dashboard opens automatically at:
|
|
@@ -240,15 +240,15 @@ Copy the `.agents/skills/` directory into your tool's native skills folder:
|
|
|
240
240
|
|
|
241
241
|
| Command | Action | Example |
|
|
242
242
|
| :--- | :--- | :--- |
|
|
243
|
-
| `npx
|
|
244
|
-
| `npx
|
|
245
|
-
| `npx
|
|
246
|
-
| `npx
|
|
247
|
-
| `npx
|
|
248
|
-
| `npx
|
|
249
|
-
| `npx
|
|
250
|
-
| `npx
|
|
251
|
-
| `npx
|
|
243
|
+
| `npx pixelcrew init` | Scaffolds `.pixel-agents/` and adapts to repository | `npx pixelcrew init --yes` |
|
|
244
|
+
| `npx pixelcrew start` | Launches orchestrator, SSE stream, and web dashboard | `npx pixelcrew start --port 4747` |
|
|
245
|
+
| `npx pixelcrew analyze` | Prints detected frameworks, database, ORM, and test stack | `npx pixelcrew analyze` |
|
|
246
|
+
| `npx pixelcrew demo` | Launches an interactive 6-agent simulated sprint | `npx pixelcrew demo` |
|
|
247
|
+
| `npx pixelcrew task "<text>"` | Dispatches a direct objective to the running agent swarm | `npx pixelcrew task "Optimize SQL queries"` |
|
|
248
|
+
| `npx pixelcrew emit [flags]` | Emits a custom telemetry event into the stream | `npx pixelcrew emit --agent db --message "Done"` |
|
|
249
|
+
| `npx pixelcrew dashboard` | Opens or serves the standalone Floor 42 dashboard UI | `npx pixelcrew dashboard` |
|
|
250
|
+
| `npx pixelcrew status` | Prints ASCII summary of swarm states and active sprints | `npx pixelcrew status` |
|
|
251
|
+
| `npx pixelcrew help` | Displays full CLI manual and flag details | `npx pixelcrew help` |
|
|
252
252
|
|
|
253
253
|
### CLI Options & Flags:
|
|
254
254
|
- `--port <number>`: Dashboard port (default: `4747`)
|
package/package.json
CHANGED
package/src/dashboard/app.js
CHANGED
|
@@ -574,6 +574,7 @@ async function initApp() {
|
|
|
574
574
|
appState.state = stRes;
|
|
575
575
|
renderState();
|
|
576
576
|
}
|
|
577
|
+
await fetchReports();
|
|
577
578
|
} catch (e) {
|
|
578
579
|
// Handled by SSE
|
|
579
580
|
}
|
|
@@ -650,16 +651,59 @@ function setupEventListeners() {
|
|
|
650
651
|
});
|
|
651
652
|
});
|
|
652
653
|
|
|
653
|
-
//
|
|
654
|
-
document.getElementById('
|
|
655
|
-
|
|
656
|
-
|
|
654
|
+
// Reports Drawer Buttons & Controls
|
|
655
|
+
const btnReportsToggle = document.getElementById('btnReportsToggle');
|
|
656
|
+
if (btnReportsToggle) {
|
|
657
|
+
btnReportsToggle.addEventListener('click', () => {
|
|
658
|
+
synth.playClick();
|
|
659
|
+
openReportsModal();
|
|
660
|
+
});
|
|
661
|
+
}
|
|
657
662
|
|
|
658
|
-
document.getElementById('
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
+
const btnCloseReportsModal = document.getElementById('btnCloseReportsModal');
|
|
664
|
+
if (btnCloseReportsModal) {
|
|
665
|
+
btnCloseReportsModal.addEventListener('click', () => {
|
|
666
|
+
closeReportsModal();
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const reportsModalEl = document.getElementById('reportsModal');
|
|
671
|
+
if (reportsModalEl) {
|
|
672
|
+
reportsModalEl.addEventListener('click', (e) => {
|
|
673
|
+
if (e.target === reportsModalEl) {
|
|
674
|
+
closeReportsModal();
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const btnCopyReportMd = document.getElementById('btnCopyReportMd');
|
|
680
|
+
if (btnCopyReportMd) {
|
|
681
|
+
btnCopyReportMd.addEventListener('click', () => {
|
|
682
|
+
copyActiveReportMarkdown();
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const btnDownloadReport = document.getElementById('btnDownloadReport');
|
|
687
|
+
if (btnDownloadReport) {
|
|
688
|
+
btnDownloadReport.addEventListener('click', () => {
|
|
689
|
+
downloadActiveReport();
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const btnToggleRawView = document.getElementById('btnToggleRawView');
|
|
694
|
+
if (btnToggleRawView) {
|
|
695
|
+
btnToggleRawView.addEventListener('click', () => {
|
|
696
|
+
toggleRawReportView();
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const reportsSearchInput = document.getElementById('reportsSearch');
|
|
701
|
+
if (reportsSearchInput) {
|
|
702
|
+
reportsSearchInput.addEventListener('input', (e) => {
|
|
703
|
+
reportsState.searchQuery = e.target.value.toLowerCase().trim();
|
|
704
|
+
renderReportsList();
|
|
705
|
+
});
|
|
706
|
+
}
|
|
663
707
|
|
|
664
708
|
// Global Keyboard Shortcuts
|
|
665
709
|
window.addEventListener('keydown', (e) => {
|
|
@@ -669,6 +713,14 @@ function setupEventListeners() {
|
|
|
669
713
|
document.getElementById('btnDemo').click();
|
|
670
714
|
} else if (e.key === 'Escape') {
|
|
671
715
|
document.getElementById('agentModal').classList.remove('open');
|
|
716
|
+
closeReportsModal();
|
|
717
|
+
} else if (e.key === 'r' || e.key === 'R') {
|
|
718
|
+
const modal = document.getElementById('reportsModal');
|
|
719
|
+
if (modal && modal.classList.contains('open')) {
|
|
720
|
+
closeReportsModal();
|
|
721
|
+
} else {
|
|
722
|
+
openReportsModal();
|
|
723
|
+
}
|
|
672
724
|
} else if (['1', '2', '3', '4', '5', '6'].includes(e.key)) {
|
|
673
725
|
const keys = ['frontend', 'backend', 'database', 'security', 'performance', 'qa'];
|
|
674
726
|
const agentKey = keys[parseInt(e.key) - 1];
|
|
@@ -677,6 +729,357 @@ function setupEventListeners() {
|
|
|
677
729
|
});
|
|
678
730
|
}
|
|
679
731
|
|
|
732
|
+
// Reports State & Controller
|
|
733
|
+
const reportsState = {
|
|
734
|
+
reports: [],
|
|
735
|
+
activeReport: null,
|
|
736
|
+
searchQuery: '',
|
|
737
|
+
rawMode: false
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
async function fetchReports() {
|
|
741
|
+
try {
|
|
742
|
+
const res = await fetch('/api/reports');
|
|
743
|
+
if (res.ok) {
|
|
744
|
+
const data = await res.json();
|
|
745
|
+
reportsState.reports = data.reports || [];
|
|
746
|
+
const badge = document.getElementById('reportsCountBadge');
|
|
747
|
+
if (badge) {
|
|
748
|
+
badge.textContent = reportsState.reports.length;
|
|
749
|
+
}
|
|
750
|
+
return reportsState.reports;
|
|
751
|
+
}
|
|
752
|
+
} catch (err) {
|
|
753
|
+
console.error('Error loading reports:', err);
|
|
754
|
+
}
|
|
755
|
+
return [];
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
async function openReportsModal(preferredId = null) {
|
|
759
|
+
const modal = document.getElementById('reportsModal');
|
|
760
|
+
if (!modal) return;
|
|
761
|
+
|
|
762
|
+
await fetchReports();
|
|
763
|
+
|
|
764
|
+
if (preferredId) {
|
|
765
|
+
reportsState.activeReport = reportsState.reports.find(r => r.id === preferredId) || reportsState.reports[0] || null;
|
|
766
|
+
} else if (!reportsState.activeReport && reportsState.reports.length > 0) {
|
|
767
|
+
reportsState.activeReport = reportsState.reports[0];
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
renderReportsList();
|
|
771
|
+
renderActiveReport();
|
|
772
|
+
|
|
773
|
+
modal.classList.add('open');
|
|
774
|
+
synth.playClick();
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function closeReportsModal() {
|
|
778
|
+
const modal = document.getElementById('reportsModal');
|
|
779
|
+
if (modal) modal.classList.remove('open');
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function renderReportsList() {
|
|
783
|
+
const container = document.getElementById('reportsHistoryList');
|
|
784
|
+
if (!container) return;
|
|
785
|
+
|
|
786
|
+
container.innerHTML = '';
|
|
787
|
+
const filtered = reportsState.reports.filter(r => {
|
|
788
|
+
if (!reportsState.searchQuery) return true;
|
|
789
|
+
const q = reportsState.searchQuery;
|
|
790
|
+
return (
|
|
791
|
+
(r.objective && r.objective.toLowerCase().includes(q)) ||
|
|
792
|
+
(r.project && r.project.toLowerCase().includes(q)) ||
|
|
793
|
+
(r.dateFormatted && r.dateFormatted.toLowerCase().includes(q))
|
|
794
|
+
);
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
if (filtered.length === 0) {
|
|
798
|
+
container.innerHTML = `
|
|
799
|
+
<div style="padding: 16px; text-align: center; color: var(--text-dim); font-size: 11px;">
|
|
800
|
+
${reportsState.searchQuery ? 'No matching reports found.' : 'No audit reports recorded yet. Run a sprint demo or task to generate.'}
|
|
801
|
+
</div>
|
|
802
|
+
`;
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
filtered.forEach(report => {
|
|
807
|
+
const card = document.createElement('div');
|
|
808
|
+
card.className = `report-item-card ${reportsState.activeReport?.id === report.id ? 'active' : ''}`;
|
|
809
|
+
|
|
810
|
+
card.innerHTML = `
|
|
811
|
+
<div class="report-item-top">
|
|
812
|
+
<span class="report-item-date">${report.dateFormatted || new Date(report.timestamp).toLocaleDateString()}</span>
|
|
813
|
+
<span class="report-item-status">${report.status || 'COMPLETED'}</span>
|
|
814
|
+
</div>
|
|
815
|
+
<div class="report-item-title">${report.objective || 'Swarm Audit Sprint'}</div>
|
|
816
|
+
<div class="report-item-meta">
|
|
817
|
+
<span>👥 ${(report.targetAgents || []).length} Squads</span>
|
|
818
|
+
<span>✨ ${report.totalFindings || (report.sections || []).length} Points</span>
|
|
819
|
+
</div>
|
|
820
|
+
`;
|
|
821
|
+
|
|
822
|
+
card.addEventListener('click', () => {
|
|
823
|
+
synth.playClick();
|
|
824
|
+
reportsState.activeReport = report;
|
|
825
|
+
renderReportsList();
|
|
826
|
+
renderActiveReport();
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
container.appendChild(card);
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function formatMarkdownText(text) {
|
|
834
|
+
if (!text) return '';
|
|
835
|
+
let html = escapeHtml(text);
|
|
836
|
+
|
|
837
|
+
// Bold: **text** or __text__
|
|
838
|
+
html = html.replace(/\*\*(.*?)\*\*/g, '<strong class="report-bold">$1</strong>');
|
|
839
|
+
html = html.replace(/__(.*?)__/g, '<strong class="report-bold">$1</strong>');
|
|
840
|
+
|
|
841
|
+
// Inline Code: `code`
|
|
842
|
+
html = html.replace(/`([^`]+)`/g, '<code class="report-inline-code">$1</code>');
|
|
843
|
+
|
|
844
|
+
// Italic: *text* or _text_
|
|
845
|
+
html = html.replace(/\*([^*]+)\*/g, '<em class="report-italic">$1</em>');
|
|
846
|
+
html = html.replace(/_([^_]+)_/g, '<em class="report-italic">$1</em>');
|
|
847
|
+
|
|
848
|
+
return html;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function parseFindingItem(finding) {
|
|
852
|
+
let rawCategory = (finding.category || '').trim();
|
|
853
|
+
let rawDesc = (finding.description || '').trim();
|
|
854
|
+
|
|
855
|
+
let combined = rawDesc;
|
|
856
|
+
if (!rawDesc || rawDesc === rawCategory) {
|
|
857
|
+
combined = rawCategory;
|
|
858
|
+
} else if (rawCategory && rawDesc) {
|
|
859
|
+
combined = `${rawCategory}: ${rawDesc}`;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
let badge = 'Finding';
|
|
863
|
+
let path = null;
|
|
864
|
+
let description = combined;
|
|
865
|
+
|
|
866
|
+
// 1. Extract markdown bold badge if present: **Title (`path`)** or **Title**
|
|
867
|
+
const boldPrefixMatch = combined.match(/^(\*\*|__)?([^*_`:]+?)(?:\s*\(`?([^`)]+?)`?\))?(\*\*|__)?(?::\s*|\s*-\s*|\s*—\s*)(.*)$/s);
|
|
868
|
+
if (boldPrefixMatch) {
|
|
869
|
+
badge = boldPrefixMatch[2].replace(/\*\*/g, '').trim();
|
|
870
|
+
path = boldPrefixMatch[3] ? boldPrefixMatch[3].trim() : null;
|
|
871
|
+
description = boldPrefixMatch[5] ? boldPrefixMatch[5].trim() : '';
|
|
872
|
+
} else {
|
|
873
|
+
// 2. Check if category itself contains title and path
|
|
874
|
+
const catMatch = rawCategory.match(/^(\*\*|__)?([^*_`:]+?)(?:\s*\(`?([^`)]+?)`?\))?(\*\*|__)?$/);
|
|
875
|
+
if (catMatch) {
|
|
876
|
+
badge = catMatch[2].replace(/\*\*/g, '').trim();
|
|
877
|
+
path = catMatch[3] ? catMatch[3].trim() : null;
|
|
878
|
+
description = rawDesc;
|
|
879
|
+
} else {
|
|
880
|
+
const colonIndex = combined.indexOf(':');
|
|
881
|
+
if (colonIndex > 0 && colonIndex < 45) {
|
|
882
|
+
let rawTitle = combined.substring(0, colonIndex).replace(/\*\*/g, '').replace(/__/g, '').trim();
|
|
883
|
+
const pathMatch = rawTitle.match(/^(.*?)(?:\s*\(`?([^`)]+?)`?\))$/);
|
|
884
|
+
if (pathMatch) {
|
|
885
|
+
badge = pathMatch[1].trim();
|
|
886
|
+
path = pathMatch[2].trim();
|
|
887
|
+
} else {
|
|
888
|
+
badge = rawTitle;
|
|
889
|
+
}
|
|
890
|
+
description = combined.substring(colonIndex + 1).trim();
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
badge = badge.replace(/\*\*/g, '').replace(/`/g, '').trim();
|
|
896
|
+
if (!badge) badge = 'Observation';
|
|
897
|
+
|
|
898
|
+
return {
|
|
899
|
+
badge,
|
|
900
|
+
path,
|
|
901
|
+
descriptionHtml: formatMarkdownText(description || combined)
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function renderActiveReport() {
|
|
906
|
+
const viewer = document.getElementById('reportsViewer');
|
|
907
|
+
if (!viewer) return;
|
|
908
|
+
|
|
909
|
+
const report = reportsState.activeReport;
|
|
910
|
+
if (!report) {
|
|
911
|
+
viewer.innerHTML = `
|
|
912
|
+
<div class="report-empty-state">
|
|
913
|
+
<span style="font-size: 32px;">📋</span>
|
|
914
|
+
<span>SELECT A SPRINT REPORT FROM THE LEFT SIDEBAR</span>
|
|
915
|
+
<span style="color: var(--text-dim); font-size: 11px;">Audit reports are generated automatically after every completed swarm run.</span>
|
|
916
|
+
</div>
|
|
917
|
+
`;
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Raw Markdown Mode
|
|
922
|
+
if (reportsState.rawMode) {
|
|
923
|
+
viewer.innerHTML = `
|
|
924
|
+
<div class="report-hero-card">
|
|
925
|
+
<div class="report-hero-header">
|
|
926
|
+
<div>
|
|
927
|
+
<h3 class="report-hero-title">RAW MARKDOWN SPECIFICATION</h3>
|
|
928
|
+
<div class="report-hero-meta">
|
|
929
|
+
<span>PROJECT: <strong>${escapeHtml(report.project || 'Project')}</strong></span>
|
|
930
|
+
<span>DATE: <strong>${escapeHtml(report.dateFormatted || 'N/A')}</strong></span>
|
|
931
|
+
</div>
|
|
932
|
+
</div>
|
|
933
|
+
</div>
|
|
934
|
+
</div>
|
|
935
|
+
<pre class="report-raw-pre">${escapeHtml(report.markdown || '# No markdown available')}</pre>
|
|
936
|
+
`;
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Visual Interactive Card Mode
|
|
941
|
+
viewer.innerHTML = `
|
|
942
|
+
<!-- HERO SUMMARY -->
|
|
943
|
+
<div class="report-hero-card">
|
|
944
|
+
<div class="report-hero-header">
|
|
945
|
+
<div>
|
|
946
|
+
<h3 class="report-hero-title">★ ${escapeHtml(report.objective || 'Sprint Audit')}</h3>
|
|
947
|
+
<div class="report-hero-meta">
|
|
948
|
+
<span>PRODUCT: <strong>${escapeHtml(report.project || 'Project')}</strong></span>
|
|
949
|
+
<span>DATE: <strong>${escapeHtml(report.dateFormatted || 'Today')}</strong></span>
|
|
950
|
+
<span>SQUADS: <strong>${(report.targetAgents || []).map(a => a.toUpperCase()).join(', ')}</strong></span>
|
|
951
|
+
</div>
|
|
952
|
+
</div>
|
|
953
|
+
<span class="report-item-status">COMPLETED</span>
|
|
954
|
+
</div>
|
|
955
|
+
<div class="report-summary-box">
|
|
956
|
+
<strong>Executive Summary:</strong> The autonomous engineering swarm executed an architectural and quality audit, resolving dependencies and formulating <strong>${report.totalFindings || 0} actionable improvements</strong>.
|
|
957
|
+
</div>
|
|
958
|
+
</div>
|
|
959
|
+
|
|
960
|
+
<!-- METRICS GRID -->
|
|
961
|
+
<div class="report-metrics-grid">
|
|
962
|
+
<div class="report-metric-card">
|
|
963
|
+
<span class="report-metric-label">ENGINEERING SQUADS</span>
|
|
964
|
+
<span class="report-metric-val" style="color: var(--color-cyan);">${(report.targetAgents || []).length}</span>
|
|
965
|
+
</div>
|
|
966
|
+
<div class="report-metric-card">
|
|
967
|
+
<span class="report-metric-label">FINDINGS & ACTIONS</span>
|
|
968
|
+
<span class="report-metric-val" style="color: var(--color-gold);">${report.totalFindings || 0}</span>
|
|
969
|
+
</div>
|
|
970
|
+
<div class="report-metric-card">
|
|
971
|
+
<span class="report-metric-label">QUALITY GATE</span>
|
|
972
|
+
<span class="report-metric-val" style="color: var(--color-green);">PASSED</span>
|
|
973
|
+
</div>
|
|
974
|
+
<div class="report-metric-card">
|
|
975
|
+
<span class="report-metric-label">BLOCKER DEFECTS</span>
|
|
976
|
+
<span class="report-metric-val" style="color: var(--color-green);">0</span>
|
|
977
|
+
</div>
|
|
978
|
+
</div>
|
|
979
|
+
|
|
980
|
+
<!-- SQUAD FINDINGS ACCORDION / CARDS -->
|
|
981
|
+
<div class="report-sections-grid">
|
|
982
|
+
${(report.sections || []).map(section => {
|
|
983
|
+
const squadColor = section.color || '#00f0ff';
|
|
984
|
+
const findings = section.findings || [];
|
|
985
|
+
return `
|
|
986
|
+
<div class="report-squad-card" style="border-left: 4px solid ${squadColor};">
|
|
987
|
+
<div class="report-squad-header">
|
|
988
|
+
<div class="report-squad-header-left" style="color: ${squadColor};">
|
|
989
|
+
<span>${section.icon || '💼'}</span>
|
|
990
|
+
<span>${escapeHtml(section.name || section.agent.toUpperCase())}</span>
|
|
991
|
+
</div>
|
|
992
|
+
<span class="report-squad-count">${findings.length} ${findings.length === 1 ? 'Point' : 'Points'}</span>
|
|
993
|
+
</div>
|
|
994
|
+
<div class="report-findings-list">
|
|
995
|
+
${findings.map(f => {
|
|
996
|
+
const parsed = parseFindingItem(f);
|
|
997
|
+
return `
|
|
998
|
+
<div class="report-finding-card">
|
|
999
|
+
<div class="finding-card-header">
|
|
1000
|
+
<span class="finding-badge" style="background: rgba(0, 240, 255, 0.08); border-color: ${squadColor}40; color: ${squadColor};">
|
|
1001
|
+
${escapeHtml(parsed.badge)}
|
|
1002
|
+
</span>
|
|
1003
|
+
${parsed.path ? `<span class="finding-path-pill"><code>${escapeHtml(parsed.path)}</code></span>` : ''}
|
|
1004
|
+
</div>
|
|
1005
|
+
<div class="finding-body-text">
|
|
1006
|
+
${parsed.descriptionHtml}
|
|
1007
|
+
</div>
|
|
1008
|
+
</div>
|
|
1009
|
+
`;
|
|
1010
|
+
}).join('')}
|
|
1011
|
+
</div>
|
|
1012
|
+
</div>
|
|
1013
|
+
`;
|
|
1014
|
+
}).join('')}
|
|
1015
|
+
</div>
|
|
1016
|
+
|
|
1017
|
+
<!-- ACTION ITEMS CHECKLIST -->
|
|
1018
|
+
<div class="report-actions-card">
|
|
1019
|
+
<div class="report-actions-title">📋 RECOMMENDED ACTION ITEMS & NEXT STEPS</div>
|
|
1020
|
+
<div class="report-checklist">
|
|
1021
|
+
${(report.actionItems || []).map(item => `
|
|
1022
|
+
<label class="report-check-item">
|
|
1023
|
+
<input type="checkbox" />
|
|
1024
|
+
<span>${formatMarkdownText(item)}</span>
|
|
1025
|
+
</label>
|
|
1026
|
+
`).join('')}
|
|
1027
|
+
</div>
|
|
1028
|
+
</div>
|
|
1029
|
+
`;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function toggleRawReportView() {
|
|
1033
|
+
synth.playClick();
|
|
1034
|
+
reportsState.rawMode = !reportsState.rawMode;
|
|
1035
|
+
const label = document.getElementById('toggleRawLabel');
|
|
1036
|
+
if (label) {
|
|
1037
|
+
label.textContent = reportsState.rawMode ? 'VISUAL CARDS' : 'RAW MARKDOWN';
|
|
1038
|
+
}
|
|
1039
|
+
renderActiveReport();
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function copyActiveReportMarkdown() {
|
|
1043
|
+
if (!reportsState.activeReport?.markdown) return;
|
|
1044
|
+
navigator.clipboard.writeText(reportsState.activeReport.markdown).then(() => {
|
|
1045
|
+
synth.playSkill();
|
|
1046
|
+
const btn = document.getElementById('btnCopyReportMd');
|
|
1047
|
+
if (btn) {
|
|
1048
|
+
const originalText = btn.innerHTML;
|
|
1049
|
+
btn.innerHTML = '<span>✓</span> COPIED!';
|
|
1050
|
+
setTimeout(() => { btn.innerHTML = originalText; }, 2000);
|
|
1051
|
+
}
|
|
1052
|
+
}).catch(err => {
|
|
1053
|
+
console.error('Failed to copy markdown:', err);
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function downloadActiveReport() {
|
|
1058
|
+
if (!reportsState.activeReport) return;
|
|
1059
|
+
const r = reportsState.activeReport;
|
|
1060
|
+
const content = r.markdown || JSON.stringify(r, null, 2);
|
|
1061
|
+
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
|
|
1062
|
+
const url = URL.createObjectURL(blob);
|
|
1063
|
+
const a = document.createElement('a');
|
|
1064
|
+
a.href = url;
|
|
1065
|
+
a.download = `${r.id || 'sprint-report'}.md`;
|
|
1066
|
+
document.body.appendChild(a);
|
|
1067
|
+
a.click();
|
|
1068
|
+
document.body.removeChild(a);
|
|
1069
|
+
URL.revokeObjectURL(url);
|
|
1070
|
+
synth.playComplete();
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
function escapeHtml(str) {
|
|
1074
|
+
if (!str) return '';
|
|
1075
|
+
return String(str)
|
|
1076
|
+
.replace(/&/g, '&')
|
|
1077
|
+
.replace(/</g, '<')
|
|
1078
|
+
.replace(/>/g, '>')
|
|
1079
|
+
.replace(/"/g, '"')
|
|
1080
|
+
.replace(/'/g, ''');
|
|
1081
|
+
}
|
|
1082
|
+
|
|
680
1083
|
// Submit Task to Server
|
|
681
1084
|
async function submitTask(prompt) {
|
|
682
1085
|
try {
|
|
@@ -759,17 +1162,28 @@ function connectSSE() {
|
|
|
759
1162
|
renderState();
|
|
760
1163
|
}
|
|
761
1164
|
|
|
762
|
-
// Audio cues
|
|
763
|
-
if (event.type === 'spawn')
|
|
764
|
-
|
|
765
|
-
else if (event.type === '
|
|
766
|
-
|
|
1165
|
+
// Audio cues & Report Refresh
|
|
1166
|
+
if (event.type === 'spawn') {
|
|
1167
|
+
synth.playSpawn();
|
|
1168
|
+
} else if (event.type === 'skill') {
|
|
1169
|
+
synth.playSkill();
|
|
1170
|
+
} else if (event.type === 'complete') {
|
|
1171
|
+
synth.playComplete();
|
|
1172
|
+
if (event.agent === 'orchestrator' || event.metadata?.report) {
|
|
1173
|
+
fetchReports();
|
|
1174
|
+
}
|
|
1175
|
+
} else if (event.type === 'error') {
|
|
1176
|
+
synth.playError();
|
|
1177
|
+
}
|
|
767
1178
|
});
|
|
768
1179
|
|
|
769
1180
|
eventSource.addEventListener('state_change', (e) => {
|
|
770
1181
|
const data = JSON.parse(e.data);
|
|
771
1182
|
appState.state = data.state;
|
|
772
1183
|
renderState();
|
|
1184
|
+
if (data.state?.status === 'COMPLETED') {
|
|
1185
|
+
fetchReports();
|
|
1186
|
+
}
|
|
773
1187
|
});
|
|
774
1188
|
|
|
775
1189
|
eventSource.onerror = () => {
|
package/src/dashboard/index.html
CHANGED
|
@@ -50,6 +50,9 @@
|
|
|
50
50
|
</div>
|
|
51
51
|
|
|
52
52
|
<div class="header-right">
|
|
53
|
+
<button class="pixel-btn btn-sm btn-info" id="btnReportsToggle" title="Open Sprint Audit Reports History [R]">
|
|
54
|
+
<span>📋</span> REPORTS (<span id="reportsCountBadge">0</span>)
|
|
55
|
+
</button>
|
|
53
56
|
<button class="pixel-btn btn-sm" id="btnAudioToggle" title="Toggle 8-bit Audio SFX">
|
|
54
57
|
<span id="audioIcon">🔊</span> SFX
|
|
55
58
|
</button>
|
|
@@ -213,10 +216,52 @@
|
|
|
213
216
|
</div>
|
|
214
217
|
</div>
|
|
215
218
|
|
|
219
|
+
<!-- SPRINT AUDIT REPORTS & HISTORY MODAL / DRAWER -->
|
|
220
|
+
<div class="pixel-modal-backdrop" id="reportsModal">
|
|
221
|
+
<div class="pixel-modal-card reports-modal-card">
|
|
222
|
+
<div class="modal-header">
|
|
223
|
+
<div class="modal-title-box">
|
|
224
|
+
<span class="modal-agent-icon">📋</span>
|
|
225
|
+
<h2 class="modal-agent-name" style="color: var(--color-cyan);">SPRINT AUDIT REPORTS & HISTORY</h2>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="reports-header-actions">
|
|
228
|
+
<button class="pixel-btn btn-xs" id="btnToggleRawView" title="Toggle between visual card and raw markdown">
|
|
229
|
+
<span>👁️</span> <span id="toggleRawLabel">RAW MARKDOWN</span>
|
|
230
|
+
</button>
|
|
231
|
+
<button class="pixel-btn btn-xs" id="btnCopyReportMd" title="Copy markdown to clipboard">
|
|
232
|
+
<span>📄</span> COPY MD
|
|
233
|
+
</button>
|
|
234
|
+
<button class="pixel-btn btn-xs" id="btnDownloadReport" title="Download report as .md file">
|
|
235
|
+
<span>💾</span> DOWNLOAD
|
|
236
|
+
</button>
|
|
237
|
+
<button class="pixel-btn btn-sm btn-warn" id="btnCloseReportsModal">✕</button>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
<div class="reports-modal-body">
|
|
241
|
+
<!-- SPRINT HISTORY SIDEBAR -->
|
|
242
|
+
<aside class="reports-sidebar">
|
|
243
|
+
<div class="reports-search-box">
|
|
244
|
+
<input type="text" class="pixel-input reports-search-input" id="reportsSearch" placeholder="Search past sprint reports..." />
|
|
245
|
+
</div>
|
|
246
|
+
<div class="reports-history-list" id="reportsHistoryList">
|
|
247
|
+
<!-- Populated by app.js -->
|
|
248
|
+
</div>
|
|
249
|
+
</aside>
|
|
250
|
+
|
|
251
|
+
<!-- ACTIVE REPORT DETAIL VIEWER -->
|
|
252
|
+
<main class="reports-viewer" id="reportsViewer">
|
|
253
|
+
<!-- Populated dynamically -->
|
|
254
|
+
</main>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
216
259
|
<!-- ARCADE / OFFICE FOOTER -->
|
|
217
260
|
<footer class="arcade-footer">
|
|
218
261
|
<div class="footer-left">
|
|
219
|
-
<span>PRESS [1..6]
|
|
262
|
+
<span>PRESS [1..6] EMPLOYEE PROFILE</span>
|
|
263
|
+
<span>•</span>
|
|
264
|
+
<span>[R] SPRINT REPORTS</span>
|
|
220
265
|
<span>•</span>
|
|
221
266
|
<span>[SPACE] SPRINT DEMO</span>
|
|
222
267
|
</div>
|