@hanzlaa/rcode 4.6.0 → 4.7.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 (29) hide show
  1. package/README.md +47 -14
  2. package/package.json +1 -1
  3. package/rcode/bin/rcode-tools.cjs +1 -0
  4. package/rcode/references/agent-shared-rules.md +6 -0
  5. package/rcode/references/executor-playbook.md +1 -1
  6. package/rcode/references/planner-playbook.md +1 -0
  7. package/rcode/workflows/init.md +13 -0
  8. package/rcode/workflows/new-project-create-roadmap.md +18 -28
  9. package/server/lib/html/client/components/App.js +15 -1
  10. package/server/lib/html/client/components/PhaseGraph.js +20 -12
  11. package/server/lib/html/client/components/Sidebar.js +1 -0
  12. package/server/lib/html/client/components/Topbar.js +3 -3
  13. package/server/lib/html/client/components/dashboard/Blockers.js +3 -3
  14. package/server/lib/html/client/components/dashboard/CompletedTasks.js +4 -2
  15. package/server/lib/html/client/components/dashboard/InProgress.js +4 -3
  16. package/server/lib/html/client/components/dashboard/ProgressTimeline.js +11 -6
  17. package/server/lib/html/client/components/dashboard/RecentDecisions.js +4 -3
  18. package/server/lib/html/client/components/shared.js +111 -2
  19. package/server/lib/html/client/store.js +47 -0
  20. package/server/lib/html/client/util.js +1 -22
  21. package/server/lib/html/client/views/BacklogView.js +44 -0
  22. package/server/lib/html/client/views/DecisionsView.js +4 -3
  23. package/server/lib/html/client/views/FilesView.js +47 -34
  24. package/server/lib/html/client/views/KanbanView.js +8 -0
  25. package/server/lib/html/client/views/PhasesView.js +8 -3
  26. package/server/lib/html/client/views/SprintsView.js +9 -0
  27. package/server/lib/html/client.js +1 -0
  28. package/server/lib/html/css.js +96 -19
  29. package/server/lib/scanner.js +21 -6
