@sienklogic/plan-build-run 2.26.2 → 2.27.1

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 (56) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +29 -0
  3. package/dashboard/public/css/layout.css +7 -283
  4. package/dashboard/public/css/status-colors.css +7 -0
  5. package/dashboard/public/css/tokens.css +3 -3
  6. package/dashboard/public/js/sidebar-toggle.js +9 -31
  7. package/dashboard/public/js/theme-toggle.js +4 -4
  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 +98 -84
  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 +145 -128
  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/copilot-pbr/skills/build/SKILL.md +12 -0
  45. package/plugins/copilot-pbr/skills/quick/SKILL.md +12 -0
  46. package/plugins/copilot-pbr/skills/review/SKILL.md +14 -0
  47. package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
  48. package/plugins/cursor-pbr/README.md +20 -0
  49. package/plugins/cursor-pbr/skills/build/SKILL.md +12 -0
  50. package/plugins/cursor-pbr/skills/quick/SKILL.md +12 -0
  51. package/plugins/cursor-pbr/skills/review/SKILL.md +14 -0
  52. package/plugins/pbr/.claude-plugin/plugin.json +1 -1
  53. package/plugins/pbr/scripts/local-llm/metrics.js +121 -33
  54. package/plugins/pbr/skills/build/SKILL.md +12 -0
  55. package/plugins/pbr/skills/quick/SKILL.md +12 -0
  56. package/plugins/pbr/skills/review/SKILL.md +14 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to Plan-Build-Run will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.27.1](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.27.0...plan-build-run-v2.27.1) (2026-02-24)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **tools:** prevent lifetime LLM metrics from plateauing at 200 entries ([2cbdaa4](https://github.com/SienkLogic/plan-build-run/commit/2cbdaa4b4402110cc4bb0f6505d034f2162aa782))
14
+
15
+ ## [2.27.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.26.2...plan-build-run-v2.27.0) (2026-02-24)
16
+
17
+
18
+ ### Features
19
+
20
+ * **tools:** add local LLM skill-level fallbacks and platform compatibility docs ([d6d1242](https://github.com/SienkLogic/plan-build-run/commit/d6d1242d79eb924525761e0bc6ac65e1a2d51375))
21
+
8
22
  ## [2.26.2](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.26.1...plan-build-run-v2.26.2) (2026-02-24)
9
23
 
10
24
 
package/README.md CHANGED
@@ -304,6 +304,35 @@ Requires a GPU with 6+ GB VRAM for best performance. CPU-only works but adds lat
304
304
 
305
305
  ---
306
306
 
307
+ ## Platform Compatibility
308
+
309
+ Plan-Build-Run works across three platforms with varying levels of hook support. Hooks are the mechanism that fires validation scripts on every tool call — they power local LLM offloading, commit format enforcement, context budget tracking, and workflow gates.
310
+
311
+ | Feature | Claude Code | Copilot CLI | Cursor IDE |
312
+ |---------|:-----------:|:-----------:|:----------:|
313
+ | Skills (slash commands) | All 26 | All 26 | All 26 |
314
+ | Agents (subagent delegation) | All 12 | All 12 | All 12 |
315
+ | `.planning/` state management | Full | Full | Full |
316
+ | **Hook support** | **Full (14 events)** | **Partial (4 events)** | **Unverified** |
317
+ | Commit format enforcement | Hook-enforced | Hook-enforced | Manual |
318
+ | PLAN/SUMMARY quality classification | Hook + skill fallback | Hook + skill fallback | Skill fallback only |
319
+ | Test failure triage | Automatic (hook) | Automatic (hook) | Not available |
320
+ | Context budget tracking | Automatic (hook) | Not available | Not available |
321
+ | Auto-continue between skills | Automatic (hook) | Not available | Not available |
322
+ | Subagent lifecycle logging | Automatic (hook) | Not available | Not available |
323
+ | **Local LLM offloading** | **Full (8 operations)** | **Mostly (6-7 operations)** | **CLI only** |
324
+ | `pbr-tools.js llm` CLI commands | Full | Full | Full |
325
+
326
+ **Key differences:**
327
+
328
+ - **Claude Code** has full hook support — all local LLM operations fire automatically on every tool call
329
+ - **Copilot CLI** supports `sessionStart`, `preToolUse`, `postToolUse`, and `sessionEnd` — covers most validation hooks but misses lifecycle events (`SubagentStop`, `PreCompact`, `Stop`)
330
+ - **Cursor IDE** hook support is unverified — hooks.json is configured but whether Cursor actually fires them is unknown. Skills include `pbr-tools.js llm` fallback calls for key operations (plan quality, verification quality) so local LLM classification is available even without hooks
331
+
332
+ All platforms share the same scripts via relative paths — no code duplication. See the [Copilot CLI](plugins/copilot-pbr/README.md) and [Cursor IDE](plugins/cursor-pbr/README.md) READMEs for platform-specific details.
333
+
334
+ ---
335
+
307
336
  ## Local Development
308
337
 
309
338
  ```bash
@@ -27,103 +27,6 @@ p { line-height: 1.65; }
27
27
 
28
28
  small { font-size: 0.8125rem; color: var(--color-text-dim); }
29
29
 
30
- /* --- Page Grid Layout --- */
31
- .page-wrapper {
32
- display: grid;
33
- gap: 0;
34
- grid-template-columns: var(--size-sidebar) 1fr;
35
- grid-template-rows: var(--size-header) 1fr auto;
36
- grid-template-areas:
37
- "header header"
38
- "sidebar content"
39
- "footer footer";
40
- min-height: 100vh;
41
- }
42
-
43
- /* --- Header --- */
44
- .page-wrapper > header {
45
- grid-area: header;
46
- display: flex;
47
- align-items: center;
48
- padding: 0 var(--space-xl);
49
- border-bottom: 1px solid var(--color-border);
50
- background: var(--color-surface-raised);
51
- backdrop-filter: blur(8px);
52
- position: sticky;
53
- top: 0;
54
- z-index: 10;
55
- }
56
-
57
- .page-wrapper > header nav {
58
- display: flex;
59
- justify-content: space-between;
60
- align-items: center;
61
- width: 100%;
62
- margin: 0;
63
- padding: 0;
64
- }
65
-
66
- .page-wrapper > header nav strong {
67
- font-size: 1rem;
68
- font-weight: 600;
69
- letter-spacing: -0.02em;
70
- white-space: nowrap;
71
- }
72
-
73
- .page-wrapper > header nav ul {
74
- list-style: none;
75
- margin: 0;
76
- padding: 0;
77
- }
78
-
79
- /* --- Sidebar --- */
80
- .page-wrapper > aside.sidebar {
81
- grid-area: sidebar;
82
- padding: var(--space-lg) 0;
83
- border-right: 1px solid var(--color-border);
84
- background: var(--color-surface-raised);
85
- position: sticky;
86
- top: var(--size-header);
87
- height: calc(100vh - var(--size-header));
88
- overflow-y: auto;
89
- }
90
-
91
- aside.sidebar nav ul {
92
- list-style: none;
93
- padding: 0;
94
- margin: 0;
95
- }
96
-
97
- aside.sidebar nav li {
98
- margin: 0;
99
- padding: 0;
100
- }
101
-
102
- aside.sidebar nav a {
103
- display: flex;
104
- align-items: center;
105
- padding: 0.6rem var(--space-lg);
106
- text-decoration: none;
107
- border-left: 3px solid transparent;
108
- font-size: 0.9rem;
109
- font-weight: 500;
110
- color: var(--color-text-dim);
111
- transition: all var(--transition-fast);
112
- }
113
-
114
- aside.sidebar nav a:hover {
115
- color: var(--pico-color);
116
- background: var(--color-surface-hover);
117
- border-left-color: var(--color-border);
118
- }
119
-
120
- aside.sidebar nav a[aria-current="page"] {
121
- font-weight: 600;
122
- color: var(--color-accent);
123
- border-left-color: var(--color-accent);
124
- background: var(--color-surface-hover);
125
- }
126
-
127
30
  /* --- Sidebar: Current Phase Card --- */
128
31
  .sidebar-current-phase {
129
32
  padding: var(--space-md) var(--space-lg);
@@ -188,59 +91,6 @@ aside.sidebar nav a[aria-current="page"] {
188
91
  white-space: nowrap;
189
92
  }
190
93
 
191
- /* --- Sidebar: Section Details --- */
192
- aside.sidebar details {
193
- border: none;
194
- margin: 0;
195
- padding: 0;
196
- }
197
-
198
- aside.sidebar details + details {
199
- margin-top: var(--space-xs);
200
- }
201
-
202
- aside.sidebar details summary {
203
- text-transform: uppercase;
204
- font-size: 0.6875rem;
205
- font-weight: 600;
206
- letter-spacing: 0.06em;
207
- color: var(--color-text-dim);
208
- padding: var(--space-xs) var(--space-lg);
209
- cursor: pointer;
210
- border-left: 3px solid transparent;
211
- }
212
-
213
- aside.sidebar details[open] summary {
214
- color: var(--color-accent);
215
- border-left-color: var(--color-accent);
216
- }
217
-
218
- aside.sidebar details ul {
219
- margin: 0;
220
- padding-bottom: var(--space-xs);
221
- }
222
-
223
- /* --- Main Content --- */
224
- .page-wrapper > main {
225
- grid-area: content;
226
- padding: var(--space-xl) var(--space-2xl);
227
- overflow-y: auto;
228
- max-width: calc(var(--content-max-width) + var(--space-2xl) * 2);
229
- }
230
-
231
- /* --- Footer --- */
232
- .page-wrapper > footer {
233
- grid-area: footer;
234
- padding: var(--space-md) var(--space-xl);
235
- border-top: 1px solid var(--color-border);
236
- text-align: center;
237
- }
238
-
239
- .page-wrapper > footer small {
240
- font-size: 0.75rem;
241
- color: var(--color-text-dim);
242
- }
243
-
244
94
  /* --- Card Component --- */
245
95
  .card,
246
96
  article {
@@ -423,36 +273,6 @@ details li {
423
273
  margin: var(--space-lg) 0;
424
274
  }
425
275
 
426
- /* --- Breadcrumbs --- */
427
- .breadcrumbs {
428
- display: flex;
429
- list-style: none;
430
- padding: 0;
431
- margin: 0 0 var(--space-md) 0;
432
- font-size: 0.85rem;
433
- gap: var(--space-xs);
434
- }
435
-
436
- .breadcrumbs li + li::before {
437
- content: ">";
438
- margin-right: var(--space-xs);
439
- color: var(--color-text-dim);
440
- }
441
-
442
- .breadcrumbs a {
443
- color: var(--color-accent);
444
- text-decoration: none;
445
- }
446
-
447
- .breadcrumbs a:hover {
448
- text-decoration: underline;
449
- }
450
-
451
- .breadcrumbs [aria-current="page"] {
452
- color: var(--color-text-dim);
453
- font-weight: 500;
454
- }
455
-
456
276
  /* --- Phase Timeline (homepage) --- */
457
277
  .phase-timeline {
458
278
  list-style: none;
@@ -586,8 +406,8 @@ main > p:first-of-type > a[href="/"]:hover {
586
406
  top: 0;
587
407
  z-index: 1000;
588
408
  padding: var(--space-sm) var(--space-md);
589
- background: var(--pico-primary);
590
- color: var(--pico-primary-inverse);
409
+ background: var(--tblr-primary);
410
+ color: var(--tblr-primary-fg, #fff);
591
411
  text-decoration: none;
592
412
  font-weight: 600;
593
413
  }
@@ -600,24 +420,13 @@ main > p:first-of-type > a[href="/"]:hover {
600
420
 
601
421
  /* --- Focus Visible --- */
602
422
  :focus-visible {
603
- outline: 2px solid var(--pico-primary);
423
+ outline: 2px solid var(--tblr-primary);
604
424
  outline-offset: 2px;
605
425
  }
606
426
 
607
- /* --- Empty State --- */
608
- .empty-state {
609
- text-align: center;
610
- padding: var(--space-2xl) var(--space-lg);
611
- }
612
-
613
- .empty-state h3 {
614
- color: var(--color-text-dim);
615
- font-weight: 500;
616
- }
617
-
618
427
  /* --- Error Card --- */
619
428
  .error-card {
620
- border-left: 4px solid var(--pico-del-color);
429
+ border-left: 4px solid var(--tblr-danger);
621
430
  padding: var(--space-lg);
622
431
  margin: var(--space-lg) 0;
623
432
  background: var(--color-surface-raised);
@@ -659,7 +468,7 @@ main > p:first-of-type > a[href="/"]:hover {
659
468
  /* --- Bar Chart --- */
660
469
  .bar-chart-row { display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-xs); }
661
470
  .bar-chart-label { min-width: 160px; font-size: 0.875rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
662
- .bar-chart-bar { height: 1.5rem; border-radius: var(--radius-sm); background: var(--pico-primary); color: var(--pico-primary-inverse, #fff); font-size: 0.75rem; display: flex; align-items: center; padding-left: var(--space-sm); min-width: 2rem; transition: width var(--transition-base); }
471
+ .bar-chart-bar { height: 1.5rem; border-radius: var(--radius-sm); background: var(--tblr-primary); color: var(--tblr-primary-fg, #fff); font-size: 0.75rem; display: flex; align-items: center; padding-left: var(--space-sm); min-width: 2rem; transition: width var(--transition-base); }
663
472
 
664
473
  /* --- Loading Bar --- */
665
474
  .loading-bar {
@@ -674,7 +483,7 @@ main > p:first-of-type > a[href="/"]:hover {
674
483
 
675
484
  .htmx-request .loading-bar {
676
485
  display: block;
677
- background: var(--pico-primary);
486
+ background: var(--tblr-primary);
678
487
  animation: loading-shimmer 1.2s ease-in-out infinite;
679
488
  }
680
489
 
@@ -683,93 +492,12 @@ main > p:first-of-type > a[href="/"]:hover {
683
492
  100% { transform: translateX(100%); }
684
493
  }
685
494
 
686
- /* --- Sidebar Backdrop (hidden by default, shown on mobile) --- */
687
- .sidebar-backdrop {
688
- display: none;
689
- }
690
-
691
- /* --- Mobile hamburger toggle --- */
692
- .sidebar-toggle {
693
- display: none;
694
- background: none;
695
- border: 1px solid var(--color-border);
696
- border-radius: var(--radius-sm);
697
- color: var(--pico-color);
698
- font-size: 1.25rem;
699
- padding: 0.25rem 0.5rem;
700
- cursor: pointer;
701
- line-height: 1;
702
- }
703
-
704
495
  /* ============================================
705
496
  Responsive Breakpoints
706
497
  ============================================ */
707
498
 
708
- /* Tablet: narrower sidebar */
709
- @media (max-width: 1024px) {
710
- :root {
711
- --size-sidebar: 180px;
712
- }
713
-
714
- .page-wrapper > main {
715
- padding: var(--space-lg) var(--space-xl);
716
- }
717
- }
718
-
719
- /* Mobile: sidebar overlays content */
499
+ /* Mobile: compact typography */
720
500
  @media (max-width: 768px) {
721
- .page-wrapper {
722
- grid-template-columns: 1fr;
723
- grid-template-rows: var(--size-header) 1fr auto;
724
- grid-template-areas:
725
- "header"
726
- "content"
727
- "footer";
728
- }
729
-
730
- .sidebar-toggle {
731
- display: block;
732
- }
733
-
734
- .page-wrapper > aside.sidebar {
735
- position: fixed;
736
- top: var(--size-header);
737
- left: 0;
738
- width: 260px;
739
- height: calc(100vh - var(--size-header));
740
- z-index: 20;
741
- transform: translateX(-100%);
742
- transition: var(--transition-transform);
743
- display: block;
744
- border-right: 1px solid var(--color-border);
745
- border-bottom: none;
746
- padding: var(--space-lg) 0;
747
- overflow-y: auto;
748
- background: var(--pico-background-color, var(--color-dark-surface));
749
- }
750
-
751
- .page-wrapper > aside.sidebar.open {
752
- transform: translateX(0);
753
- box-shadow: 4px 0 24px var(--overlay-bg);
754
- }
755
-
756
- .sidebar-backdrop {
757
- display: none;
758
- position: fixed;
759
- inset: 0;
760
- top: var(--size-header);
761
- background: var(--overlay-bg-heavy);
762
- z-index: 19;
763
- }
764
-
765
- .sidebar-backdrop.open {
766
- display: block;
767
- }
768
-
769
- .page-wrapper > main {
770
- padding: var(--space-lg) var(--space-md);
771
- }
772
-
773
501
  h1 { font-size: 1.4rem; }
774
502
  h2 { font-size: 1.15rem; }
775
503
 
@@ -810,10 +538,6 @@ main > p:first-of-type > a[href="/"]:hover {
810
538
  font-size: 14px;
811
539
  }
812
540
 
813
- .page-wrapper > main {
814
- padding: var(--space-md);
815
- }
816
-
817
541
  article > header {
818
542
  padding: var(--space-sm) var(--space-md);
819
543
  }
@@ -97,6 +97,13 @@
97
97
  border-color: rgba(129, 140, 248, 0.2);
98
98
  }
99
99
 
100
+ /* Tabler .badge compatibility shim */
101
+ .badge.status-badge {
102
+ font-size: var(--badge-font-size-base);
103
+ font-weight: 600;
104
+ letter-spacing: 0.02em;
105
+ }
106
+
100
107
  /* Size variants */
101
108
  .status-badge--sm {
102
109
  padding: var(--badge-padding-sm);
@@ -13,7 +13,7 @@
13
13
  --color-border: rgba(0, 0, 0, 0.1);
14
14
  --color-text-dim: rgba(0, 0, 0, 0.5);
15
15
  --color-text: #1a1a2e;
16
- --color-accent: var(--pico-primary);
16
+ --color-accent: var(--tblr-primary, #0054a6);
17
17
 
18
18
  /* Spacing */
19
19
  --space-xs: 0.25rem;
@@ -73,7 +73,7 @@
73
73
  }
74
74
 
75
75
  /* --- Dark Mode (explicit attribute) --- */
76
- [data-theme="dark"] {
76
+ [data-bs-theme="dark"] {
77
77
  --color-surface: #13131a;
78
78
  --color-surface-raised: rgba(255, 255, 255, 0.03);
79
79
  --color-surface-hover: rgba(255, 255, 255, 0.06);
@@ -84,7 +84,7 @@
84
84
 
85
85
  /* --- Dark Mode (system preference, unless light forced) --- */
86
86
  @media (prefers-color-scheme: dark) {
87
- :root:not([data-theme="light"]) {
87
+ :root:not([data-bs-theme="light"]) {
88
88
  --color-surface: #13131a;
89
89
  --color-surface-raised: rgba(255, 255, 255, 0.03);
90
90
  --color-surface-hover: rgba(255, 255, 255, 0.06);
@@ -1,34 +1,12 @@
1
- // Mobile sidebar toggle with backdrop overlay
1
+ // sidebar-toggle.js Tabler vertical navbar toggle shim
2
+ // Tabler's Bootstrap JS handles collapse via data-bs-toggle="collapse".
3
+ // This file remains for the header button (#sidebar-toggle) which triggers
4
+ // the .navbar-vertical collapse on mobile.
2
5
  document.addEventListener('DOMContentLoaded', function() {
3
- var toggle = document.querySelector('.sidebar-toggle');
4
- var sidebar = document.querySelector('.sidebar');
5
- if (!toggle || !sidebar) return;
6
-
7
- // Create backdrop element
8
- var backdrop = document.createElement('div');
9
- backdrop.className = 'sidebar-backdrop';
10
- document.body.appendChild(backdrop);
11
-
12
- function closeSidebar() {
13
- sidebar.classList.remove('open');
14
- backdrop.classList.remove('open');
15
- }
16
-
17
- function toggleSidebar() {
18
- sidebar.classList.toggle('open');
19
- backdrop.classList.toggle('open');
20
- }
21
-
22
- toggle.addEventListener('click', toggleSidebar);
23
- backdrop.addEventListener('click', closeSidebar);
24
-
25
- // Close sidebar when a nav link is clicked (mobile)
26
- var navLinks = sidebar.querySelectorAll('a');
27
- navLinks.forEach(function(link) {
28
- link.addEventListener('click', function() {
29
- if (window.innerWidth <= 768) {
30
- closeSidebar();
31
- }
32
- });
6
+ var toggle = document.getElementById('sidebar-toggle');
7
+ var sidebarMenu = document.getElementById('sidebar-menu');
8
+ if (!toggle || !sidebarMenu) return;
9
+ toggle.addEventListener('click', function() {
10
+ sidebarMenu.classList.toggle('show');
33
11
  });
34
12
  });
@@ -1,11 +1,11 @@
1
- /* theme-toggle.js — Toggle light/dark theme via data-theme attribute + localStorage */
1
+ /* theme-toggle.js — Toggle light/dark theme via data-bs-theme attribute + localStorage */
2
2
  (function () {
3
3
  'use strict';
4
4
 
5
5
  var STORAGE_KEY = 'pbr-theme';
6
6
 
7
7
  function getEffectiveTheme() {
8
- var explicit = document.documentElement.dataset.theme;
8
+ var explicit = document.documentElement.dataset.bsTheme;
9
9
  if (explicit === 'light' || explicit === 'dark') return explicit;
10
10
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
11
11
  }
@@ -23,7 +23,7 @@
23
23
  // Apply stored theme (also done in layout-top inline script for flash prevention)
24
24
  var stored = localStorage.getItem(STORAGE_KEY);
25
25
  if (stored) {
26
- document.documentElement.dataset.theme = stored;
26
+ document.documentElement.dataset.bsTheme = stored;
27
27
  }
28
28
 
29
29
  updateIcon(btn, getEffectiveTheme());
@@ -31,7 +31,7 @@
31
31
  btn.addEventListener('click', function () {
32
32
  var current = getEffectiveTheme();
33
33
  var next = current === 'dark' ? 'light' : 'dark';
34
- document.documentElement.dataset.theme = next;
34
+ document.documentElement.dataset.bsTheme = next;
35
35
  localStorage.setItem(STORAGE_KEY, next);
36
36
  updateIcon(btn, next);
37
37
  });
@@ -1,19 +1,27 @@
1
1
  <% if (!recentActivity || recentActivity.length === 0) { %>
2
- <p class="muted">No recent activity.</p>
2
+ <%- include('empty-state', { title: 'No recent activity' }) %>
3
3
  <% } else { %>
4
- <ul class="activity-feed">
4
+ <div class="list-group list-group-flush">
5
5
  <% recentActivity.forEach(function(item) { %>
6
6
  <%
7
7
  // Format path: strip .planning/ prefix for brevity
8
- var displayPath = item.path.replace(/^\.planning\//, '');
8
+ var displayPath = (item.path || item.description || String(item)).replace(/^\.planning\//, '');
9
9
  // Format timestamp: show just date+time, no timezone
10
- var ts = item.timestamp || '';
10
+ var ts = item.timestamp || item.time || item.date || '';
11
11
  var dateOnly = ts.replace(/\s+[-+]\d{4}$/, '').replace(/:\d{2}$/, '');
12
12
  %>
13
- <li class="activity-item">
14
- <span class="activity-path"><%= displayPath %></span>
15
- <time class="activity-time" datetime="<%= ts %>"><%= dateOnly %></time>
16
- </li>
13
+ <div class="list-group-item">
14
+ <div class="row align-items-center">
15
+ <div class="col text-truncate">
16
+ <span class="text-truncate small"><%= displayPath %></span>
17
+ </div>
18
+ <% if (dateOnly) { %>
19
+ <div class="col-auto">
20
+ <time class="text-muted small" datetime="<%= ts %>"><%= dateOnly %></time>
21
+ </div>
22
+ <% } %>
23
+ </div>
24
+ </div>
17
25
  <% }); %>
18
- </ul>
26
+ </div>
19
27
  <% } %>