@sienklogic/plan-build-run 2.27.0 → 2.27.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dashboard/public/css/layout.css +7 -283
  3. package/dashboard/public/css/status-colors.css +7 -0
  4. package/dashboard/public/css/tokens.css +3 -3
  5. package/dashboard/public/js/sidebar-toggle.js +9 -31
  6. package/dashboard/public/js/theme-toggle.js +4 -4
  7. package/dashboard/src/services/phase.service.js +6 -2
  8. package/dashboard/src/views/partials/activity-feed.ejs +17 -9
  9. package/dashboard/src/views/partials/analytics-content.ejs +178 -88
  10. package/dashboard/src/views/partials/audit-detail-content.ejs +6 -4
  11. package/dashboard/src/views/partials/audits-content.ejs +28 -26
  12. package/dashboard/src/views/partials/breadcrumbs.ejs +8 -4
  13. package/dashboard/src/views/partials/config-content.ejs +98 -95
  14. package/dashboard/src/views/partials/dashboard-content.ejs +69 -60
  15. package/dashboard/src/views/partials/dependencies-content.ejs +5 -3
  16. package/dashboard/src/views/partials/empty-state.ejs +10 -5
  17. package/dashboard/src/views/partials/footer.ejs +8 -2
  18. package/dashboard/src/views/partials/head.ejs +2 -1
  19. package/dashboard/src/views/partials/header.ejs +16 -19
  20. package/dashboard/src/views/partials/layout-bottom.ejs +5 -40
  21. package/dashboard/src/views/partials/layout-top.ejs +6 -5
  22. package/dashboard/src/views/partials/logs-content.ejs +26 -29
  23. package/dashboard/src/views/partials/milestone-detail-content.ejs +5 -5
  24. package/dashboard/src/views/partials/milestones-content.ejs +40 -31
  25. package/dashboard/src/views/partials/note-detail-content.ejs +7 -5
  26. package/dashboard/src/views/partials/notes-content.ejs +4 -4
  27. package/dashboard/src/views/partials/phase-content.ejs +6 -8
  28. package/dashboard/src/views/partials/phase-doc-content.ejs +13 -15
  29. package/dashboard/src/views/partials/phase-timeline.ejs +22 -15
  30. package/dashboard/src/views/partials/phases-content.ejs +100 -86
  31. package/dashboard/src/views/partials/quick-content.ejs +34 -32
  32. package/dashboard/src/views/partials/quick-detail-content.ejs +20 -19
  33. package/dashboard/src/views/partials/requirements-content.ejs +6 -6
  34. package/dashboard/src/views/partials/research-content.ejs +14 -14
  35. package/dashboard/src/views/partials/research-detail-content.ejs +13 -11
  36. package/dashboard/src/views/partials/roadmap-content.ejs +149 -132
  37. package/dashboard/src/views/partials/sidebar.ejs +86 -140
  38. package/dashboard/src/views/partials/todo-create-content.ejs +51 -46
  39. package/dashboard/src/views/partials/todo-detail-content.ejs +26 -25
  40. package/dashboard/src/views/partials/todos-content.ejs +65 -62
  41. package/dashboard/src/views/partials/todos-done-content.ejs +33 -31
  42. package/package.json +1 -1
  43. package/plugins/copilot-pbr/plugin.json +1 -1
  44. package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
  45. package/plugins/pbr/.claude-plugin/plugin.json +1 -1
  46. package/plugins/pbr/scripts/local-llm/metrics.js +121 -33
@@ -2,9 +2,11 @@
2
2
  <h1>Phases</h1>
3
3
 