@@ -997,6 +997,7 @@ section .body {
997
997
  .inline-file-entry {
998
998
  display: flex;
999
999
  align-items: center;
1000
+ gap: var(--space-2);
1000
1001
  padding: var(--space-2) var(--space-4);
1001
1002
  font-family: var(--font-mono);
1002
1003
  font-size: var(--text-xs);
@@ -1009,8 +1010,49 @@ section .body {
1009
1010
  .inline-file-entry:last-child { border-bottom: none; }
1010
1011
  .inline-file-entry:hover { background: var(--bg-hover); color: var(--text-primary); }
1011
1012
  .inline-file-entry.selected { background: rgba(34,182,207,0.1); color: var(--accent-primary); }
1013
+ .inline-file-entry svg { flex-shrink: 0; opacity: 0.7; }
1014
+ .inline-file-label {
1015
+ overflow: hidden;
1016
+ text-overflow: ellipsis;
1017
+ white-space: nowrap;
1018
+ }
1012
1019
  .inline-file-group { margin-bottom: var(--space-3); }
1013
- .inline-subgroup { padding-left: var(--space-4); }
1020
+ .inline-file-group-title {
1021
+ display: flex;
1022
+ align-items: center;
1023
+ gap: var(--space-2);
1024
+ font-size: var(--text-xs);
1025
+ font-weight: 600;
1026
+ color: var(--text-muted);
1027
+ text-transform: uppercase;
1028
+ letter-spacing: 0.07em;
1029
+ padding: var(--space-1) var(--space-3);
1030
+ }
1031
+ .inline-file-group-count {
1032
+ font-size: var(--text-2xs);
1033
+ font-weight: 500;
1034
+ color: var(--text-muted);
1035
+ text-transform: none;
1036
+ letter-spacing: 0;
1037
+ }
1038
+ .inline-subgroup { padding-left: var(--space-4); margin-bottom: var(--space-1); }
1039
+ .inline-subgroup-summary {
1040
+ display: flex;
1041
+ align-items: center;
1042
+ gap: var(--space-2);
1043
+ font-size: var(--text-xs);
1044
+ font-weight: 500;
1045
+ color: var(--text-secondary);
1046
+ cursor: pointer;
1047
+ padding: var(--space-1) var(--space-3);
1048
+ user-select: none;
1049
+ }
1050
+ .inline-file-total {
1051
+ margin-left: var(--space-2);
1052
+ font-size: var(--text-sm);
1053
+ font-weight: 400;
1054
+ color: var(--text-muted);
1055
+ }
1014
1056
  .file-path-header {
1015
1057
  font-size: var(--text-2xs);
1016
1058
  color: var(--text-muted);
@@ -2254,6 +2296,27 @@ footer {
2254
2296
  color var(--t-fast) var(--ease),
2255
2297
  border-color var(--t-fast) var(--ease);
2256
2298
  }
2299
+ .card-file-btn {
2300
+ float: right;
2301
+ margin-left: var(--space-3);
2302
+ height: 20px;
2303
+ padding: 0 var(--space-3);
2304
+ background: transparent;
2305
+ border: 1px solid var(--border-default);
2306
+ border-radius: var(--radius-2);
2307
+ color: var(--text-tertiary);
2308
+ font-size: 10px;
2309
+ font-weight: 500;
2310
+ cursor: pointer;
2311
+ transition: background var(--t-fast) var(--ease),
2312
+ color var(--t-fast) var(--ease),
2313
+ border-color var(--t-fast) var(--ease);
2314
+ }
2315
+ .card-file-btn:hover {
2316
+ background: var(--accent-blue);
2317
+ border-color: var(--accent-blue);
2318
+ color: #fff;
2319
+ }
2257
2320
  .card-run-btn:hover {
2258
2321
  background: var(--accent-green);
2259
2322
  border-color: var(--accent-green);
@@ -2879,7 +2942,6 @@ footer {
2879
2942
  }
2880
2943
  .tb-btn--primary:hover { filter: brightness(1.05); border-color: transparent; }
2881
2944
  .tb-btn--icon { padding: 8px 11px; }
2882
- .tb-kebab { font-size: 16px; line-height: 1; letter-spacing: 1px; }
2883
2945
 
2884
2946
  @media (max-width: 760px) {
2885
2947
  .tb-sub { display: none; }
@@ -3718,7 +3780,6 @@ footer {
3718
3780
  }
3719
3781
  .tb-btn--primary:hover { filter: brightness(1.05); border-color: transparent; }
3720
3782
  .tb-btn--icon { padding: 8px 11px; }
3721
- .tb-kebab { font-size: 16px; line-height: 1; letter-spacing: 1px; }
3722
3783
 
3723
3784
  @media (max-width: 760px) {
3724
3785
  .tb-sub { display: none; }
@@ -5436,21 +5497,32 @@ summary:focus-visible,
5436
5497
  .pg-tip text { font-size: var(--text-2xs); fill: var(--text-secondary); pointer-events: none; }
5437
5498
  .pg-tip .pg-tip-title { fill: var(--text-primary); font-weight: 600; }
5438
5499
 
5439
- /* Flow-row mode — no cross-phase dependencies: wrapped chip sequence. */
5440
- .pg-flow {
5500
+ /* Sequence mode — no cross-phase dependencies: a connected chain of small
5501
+ pills in roadmap order (arrows imply order, not a graph edge). */
5502
+ .pg-hint-top {
5503
+ padding-top: var(--space-2);
5504
+ padding-bottom: var(--space-3);
5505
+ }
5506
+ .pg-seq {
5441
5507
  display: flex;
5442
5508
  flex-wrap: wrap;
5443
- gap: var(--space-3);
5444
- padding: 0 var(--space-5) var(--space-2);
5509
+ align-items: center;
5510
+ row-gap: var(--space-2);
5511
+ padding: 0 var(--space-5) var(--space-4);
5445
5512
  }
5446
- .pg-chip {
5513
+ .pg-seq-item {
5447
5514
  display: inline-flex;
5448
5515
  align-items: center;
5449
- gap: var(--space-2);
5450
- max-width: 220px;
5451
- padding: var(--space-2) var(--space-3);
5452
- border: 1.5px solid var(--pg-todo);
5453
- border-radius: var(--radius-3);
5516
+ }
5517
+ .pg-seq-chip {
5518
+ display: inline-flex;
5519
+ align-items: center;
5520
+ gap: 6px;
5521
+ max-width: 190px;
5522
+ padding: 3px var(--space-3);
5523
+ border: 1px solid var(--border-default);
5524
+ border-left: 3px solid var(--pg-todo);
5525
+ border-radius: var(--radius-full, 999px);
5454
5526
  background: var(--bg-elev-2);
5455
5527
  color: var(--text-secondary);
5456
5528
  font-size: var(--text-2xs);
@@ -5458,20 +5530,25 @@ summary:focus-visible,
5458
5530
  cursor: pointer;
5459
5531
  transition: background var(--t-fast) var(--ease);
5460
5532
  }
5461
- .pg-chip:hover { background: var(--bg-hover); }
5462
- .pg-chip.pg-done { border-color: var(--pg-done); }
5463
- .pg-chip.pg-active { border-color: var(--pg-active); animation: pg-chip-pulse 2s var(--ease) infinite; }
5464
- .pg-chip.pg-blocked { border-color: var(--pg-blocked); }
5533
+ .pg-seq-chip:hover { background: var(--bg-hover); }
5534
+ .pg-seq-chip.pg-done { border-left-color: var(--pg-done); }
5535
+ .pg-seq-chip.pg-active { border-left-color: var(--pg-active); animation: pg-chip-pulse 2s var(--ease) infinite; }
5536
+ .pg-seq-chip.pg-blocked { border-left-color: var(--pg-blocked); }
5465
5537
  @keyframes pg-chip-pulse {
5466
5538
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
5467
5539
  50% { box-shadow: 0 0 6px 0 var(--pg-active); }
5468
5540
  }
5469
- .pg-chip-id { font-weight: 600; color: var(--text-primary); }
5470
- .pg-chip-name {
5541
+ .pg-seq-id { font-weight: 600; color: var(--text-primary); }
5542
+ .pg-seq-name {
5471
5543
  overflow: hidden;
5472
5544
  text-overflow: ellipsis;
5473
5545
  white-space: nowrap;
5474
5546
  }
5547
+ .pg-seq-arrow {
5548
+ padding: 0 var(--space-2);
5549
+ color: var(--text-muted);
5550
+ font-size: var(--text-sm);
5551
+ }
5475
5552
  .pg-hint, .pg-empty {
5476
5553
  padding: 0 var(--space-5) var(--space-3);
5477
5554
  font-size: var(--text-2xs);
@@ -195,7 +195,13 @@ function buildPhaseTree(projectDir, rawPhases, listCached, overrides) {
195
195
  : (p.status === 'active' || p.status === 'in_progress') ? 'in_progress'
196
196
  : 'planned';
197
197
 
198
- return { id: sid, number: num, goal: goal || `Sprint ${num}`, status, stories, dependsOn };
198
+ // Project-relative path to this sprint's SPRINT.md same shape as the
199
+ // sprintFile computed below for roadmap cards, so /api/file?path=...
200
+ // resolves it identically. Lets task rows (which point at a specific
201
+ // sprint, not just "latest file in the phase") open the right file.
202
+ const file = path.join('.planning', 'phases', dir.name, f).split(path.sep).join('/');
203
+
204
+ return { id: sid, number: num, goal: goal || `Sprint ${num}`, status, stories, dependsOn, file };
199
205
  });
200
206
 
201
207
  // Derive phase-level depends_on by aggregating sprint-level depends_on entries.
@@ -366,11 +372,15 @@ function buildDashboard(state) {
366
372
  const stories = Array.isArray(s.stories) ? s.stories : [];
367
373
  for (const st of stories) {
368
374
  if (/done|complete/i.test(st.status || '')) {
369
- completedTasks.push({ title: st.title || st.id, date: fmtISODate(p.completed || s.completed_at || p.created) });
375
+ completedTasks.push({
376
+ title: st.title || st.id,
377
+ date: fmtISODate(p.completed || s.completed_at || p.created),
378
+ file: s.file || null,
379
+ });
370
380
  } else if (p.state === 'active') {
371
381
  // No per-task progress tracking exists — pct stays null and the UI
372
382
  // omits the percent pill rather than inventing a number.
373
- inProgressTasks.push({ title: st.title || st.id, pct: null });
383
+ inProgressTasks.push({ title: st.title || st.id, pct: null, file: s.file || null });
374
384
  }
375
385
  }
376
386
  }
@@ -378,10 +388,10 @@ function buildDashboard(state) {
378
388
  // Fallbacks so the cards are never empty when stories are unregistered in state.json.
379
389
  if (!completedTasks.length) {
380
390
  phases.filter(p => p.state === 'done').slice(-6).forEach(p =>
381
- completedTasks.push({ title: p.name, date: fmtISODate(p.completed || p.created) }));
391
+ completedTasks.push({ title: p.name, date: fmtISODate(p.completed || p.created), file: null }));
382
392
  }
383
393
  if (!inProgressTasks.length && activePhase && activePhase.state === 'active') {
384
- inProgressTasks.push({ title: activePhase.name, pct: null });
394
+ inProgressTasks.push({ title: activePhase.name, pct: null, file: null });
385
395
  }
386
396
  const tasks = {
387
397
  completed: completedTasks.slice(-8).reverse(),
@@ -419,6 +429,11 @@ function buildDashboard(state) {
419
429
  .sort((a, b) => String(b.date).localeCompare(String(a.date)))
420
430
  .slice(0, 8);
421
431
 
432
+ // ---- backlog (phases not yet started — real state.json data, no invented rows) ----
433
+ const backlog = phases
434
+ .filter(p => p.state === 'todo')
435
+ .map(p => ({ id: p.id, name: p.name, range: p.range }));
436
+
422
437
  // ---- project (identity + current user) ----
423
438
  // User comes from config, else the real OS account running the server;
424
439
  // null when neither exists (UI greets generically, hides the profile row).
@@ -430,7 +445,7 @@ function buildDashboard(state) {
430
445
  user: { name: userName, email: cfg.user_email || '' },
431
446
  };
432
447
 
433
- return { project, progress, currentPhase, timeline: { launchDate, onTrack: blockers.length === 0, points }, tasks, blockers, health, decisions, phases };
448
+ return { project, progress, currentPhase, timeline: { launchDate, onTrack: blockers.length === 0, points }, tasks, blockers, health, decisions, phases, backlog };
434
449
  }
435
450
 
436
451
  function scanStateUncached(rcodeDir) {