@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.
- package/README.md +47 -14
- package/package.json +1 -1
- package/rcode/agents/rcode-mariam.md +6 -0
- package/rcode/agents/rcode-sadiq.md +6 -0
- package/rcode/agents/rcode-waleed.md +6 -0
- package/rcode/bin/rcode-hooks.cjs +41 -5
- package/rcode/skills/agents/mariam-marketing/SKILL.md +1 -0
- package/rcode/skills/agents/sadiq-analyst/SKILL.md +1 -0
- package/rcode/skills/agents/waleed-architect/SKILL.md +1 -0
- package/rcode/workflows/council.md +29 -1
- package/server/dashboard.js +6 -3
- package/server/lib/html/client/components/App.js +16 -2
- package/server/lib/html/client/components/OrchPanel.js +2 -2
- package/server/lib/html/client/components/PhaseGraph.js +20 -12
- package/server/lib/html/client/components/Sidebar.js +1 -0
- package/server/lib/html/client/components/Topbar.js +3 -3
- package/server/lib/html/client/components/XtermPanel.js +98 -23
- package/server/lib/html/client/components/dashboard/Blockers.js +3 -3
- package/server/lib/html/client/components/dashboard/CompletedTasks.js +4 -2
- package/server/lib/html/client/components/dashboard/InProgress.js +4 -3
- package/server/lib/html/client/components/dashboard/ProgressTimeline.js +11 -6
- package/server/lib/html/client/components/dashboard/RecentDecisions.js +4 -3
- package/server/lib/html/client/components/shared.js +111 -2
- package/server/lib/html/client/orchestrator.js +28 -15
- package/server/lib/html/client/store.js +47 -0
- package/server/lib/html/client/util.js +1 -22
- package/server/lib/html/client/views/BacklogView.js +44 -0
- package/server/lib/html/client/views/DecisionsView.js +4 -3
- package/server/lib/html/client/views/FilesView.js +47 -34
- package/server/lib/html/client/views/KanbanView.js +8 -0
- package/server/lib/html/client/views/OrchestrationView.js +247 -169
- package/server/lib/html/client/views/PhasesView.js +8 -3
- package/server/lib/html/client/views/SprintsView.js +9 -0
- package/server/lib/html/client.js +1 -0
- package/server/lib/html/css.js +692 -246
- package/server/lib/html/shell.js +9 -3
- package/server/lib/scanner.js +21 -6
- 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,
|
|
24
|
+
function FileEntry({ file, onSelect, isSelected }) {
|
|
19
25
|
return html`
|
|
20
26
|
<div
|
|
21
|
-
class=${'
|
|
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
|
-
|
|
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"
|
|
41
|
-
<div
|
|
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
|
-
${
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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"
|
|
73
|
-
<div
|
|
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">
|
|
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"
|
|
147
|
+
? html`<div class="empty">Loading…</div>`
|
|
135
148
|
: groups.length === 0
|
|
136
|
-
? html`<div class="empty"
|
|
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}
|