4
4
  <% if (phases.length === 0) { %>
5
- <article>
6
- <p>No phases found. Add a ROADMAP.md file to your .planning/ directory to see phases here.</p>
7
- </article>
5
+ <div class="card mb-3">
6
+ <div class="card-body">
7
+ <p>No phases found. Add a ROADMAP.md file to your .planning/ directory to see phases here.</p>
8
+ </div>
9
+ </div>
8
10
  <% } else { %>
9
11
 
10
12
  <%
@@ -20,20 +22,28 @@
20
22
  %>
21
23
 
22
24
  <!-- Summary -->
23
- <article>
24
- <header><strong>Summary</strong></header>
25
- <p>
26
- <%= phases.filter(p => p.status === 'complete').length %> of <%= phases.length %> phases complete<%= milestoneGroups.length > 0 ? ' across ' + milestoneGroups.length + ' milestone' + (milestoneGroups.length !== 1 ? 's' : '') : '' %>.
27
- </p>
28
- <progress value="<%= phases.filter(p => p.status === 'complete').length %>" max="<%= phases.length %>"></progress>
29
- </article>
25
+ <div class="card mb-3">
26
+ <div class="card-header"><strong>Summary</strong></div>
27
+ <div class="card-body">
28
+ <p>
29
+ <%= phases.filter(p => p.status === 'complete').length %> of <%= phases.length %> phases complete<%= milestoneGroups.length > 0 ? ' across ' + milestoneGroups.length + ' milestone' + (milestoneGroups.length !== 1 ? 's' : '') : '' %>.
30
+ </p>
31
+ <div class="progress" style="height:0.5rem">
32
+ <div class="progress-bar" role="progressbar"
33
+ style="width:<%= phases.filter(p => p.status === 'complete').length / phases.length * 100 %>%"
34
+ aria-valuenow="<%= phases.filter(p => p.status === 'complete').length %>"
35
+ aria-valuemin="0" aria-valuemax="<%= phases.length %>">
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
30
40
 
31
41
  <% milestoneGroups.forEach(function(group) { %>
32
- <article>
33
- <header>
42
+ <div class="card mb-3">
43
+ <div class="card-header">
34
44
  <strong><%= group.name %></strong>
35
- <small>(Phases <%= String(group.startPhase).padStart(2, '0') %>&ndash;<%= String(group.endPhase).padStart(2, '0') %>)</small>
36
- &mdash;
45
+ <small class="ms-1">(Phases <%= String(group.startPhase).padStart(2, '0') %>&ndash;<%= String(group.endPhase).padStart(2, '0') %>)</small>
46
+ &nbsp;&mdash;&nbsp;
37
47
  <%
38
48
  const groupComplete = group.phases.filter(p => p.status === 'complete').length;
39
49
  const groupTotal = group.phases.length;
@@ -41,83 +51,87 @@
41
51
  <span class="status-badge" data-status="<%= groupComplete === groupTotal ? 'complete' : (groupComplete > 0 ? 'in-progress' : 'not-started') %>">
42
52
  <%= groupComplete %>/<%= groupTotal %>
43
53
  </span>
44
- </header>
45
- <% if (group.goal) { %>
46
- <p><small><%= group.goal %></small></p>
47
- <% } %>
48
- <div class="table-wrap">
49
- <table>
50
- <thead>
51
- <tr>
52
- <th scope="col">Phase</th>
53
- <th scope="col">Name</th>
54
- <th scope="col">Plans</th>
55
- <th scope="col">Status</th>
56
- </tr>
57
- </thead>
58
- <tbody>
59
- <% group.phases.forEach(function(phase) { %>
60
- <tr>
61
- <td><%= String(phase.id).padStart(2, '0') %></td>
62
- <td>
63
- <a href="/phases/<%= String(phase.id).padStart(2, '0') %>"
64
- hx-get="/phases/<%= String(phase.id).padStart(2, '0') %>"
65
- hx-target="#main-content"
66
- hx-push-url="true">
67
- <%= phase.name %>
68
- </a>
69
- </td>
70
- <td><%= phase.planCount %></td>
71
- <td>
72
- <span class="status-badge" data-status="<%= phase.status %>">
73
- <%= phase.status.replace('-', ' ') %>
74
- </span>
75
- </td>
76
- </tr>
77
- <% }); %>
78
- </tbody>
79
- </table>
80
54
  </div>
81
- </article>
55
+ <div class="card-body">
56
+ <% if (group.goal) { %>
57
+ <p><small><%= group.goal %></small></p>
58
+ <% } %>
59
+ <div class="table-responsive">
60
+ <table class="table table-vcenter table-hover">
61
+ <thead>
62
+ <tr>
63
+ <th scope="col">Phase</th>
64
+ <th scope="col">Name</th>
65
+ <th scope="col">Plans</th>
66
+ <th scope="col">Status</th>
67
+ </tr>
68
+ </thead>
69
+ <tbody>
70
+ <% group.phases.forEach(function(phase) { %>
71
+ <tr>
72
+ <td><%= String(phase.id).padStart(2, '0') %></td>
73
+ <td>
74
+ <a href="/phases/<%= String(phase.id).padStart(2, '0') %>"
75
+ hx-get="/phases/<%= String(phase.id).padStart(2, '0') %>"
76
+ hx-target="#main-content"
77
+ hx-push-url="true">
78
+ <%= phase.name %>
79
+ </a>
80
+ </td>
81
+ <td><%= phase.planCount %></td>
82
+ <td>
83
+ <span class="status-badge" data-status="<%= phase.status %>">
84
+ <%= phase.status.replace('-', ' ') %>
85
+ </span>
86
+ </td>
87
+ </tr>
88
+ <% }); %>
89
+ </tbody>
90
+ </table>
91
+ </div>
92
+ </div>
93
+ </div>
82
94
  <% }); %>
83
95
 
84
96
  <% if (ungrouped.length > 0) { %>
85
- <article>
86
- <header><strong>Other Phases</strong></header>
87
- <div class="table-wrap">
88
- <table>
89
- <thead>
90
- <tr>
91
- <th scope="col">Phase</th>
92
- <th scope="col">Name</th>
93
- <th scope="col">Plans</th>
94
- <th scope="col">Status</th>
95
- </tr>
96
- </thead>
97
- <tbody>
98
- <% ungrouped.forEach(function(phase) { %>
99
- <tr>
100
- <td><%= String(phase.id).padStart(2, '0') %></td>
101
- <td>
102
- <a href="/phases/<%= String(phase.id).padStart(2, '0') %>"
103
- hx-get="/phases/<%= String(phase.id).padStart(2, '0') %>"
104
- hx-target="#main-content"
105
- hx-push-url="true">
106
- <%= phase.name %>
107
- </a>
108
- </td>
109
- <td><%= phase.planCount %></td>
110
- <td>
111
- <span class="status-badge" data-status="<%= phase.status %>">
112
- <%= phase.status.replace('-', ' ') %>
113
- </span>
114
- </td>
115
- </tr>
116
- <% }); %>
117
- </tbody>
118
- </table>
97
+ <div class="card mb-3">
98
+ <div class="card-header"><strong>Other Phases</strong></div>
99
+ <div class="card-body">
100
+ <div class="table-responsive">
101
+ <table class="table table-vcenter table-hover">
102
+ <thead>
103
+ <tr>
104
+ <th scope="col">Phase</th>
105
+ <th scope="col">Name</th>
106
+ <th scope="col">Plans</th>
107
+ <th scope="col">Status</th>
108
+ </tr>
109
+ </thead>
110
+ <tbody>
111
+ <% ungrouped.forEach(function(phase) { %>
112
+ <tr>
113
+ <td><%= String(phase.id).padStart(2, '0') %></td>
114
+ <td>
115
+ <a href="/phases/<%= String(phase.id).padStart(2, '0') %>"
116
+ hx-get="/phases/<%= String(phase.id).padStart(2, '0') %>"
117
+ hx-target="#main-content"
118
+ hx-push-url="true">
119
+ <%= phase.name %>
120
+ </a>
121
+ </td>
122
+ <td><%= phase.planCount %></td>
123
+ <td>
124
+ <span class="status-badge" data-status="<%= phase.status %>">
125
+ <%= phase.status.replace('-', ' ') %>
126
+ </span>
127
+ </td>
128
+ </tr>
129
+ <% }); %>
130
+ </tbody>
131
+ </table>
132
+ </div>
119
133
  </div>
120
- </article>
134
+ </div>
121
135
  <% } %>
122
136
 
123
137
  <% } %>
