@hanzlaa/rcode 4.5.0 → 4.7.0

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 (38) hide show
  1. package/README.md +47 -14
  2. package/package.json +1 -1
  3. package/rcode/agents/rcode-mariam.md +6 -0
  4. package/rcode/agents/rcode-sadiq.md +6 -0
  5. package/rcode/agents/rcode-waleed.md +6 -0
  6. package/rcode/bin/rcode-hooks.cjs +41 -5
  7. package/rcode/skills/agents/mariam-marketing/SKILL.md +1 -0
  8. package/rcode/skills/agents/sadiq-analyst/SKILL.md +1 -0
  9. package/rcode/skills/agents/waleed-architect/SKILL.md +1 -0
  10. package/rcode/workflows/council.md +29 -1
  11. package/server/dashboard.js +6 -3
  12. package/server/lib/html/client/components/App.js +16 -2
  13. package/server/lib/html/client/components/OrchPanel.js +2 -2
  14. package/server/lib/html/client/components/PhaseGraph.js +20 -12
  15. package/server/lib/html/client/components/Sidebar.js +1 -0
  16. package/server/lib/html/client/components/Topbar.js +3 -3
  17. package/server/lib/html/client/components/XtermPanel.js +98 -23
  18. package/server/lib/html/client/components/dashboard/Blockers.js +3 -3
  19. package/server/lib/html/client/components/dashboard/CompletedTasks.js +4 -2
  20. package/server/lib/html/client/components/dashboard/InProgress.js +4 -3
  21. package/server/lib/html/client/components/dashboard/ProgressTimeline.js +11 -6
  22. package/server/lib/html/client/components/dashboard/RecentDecisions.js +4 -3
  23. package/server/lib/html/client/components/shared.js +111 -2
  24. package/server/lib/html/client/orchestrator.js +28 -15
  25. package/server/lib/html/client/store.js +47 -0
  26. package/server/lib/html/client/util.js +1 -22
  27. package/server/lib/html/client/views/BacklogView.js +44 -0
  28. package/server/lib/html/client/views/DecisionsView.js +4 -3
  29. package/server/lib/html/client/views/FilesView.js +47 -34
  30. package/server/lib/html/client/views/KanbanView.js +8 -0
  31. package/server/lib/html/client/views/OrchestrationView.js +247 -169
  32. package/server/lib/html/client/views/PhasesView.js +8 -3
  33. package/server/lib/html/client/views/SprintsView.js +9 -0
  34. package/server/lib/html/client.js +1 -0
  35. package/server/lib/html/css.js +692 -246
  36. package/server/lib/html/shell.js +9 -3
  37. package/server/lib/scanner.js +21 -6
  38. package/server/orchestrator.js +3 -4
@@ -75,7 +75,7 @@ export function allSprints(phases) {
75
75
  export function allTasks(phases) {
76
76
  return (phases || []).flatMap(p =>
77
77
  (p.sprints || []).flatMap(s =>
78
- (s.stories || []).map(t => Object.assign({}, t, { sprintId: s.id, phaseId: p.id, phaseName: p.name }))
78
+ (s.stories || []).map(t => Object.assign({}, t, { sprintId: s.id, phaseId: p.id, phaseName: p.name, file: s.file || null }))
79
79
  )
80
80
  );
81
81
  }
@@ -139,27 +139,6 @@ export function sessionChip(status) {
139
139
  return { cls, label: status };
140
140
  }
141
141
 
142
- /**
143
- * Props for a clickable card row that navigates to a hash route.
144
- * Spread onto a list row (`<li ...${rowLink('tasks')}>`) to make it act like
145
- * a link: pointer + keyboard activation and an accessible role. Pair with the
146
- * `ovr-link` class for the hover/focus affordance.
147
- *
148
- * @param {string} hash — target hash route without the leading '#'.
149
- * @returns {object} Preact props (role, tabindex, onClick, onKeyDown).
150
- */
151
- export function rowLink(hash) {
152
- const go = () => { location.hash = hash; };
153
- return {
154
- role: 'link',
155
- tabindex: 0,
156
- onClick: go,
157
- onKeyDown: (e) => {
158
- if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); go(); }
159
- },
160
- };
161
- }
162
-
163
142
  /**
164
143
  * Human-readable elapsed time since an ISO timestamp.
165
144
  * Ported from _orchElapsed() in client-main.js.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * BacklogView — Preact component.
3
+ *
4
+ * Lists phases that haven't started yet (state.dashboard.backlog, derived
5
+ * server-side in scanner.js from phases with state === 'todo'). Each row
6
+ * links to that phase's detail page — same navigation PhaseCard/SprintCard
7
+ * already use elsewhere, no new routing concept.
8
+ */
9
+
10
+ import { html } from '../preact.js';
11
+ import { useStore } from '../store.js';
12
+ import { pressable } from '../components/shared.js';
13
+ import { Icon } from '../icons-client.js';
14
+
15
+ export function BacklogView() {
16
+ const S = useStore();
17
+ const backlog = S.backlog || [];
18
+
19
+ return html`
20
+ <div id="view-backlog" class="view active">
21
+ <div class="view-title">Backlog</div>
22
+ ${backlog.length === 0
23
+ ? html`
24
+ <div class="empty">
25
+ No phases waiting in the backlog.
26
+ <div class="empty-action">Run /rcode-add-phase to queue up new work</div>
27
+ </div>
28
+ `
29
+ : html`
30
+ <div class="phase-list">
31
+ ${backlog.map(p => html`
32
+ <div key=${p.id} class="item item-clickable"
33
+ ...${pressable(() => { location.hash = 'phases/' + p.id; })}>
34
+ <div class="item-title">
35
+ <${Icon} name="clipboard-list" size=${16}/> Phase ${p.id} — ${p.name}
36
+ </div>
37
+ ${p.range ? html`<div class="item-meta">${p.range}</div>` : null}
38
+ </div>
39
+ `)}
40
+ </div>
41
+ `}
42
+ </div>
43
+ `;
44
+ }
@@ -7,9 +7,9 @@
7
7
  */