@@ -2,39 +2,41 @@
2
2
  <h1>Quick Tasks</h1>
3
3
 
4
4
  <% if (typeof tasks !== 'undefined' && tasks.length > 0) { %>
5
- <article>
6
- <div class="table-wrap">
7
- <table>
8
- <thead>
9
- <tr>
10
- <th scope="col">ID</th>
11
- <th scope="col">Title</th>
12
- <th scope="col">Status</th>
13
- </tr>
14
- </thead>
15
- <tbody>
16
- <% tasks.forEach(function(task) { %>
17
- <tr>
18
- <td><%= task.id %></td>
19
- <td>
20
- <a href="/quick/<%= task.id %>"
21
- hx-get="/quick/<%= task.id %>"
22
- hx-target="#main-content"
23
- hx-push-url="true">
24
- <%= task.title %>
25
- </a>
26
- </td>
27
- <td>
28
- <span class="status-badge" data-status="<%= task.status %>">
29
- <%= task.status %>
30
- </span>
31
- </td>
32
- </tr>
33
- <% }); %>
34
- </tbody>
35
- </table>
5
+ <div class="card">
6
+ <div class="card-body p-0">
7
+ <div class="table-responsive">
8
+ <table class="table table-vcenter card-table">
9
+ <thead>
10
+ <tr>
11
+ <th scope="col">ID</th>
12
+ <th scope="col">Title</th>
13
+ <th scope="col">Status</th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <% tasks.forEach(function(task) { %>
18
+ <tr>
19
+ <td><%= task.id %></td>
20
+ <td>
21
+ <a href="/quick/<%= task.id %>"
22
+ hx-get="/quick/<%= task.id %>"
23
+ hx-target="#main-content"
24
+ hx-push-url="true">
25
+ <%= task.title %>
26
+ </a>
27
+ </td>
28
+ <td>
29
+ <span class="status-badge" data-status="<%= task.status %>">
30
+ <%= task.status %>
31
+ </span>
32
+ </td>
33
+ </tr>
34
+ <% }); %>
35
+ </tbody>
36
+ </table>
37
+ </div>
36
38
  </div>
37
- </article>
39
+ </div>
38
40
  <% } else { %>
39
41
  <%- include('empty-state', { icon: '⚡', title: 'No quick tasks found', action: '' }) %>
40
42
  <% } %>
@@ -1,29 +1,30 @@
1
1
  <%- include('breadcrumbs', { breadcrumbs: typeof breadcrumbs !== 'undefined' ? breadcrumbs : [] }) %>
2
2
  <h1><%= title %></h1>
3
3
 
4
- <p><a href="/quick">&larr; Back to Quick Tasks</a></p>
4
+ <p><a href="/quick" class="btn btn-outline-secondary">&larr; Back to Quick Tasks</a></p>
5
5
 
6
- <article>
7
- <header>
6
+ <div class="card mb-3">
7
+ <div class="card-header">
8
8
  <strong>Quick Task <%= id %></strong>
9
9
  &nbsp;
10
10
  <span class="status-badge" data-status="<%= status %>">
11
11
  <%= status %>
12
12
  </span>
13
- </header>
13
+ </div>
14
+ <div class="card-body">
15
+ <% if (planHtml) { %>
16
+ <section>
17
+ <h2>Plan</h2>
18
+ <%- planHtml %>
19
+ </section>
20
+ <% } %>
14
21
 
15
- <% if (planHtml) { %>
16
- <section>
17
- <h2>Plan</h2>
18
- <%- planHtml %>
19
- </section>
20
- <% } %>
21
-
22
- <% if (summaryHtml) { %>
23
- <hr>
24
- <section>
25
- <h2>Summary</h2>
26
- <%- summaryHtml %>
27
- </section>
28
- <% } %>
29
- </article>
22
+ <% if (summaryHtml) { %>
23
+ <hr>
24
+ <section>
25
+ <h2>Summary</h2>
26
+ <%- summaryHtml %>
27
+ </section>
28
+ <% } %>
29
+ </div>
30
+ </div>
@@ -17,19 +17,19 @@
17
17
  <section class="requirements-section">
18
18
  <h2><%= section.sectionTitle %></h2>
19
19
  <% section.requirements.forEach(function(req) { %>
20
- <article class="card requirements-card" data-covered="<%= req.covered %>">
21
- <header class="card__header">
20
+ <div class="card mb-2 requirements-card" data-covered="<%= req.covered %>">
21
+ <div class="card-header">
22
22
  <code class="req-id"><%= req.id %></code>
23
23
  <% if (req.covered) { %>
24
24
  <span class="status-badge" data-status="verified">covered</span>
25
25
  <% } else { %>
26
26
  <span class="status-badge" data-status="warning">uncovered</span>
27
27
  <% } %>
28
- </header>
29
- <div class="card__body">
28
+ </div>
29
+ <div class="card-body">
30
30
  <p><%= req.text %></p>
31
31
  <% if (req.planRefs.length > 0) { %>
32
- <p class="req-plan-refs">
32
+ <p class="req-plan-refs mb-0">
33
33
  <small>Plans: </small>
34
34
  <% req.planRefs.forEach(function(planId) { %>
35
35
  <code class="req-plan-ref"><%= planId %></code>
@@ -37,7 +37,7 @@
37
37
  </p>
38
38
  <% } %>
39
39
  </div>
40
- </article>
40
+ </div>
41
41
  <% }); %>
42
42
  </section>
43
43
  <% }); %>
@@ -6,17 +6,17 @@
6
6
  <%- include('empty-state', { icon: 'R', title: 'No research documents', action: 'Run /pbr:explore or /pbr:scan to generate research.' }) %>