8
8
 
9
9
  import { html, useState } from '../preact.js';
10
- import { useStore } from '../store.js';
10
+ import { useStore, openDecisionViewer } from '../store.js';
11
11
  import { humanDate } from '../util.js';
12
- import { CmdHint, CmdHints, showToast } from '../components/shared.js';
12
+ import { CmdHint, CmdHints, showToast, pressable } from '../components/shared.js';
13
13
 
14
14
  const CMD_HINTS = [
15
15
  ['/rcode-council', 'Convene the council for a new decision'],
@@ -83,8 +83,9 @@ export function DecisionsView() {
83
83
  const rationale = (typeof d === 'object' && d.rationale)
84
84
  ? html`<div style="color:var(--text-secondary);font-size:var(--text-sm);margin-top:4px;">${d.rationale}</div>`
85
85
  : null;
86
+ const decisionObj = typeof d === 'object' ? d : { title: d };
86
87
  return html`
87
- <div key=${i} class="item">
88
+ <div key=${i} class="item item-clickable" ...${pressable(() => openDecisionViewer(decisionObj))}>
88
89
  <div class="item-title">${title}${dateInfo}</div>
89
90
  <div class="item-meta">${phaseInfo}</div>
90
91
  ${rationale}
@@ -10,20 +10,27 @@
10
10
  * clears the field so subsequent renders don't re-trigger.
11
11
  */
12
12
 
13
- import { html, useState, useEffect } from '../preact.js';
13
+ import { html, useState, useEffect, useMemo } from '../preact.js';
14
14
  import { useStore, setState } from '../store.js';
15
15
  import { FileReader } from '../components/FileReader.js';
16
+ import { Icon } from '../icons-client.js';
17
+
18
+ // File-type icon — markdown gets its own glyph, everything else a plain file.
19
+ function fileIcon(label) {
20
+ return /\.md$/i.test(label || '') ? 'file-text' : 'file';
21
+ }
16
22
 
17
23
  // ---- File tree components ----
18
- function FileEntry({ file, extraText, onSelect, isSelected }) {
24
+ function FileEntry({ file, onSelect, isSelected }) {
19
25
  return html`
20
26
  <div
21
- class=${'item item-clickable inline-file-entry' + (isSelected ? ' selected' : '')}
27
+ class=${'inline-file-entry' + (isSelected ? ' selected' : '')}
22
28
  data-path=${file.path}
29
+ title=${file.path}
23
30
  onClick=${() => onSelect(file)}
24
- style="padding:var(--space-2) var(--space-3);font-family:'SF Mono',Monaco,Consolas,monospace;font-size:var(--text-xs);"
25
31
  >
26
- ${file.label}
32
+ <${Icon} name=${fileIcon(file.label)} size=${13}/>
33
+ <span class="inline-file-label">${file.label}</span>
27
34
  </div>
28
35
  `;
29
36
  }
@@ -36,31 +43,31 @@ function FileGroup({ group, onSelect, selectedPath, filter }) {
36
43
  }
37
44
 
38
45
  if (group.subGroups) {
46
+ const subGroupsVisible = group.subGroups
47
+ .map(sg => ({ sg, visible: sg.files.filter(f => matchesFilter(f, sg.subGroup)) }))
48
+ .filter(x => x.visible.length);
49
+ if (!subGroupsVisible.length) return null;
50
+ const total = subGroupsVisible.reduce((n, x) => n + x.visible.length, 0);
39
51
  return html`
40
- <div class="inline-file-group" style="margin-bottom:var(--space-3);">
41
- <div style="font-size:var(--text-xs);font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.07em;padding:var(--space-1) var(--space-3);">
42
- ${group.group}
52
+ <div class="inline-file-group">
53
+ <div class="inline-file-group-title">
54
+ ${group.group} <span class="inline-file-group-count">${total}</span>
43
55
  </div>
44
- ${group.subGroups.map(sg => {
45
- const visible = sg.files.filter(f => matchesFilter(f, sg.subGroup));
46
- if (!visible.length) return null;
47
- return html`
48
- <details class="inline-subgroup" open style="margin-left:var(--space-2);margin-bottom:var(--space-1);">
49
- <summary style="font-size:var(--text-xs);font-weight:500;color:var(--text-secondary);cursor:pointer;padding:var(--space-1) var(--space-3);user-select:none;">
50
- ${sg.subGroup} <span style="color:var(--text-muted);font-weight:400;">(${visible.length})</span>
51
- </summary>
52
- ${visible.map(f => html`
53
- <${FileEntry}
54
- key=${f.path}
55
- file=${f}
56
- extraText=${sg.subGroup}
57
- onSelect=${onSelect}
58
- isSelected=${selectedPath === f.path}
59
- />
60
- `)}
61
- </details>
62
- `;
63
- })}
56
+ ${subGroupsVisible.map(({ sg, visible }) => html`
57
+ <details class="inline-subgroup" open>
58
+ <summary class="inline-subgroup-summary">
59
+ ${sg.subGroup} <span class="inline-file-group-count">${visible.length}</span>
60
+ </summary>
61
+ ${visible.map(f => html`
62
+ <${FileEntry}
63
+ key=${f.path}
64
+ file=${f}
65
+ onSelect=${onSelect}
66
+ isSelected=${selectedPath === f.path}
67
+ />
68
+ `)}
69
+ </details>
70
+ `)}
64
71
  </div>
65
72
  `;
66
73
  }
@@ -69,9 +76,9 @@ function FileGroup({ group, onSelect, selectedPath, filter }) {
69
76
  const visible = group.files.filter(f => matchesFilter(f, ''));
70
77
  if (!visible.length) return null;
71
78
  return html`
72
- <div class="inline-file-group" style="margin-bottom:var(--space-3);">
73
- <div style="font-size:var(--text-xs);font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.07em;padding:var(--space-1) var(--space-3);">
74
- ${group.group}
79
+ <div class="inline-file-group">
80
+ <div class="inline-file-group-title">
81
+ ${group.group} <span class="inline-file-group-count">${visible.length}</span>
75
82
  </div>
76
83
  ${visible.map(f => html`
77
84
  <${FileEntry}
@@ -116,9 +123,15 @@ export function FilesView() {
116
123
  setState({ requestedFile: null });
117
124
  }, [requestedFile]);
118
125
 
126
+ const total = useMemo(() => groups.reduce((n, g) =>
127
+ n + (g.files ? g.files.length : (g.subGroups || []).reduce((m, sg) => m + sg.files.length, 0)), 0),
128
+ [groups]);
129
+
119
130
  return html`
120
131
  <div class="view active" id="view-files">
121
- <div class="view-title">Files</div>
132
+ <div class="view-title">
133
+ Files ${total > 0 ? html`<span class="inline-file-total">${total}</span>` : null}
134
+ </div>
122
135
  <div id="file-list-inline">
123
136
  <div class="filter-bar">
124
137
  <input
@@ -131,9 +144,9 @@ export function FilesView() {
131
144
  </div>
132
145
  <div id="inline-file-items" class="phase-list">
133
146
  ${loading
134
- ? html`<div class="empty" style="margin:16px;">Loading…</div>`
147
+ ? html`<div class="empty">Loading…</div>`
135
148
  : groups.length === 0
136
- ? html`<div class="empty" style="margin:16px;">No files found.</div>`
149
+ ? html`<div class="empty">No files found.</div>`
137
150
  : groups.map(g => html`
138
151
  <${FileGroup}
139
152
  key=${g.group}
@@ -11,6 +11,7 @@ import { html, useState, useCallback } from '../preact.js';
11
11
  import { useStore, refresh } from '../store.js';
12
12
  import { allTasks, currentPhaseName } from '../util.js';
13
13
  import { stopStory, openOrchPanel, openTermPanel, setTaskStatus } from '../orchestrator.js';
14
+ import { openFileViewer } from '../store.js';
14
15
  import { openRunnerPicker } from '../components/RunnerPicker.js';
15
16
  import { showToast } from '../components/shared.js';
16
17
 
@@ -66,6 +67,10 @@ function KanbanCard({ task, col, live, orchDown, onDragStart, onDragEnd }) {
66
67
  e.stopPropagation();
67
68
  openOrchPanel(sid);
68
69
  }
70
+ function handleViewFile(e) {
71
+ e.stopPropagation();
72
+ openFileViewer(task.file, task.title || sid);
73
+ }
69
74
 
70
75
  return html`
71
76
  <div
@@ -92,6 +97,9 @@ function KanbanCard({ task, col, live, orchDown, onDragStart, onDragEnd }) {
92
97
  ` : null}
93
98
  ${sid ? html`
94
99
  <div class="kanban-card-actions">
100
+ ${task.file ? html`
101
+ <button class="kanban-view-btn" title=${'View ' + task.file} onClick=${handleViewFile}>📄 File</button>
102
+ ` : null}
95
103
  ${canRun ? html`
96
104
  <button class="kanban-run-btn" disabled=${orchDown}
97
105
  title=${orchDown ? 'Orchestrator unreachable' : 'Run ' + sid}