7
7
  <% } else { %>
8
8
  <% researchDocs.forEach(function(doc) { %>
9
- <article class="card">
10
- <header class="card__header">
9
+ <div class="card mb-2">
10
+ <div class="card-header">
11
11
  <strong>
12
12
  <a href="/research/<%= doc.slug %>"
13
13
  hx-get="/research/<%= doc.slug %>"
14
14
  hx-target="#main-content"
15
15
  hx-push-url="true"><%= doc.title %></a>
16
16
  </strong>
17
- <% if (doc.date) { %><small style="float:right"><%= doc.date %></small><% } %>
18
- </header>
19
- <div class="card__body">
17
+ <% if (doc.date) { %><small class="ms-auto text-muted"><%= doc.date %></small><% } %>
18
+ </div>
19
+ <div class="card-body">
20
20
  <% if (doc.topic) { %><p><strong>Topic:</strong> <%= doc.topic %></p><% } %>
21
21
  <% if (doc.confidence || doc.coverage) { %>
22
22
  <p>
@@ -25,10 +25,10 @@
25
25
  </p>
26
26
  <% } %>
27
27
  <% if (!doc.topic && !doc.confidence && !doc.coverage) { %>
28
- <p class="muted"><small>Click to view full document</small></p>
28
+ <p class="text-muted mb-0"><small>Click to view full document</small></p>
29
29
  <% } %>
30
30
  </div>
31
- </article>
31
+ </div>
32
32
  <% }); %>
33
33
  <% } %>
34
34
 
@@ -37,20 +37,20 @@
37
37
  <%- include('empty-state', { icon: 'C', title: 'No codebase documents', action: 'Run /pbr:scan to generate codebase analysis.' }) %>
38
38
  <% } else { %>
39
39
  <% codebaseDocs.forEach(function(doc) { %>
40
- <article class="card">
41
- <header class="card__header">
40
+ <div class="card mb-2">
41
+ <div class="card-header">
42
42
  <strong>
43
43
  <a href="/research/<%= doc.slug %>"
44
44
  hx-get="/research/<%= doc.slug %>"
45
45
  hx-target="#main-content"
46
46
  hx-push-url="true"><%= doc.title %></a>
47
47
  </strong>
48
- <% if (doc.date) { %><small style="float:right"><%= doc.date %></small><% } %>
49
- </header>
50
- <div class="card__body">
48
+ <% if (doc.date) { %><small class="ms-auto text-muted"><%= doc.date %></small><% } %>
49
+ </div>
50
+ <div class="card-body">
51
51
  <% if (doc.focus) { %><p><strong>Focus:</strong> <%= doc.focus %></p><% } %>
52
- <% if (!doc.focus) { %><p class="muted"><small>Click to view full document</small></p><% } %>
52
+ <% if (!doc.focus) { %><p class="text-muted mb-0"><small>Click to view full document</small></p><% } %>
53
53
  </div>
54
- </article>
54
+ </div>
55
55
  <% }); %>
56
56
  <% } %>
@@ -2,22 +2,24 @@
2
2
  <h1><%= title %></h1>
3
3
 
4
4
  <% if (topic || date || confidence || coverage || sources_checked) { %>
5
- <aside class="research-meta">
6
- <% if (topic) { %><p><strong>Topic:</strong> <%= topic %></p><% } %>
7
- <% if (date) { %><p><strong>Date:</strong> <%= date %></p><% } %>
8
- <% if (confidence) { %><p><strong>Confidence:</strong> <span class="status-badge" data-status="<%= confidence %>"><%= confidence %></span></p><% } %>
9
- <% if (coverage) { %><p><strong>Coverage:</strong> <%= coverage %></p><% } %>
10
- <% if (sources_checked) { %><p><strong>Sources checked:</strong> <%= sources_checked %></p><% } %>
11
- </aside>
5
+ <div class="card mb-3">
6
+ <div class="card-body">
7
+ <% if (topic) { %><p><strong>Topic:</strong> <%= topic %></p><% } %>
8
+ <% if (date) { %><p><strong>Date:</strong> <%= date %></p><% } %>
9
+ <% if (confidence) { %><p><strong>Confidence:</strong> <span class="status-badge" data-status="<%= confidence %>"><%= confidence %></span></p><% } %>
10
+ <% if (coverage) { %><p><strong>Coverage:</strong> <%= coverage %></p><% } %>
11
+ <% if (sources_checked) { %><p class="mb-0"><strong>Sources checked:</strong> <%= sources_checked %></p><% } %>
12
+ </div>
13
+ </div>
12
14
  <% } %>
13
15
 
14
- <article class="card">
15
- <div class="card__body markdown-body">
16
+ <div class="card mb-3">
17
+ <div class="card-body markdown-body">
16
18
  <%- html %>
17
19
  </div>
18
- </article>
20
+ </div>
19
21
 
20
- <p><a href="/research"
22
+ <p><a href="/research" class="btn btn-outline-secondary"
21
23
  hx-get="/research"
22
24
  hx-target="#main-content"
23
25
  hx-push-url="true">&larr; Back to Research</a></p>