@hanzlaa/rcode 2.3.1 → 2.3.3

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 (95) hide show
  1. package/CLAUDE.md +15 -0
  2. package/README.md +3 -3
  3. package/cli/postinstall.js +4 -4
  4. package/package.json +3 -2
  5. package/rihal/agents/rihal-executor.md +1 -0
  6. package/rihal/agents/rihal-phase-researcher.md +1 -0
  7. package/rihal/agents/rihal-planner.md +2 -1
  8. package/rihal/brain/best-practices/no-theoretical-suggestions.md +56 -0
  9. package/rihal/commands/add-phase.md +2 -2
  10. package/rihal/commands/cleanup.md +2 -2
  11. package/rihal/commands/pr-branch.md +2 -2
  12. package/rihal/commands/remove-phase.md +2 -2
  13. package/rihal/commands/research-phase.md +2 -2
  14. package/rihal/commands/ship.md +15 -3
  15. package/rihal/commands/validate-phase.md +1 -1
  16. package/rihal/commands/verify-phase.md +2 -2
  17. package/rihal/skills/actions/1-analysis/research/rihal-domain-research/SKILL.md +11 -0
  18. package/rihal/skills/actions/1-analysis/research/rihal-market-research/SKILL.md +11 -0
  19. package/rihal/skills/actions/1-analysis/research/rihal-technical-research/SKILL.md +13 -0
  20. package/rihal/skills/actions/1-analysis/rihal-document-project/SKILL.md +11 -0
  21. package/rihal/skills/actions/1-analysis/rihal-prfaq/SKILL.md +2 -0
  22. package/rihal/skills/actions/1-analysis/rihal-product-brief/SKILL.md +7 -0
  23. package/rihal/skills/actions/2-plan/rihal-create-epics-and-stories/SKILL.md +12 -0
  24. package/rihal/skills/actions/2-plan/rihal-create-milestone/SKILL.md +18 -0
  25. package/rihal/skills/actions/2-plan/rihal-create-prd/SKILL.md +13 -0
  26. package/rihal/skills/actions/2-plan/rihal-create-story/SKILL.md +12 -0
  27. package/rihal/skills/actions/2-plan/rihal-create-ux-design/SKILL.md +12 -0
  28. package/rihal/skills/actions/2-plan/rihal-edit-prd/SKILL.md +11 -0
  29. package/rihal/skills/actions/2-plan/rihal-frontend-design/SKILL.md +13 -0
  30. package/rihal/skills/actions/2-plan/rihal-validate-prd/SKILL.md +12 -0
  31. package/rihal/skills/actions/3-solutioning/rihal-check-implementation-readiness/SKILL.md +12 -0
  32. package/rihal/skills/actions/3-solutioning/rihal-create-architecture/SKILL.md +14 -0
  33. package/rihal/skills/actions/3-solutioning/rihal-generate-project-context/SKILL.md +12 -0
  34. package/rihal/skills/actions/4-implementation/rihal-checkpoint-preview/SKILL.md +6 -0
  35. package/rihal/skills/actions/4-implementation/rihal-code-review/SKILL.md +12 -0
  36. package/rihal/skills/actions/4-implementation/rihal-correct-course/SKILL.md +13 -0
  37. package/rihal/skills/actions/4-implementation/rihal-dev-story/SKILL.md +12 -0
  38. package/rihal/skills/actions/4-implementation/rihal-qa-generate-e2e-tests/SKILL.md +12 -0
  39. package/rihal/skills/actions/4-implementation/rihal-retrospective/SKILL.md +11 -0
  40. package/rihal/skills/actions/4-implementation/rihal-scaffold-project/SKILL.md +10 -0
  41. package/rihal/skills/actions/4-implementation/rihal-sprint-planning/SKILL.md +11 -0
  42. package/rihal/skills/actions/4-implementation/rihal-sprint-status/SKILL.md +10 -0
  43. package/rihal/skills/agents/ahmed-hassani-director/SKILL.md +13 -1
  44. package/rihal/skills/agents/fatima-qa/SKILL.md +14 -1
  45. package/rihal/skills/agents/haitham-frontend/SKILL.md +15 -1
  46. package/rihal/skills/agents/hanzla-engineer/SKILL.md +14 -1
  47. package/rihal/skills/agents/hussain-pm/SKILL.md +14 -1
  48. package/rihal/skills/agents/hussain-sm/SKILL.md +14 -1
  49. package/rihal/skills/agents/layla-designer/SKILL.md +15 -1
  50. package/rihal/skills/agents/majlis-council/SKILL.md +14 -1
  51. package/rihal/skills/agents/mariam-marketing/SKILL.md +15 -1
  52. package/rihal/skills/agents/nasser-eng-manager/SKILL.md +14 -1
  53. package/rihal/skills/agents/noor-writer/SKILL.md +14 -1
  54. package/rihal/skills/agents/raees-orchestrator/SKILL.md +13 -1
  55. package/rihal/skills/agents/sadiq-analyst/SKILL.md +15 -1
  56. package/rihal/skills/agents/waleed-architect/SKILL.md +16 -1
  57. package/rihal/skills/agents/yousef-backend/SKILL.md +16 -1
  58. package/rihal/skills/agents/zahra-branding/SKILL.md +15 -1
  59. package/rihal/skills/agents/zayd-ml/SKILL.md +17 -1
  60. package/rihal/skills/core/rihal-advanced-elicitation/SKILL.md +12 -0
  61. package/rihal/skills/core/rihal-brainstorming/SKILL.md +16 -0
  62. package/rihal/skills/core/rihal-clone-website/SKILL.md +21 -0
  63. package/rihal/skills/core/rihal-distillator/SKILL.md +8 -0
  64. package/rihal/skills/core/rihal-editorial-review-prose/SKILL.md +12 -0
  65. package/rihal/skills/core/rihal-editorial-review-structure/SKILL.md +18 -0
  66. package/rihal/skills/core/rihal-help/SKILL.md +12 -0
  67. package/rihal/skills/core/rihal-index-docs/SKILL.md +18 -0
  68. package/rihal/skills/core/rihal-init/SKILL.md +8 -0
  69. package/rihal/skills/core/rihal-party-mode/SKILL.md +14 -0
  70. package/rihal/skills/core/rihal-review-adversarial-general/SKILL.md +12 -0
  71. package/rihal/skills/core/rihal-review-edge-case-hunter/SKILL.md +18 -0
  72. package/rihal/skills/core/rihal-shard-doc/SKILL.md +18 -0
  73. package/rihal/state.json +22 -0
  74. package/rihal/team.yaml +205 -0
  75. package/rihal/workflows/autonomous.md +2 -2
  76. package/rihal/workflows/chain.md +1 -1
  77. package/rihal/workflows/checkpoint-preview.md +1 -1
  78. package/rihal/workflows/council.md +1 -1
  79. package/rihal/workflows/discuss.md +2 -2
  80. package/rihal/workflows/document-project.md +1 -1
  81. package/rihal/workflows/enable-hooks.md +1 -1
  82. package/rihal/workflows/help.md +1 -1
  83. package/rihal/workflows/prfaq.md +1 -1
  84. package/rihal/workflows/settings.md +18 -17
  85. package/rihal/workflows/ship.md +31 -1
  86. package/rihal/workflows/sprint-planning.md +2 -2
  87. package/server/dashboard.js +34 -575
  88. package/server/lib/api.js +123 -0
  89. package/server/lib/html/client.js +969 -0
  90. package/server/lib/html/css.js +416 -0
  91. package/server/lib/html/shell.js +230 -0
  92. package/server/lib/scanner.js +142 -0
  93. package/rihal/skills/core/rihal-advanced-elicitation/rihal-advanced-elicitation/SKILL.md +0 -148
  94. package/rihal/skills/core/rihal-advanced-elicitation/rihal-advanced-elicitation/methods.csv +0 -51
  95. package/rihal/skills/core/rihal-shard-doc/rihal-shard-doc/SKILL.md +0 -122
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Dashboard CSS — all styles in one module.
3
+ * Supports dark mode (default) and light mode via data-theme="light".
4
+ */
5
+ function renderCss() {
6
+ return `<style>
7
+ :root {
8
+ --rihal-blue: #1e3a8a;
9
+ --rihal-gold: #f59e0b;
10
+ --bg: #0a0a0b;
11
+ --bg-card: #111113;
12
+ --bg-hover: #1a1a1e;
13
+ --border: #1e1e24;
14
+ --text-primary: #f0f0f2;
15
+ --text-secondary: #a0a0aa;
16
+ --text-muted: #606068;
17
+ --accent-blue: #3b82f6;
18
+ --accent-green: #10b981;
19
+ --accent-amber: #f59e0b;
20
+ --accent-red: #ef4444;
21
+ --text-xs: 11px;
22
+ --text-sm: 13px;
23
+ --text-base: 15px;
24
+ --text-lg: 18px;
25
+ --text-xl: 24px;
26
+ --space-1: 4px;
27
+ --space-2: 8px;
28
+ --space-3: 12px;
29
+ --space-4: 16px;
30
+ --space-5: 20px;
31
+ --space-6: 24px;
32
+ --space-7: 28px;
33
+ --space-8: 32px;
34
+ --radius-sm: 4px;
35
+ --radius-md: 8px;
36
+ --radius-lg: 12px;
37
+ --shadow-card: 0 1px 3px rgba(0,0,0,0.4), 0 0 0 1px var(--border);
38
+ }
39
+ /* #313 Light mode */
40
+ [data-theme="light"] {
41
+ --bg: #f8f9fa;
42
+ --bg-card: #ffffff;
43
+ --bg-hover: #f0f1f3;
44
+ --border: #e2e4e8;
45
+ --text-primary: #1a1a1a;
46
+ --text-secondary: #555;
47
+ --text-muted: #888;
48
+ --shadow-card: 0 1px 3px rgba(0,0,0,0.08), 0 0 0 1px var(--border);
49
+ }
50
+ * { box-sizing: border-box; margin: 0; padding: 0; }
51
+ body {
52
+ font-family: 'Inter', -apple-system, 'Segoe UI', sans-serif;
53
+ background: var(--bg);
54
+ color: var(--text-primary);
55
+ line-height: 1.6;
56
+ }
57
+ .app-shell { display: flex; height: 100vh; overflow: hidden; }
58
+ /* Sidebar */
59
+ .sidebar {
60
+ width: 240px; min-width: 240px;
61
+ background: var(--bg-card);
62
+ border-right: 1px solid var(--border);
63
+ display: flex; flex-direction: column;
64
+ overflow-y: auto; padding: var(--space-4) 0;
65
+ }
66
+ .sidebar-project {
67
+ padding: var(--space-3) var(--space-4);
68
+ font-size: var(--text-sm); font-weight: 600;
69
+ color: var(--text-primary);
70
+ border-bottom: 1px solid var(--border);
71
+ margin-bottom: var(--space-3);
72
+ }
73
+ .sidebar-project .project-label {
74
+ font-size: var(--text-xs); color: var(--text-muted);
75
+ text-transform: uppercase; letter-spacing: 0.07em;
76
+ margin-bottom: var(--space-1);
77
+ }
78
+ .nav-link {
79
+ display: flex; align-items: center; gap: var(--space-2);
80
+ padding: var(--space-2) var(--space-4);
81
+ font-size: var(--text-sm); color: var(--text-secondary);
82
+ cursor: pointer; border-radius: 0; border: none; background: none;
83
+ width: 100%; text-align: left;
84
+ transition: background 0.15s, color 0.15s; user-select: none;
85
+ }
86
+ .nav-link:hover { background: var(--bg-hover); color: var(--text-primary); }
87
+ .nav-link.active { background: var(--bg-hover); color: var(--text-primary); font-weight: 600; }
88
+ .nav-section {
89
+ padding: var(--space-3) var(--space-4) var(--space-1);
90
+ font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
91
+ color: var(--text-muted); font-weight: 600;
92
+ }
93
+ /* Content */
94
+ .content-area { flex: 1; overflow-y: auto; background: var(--bg); display: flex; flex-direction: column; }
95
+ .view { display: none; padding: var(--space-8); }
96
+ .view.active { display: block; }
97
+ /* Header */
98
+ header {
99
+ background: var(--bg-card); border-bottom: 1px solid var(--border);
100
+ padding: var(--space-4) var(--space-8);
101
+ display: flex; justify-content: space-between; align-items: center; flex-shrink: 0;
102
+ }
103
+ .brand { display: flex; align-items: center; gap: var(--space-4); }
104
+ .brand .icon { font-size: 40px; }
105
+ .brand h1 { font-size: var(--text-xl); font-weight: 700; }
106
+ .brand .arabic { color: var(--rihal-gold); font-size: var(--text-lg); margin-top: 2px; }
107
+ .header-meta { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--text-secondary); }
108
+ .header-actions { display: flex; align-items: center; gap: var(--space-2); }
109
+ .live { display: inline-block; width: 8px; height: 8px; background: var(--accent-green); border-radius: 50%; animation: pulse 2s infinite; }
110
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
111
+ .header-btn {
112
+ background: var(--bg-hover); border: 1px solid var(--border); color: var(--text-primary);
113
+ padding: var(--space-1) var(--space-3); border-radius: var(--radius-sm);
114
+ cursor: pointer; font-size: var(--text-sm); transition: background 0.15s; font-family: inherit;
115
+ }
116
+ .header-btn:hover { background: var(--border); }
117
+ /* Blocker banner */
118
+ #blocker-banner {
119
+ background: rgba(239,68,68,0.12); border-bottom: 1px solid rgba(239,68,68,0.4);
120
+ padding: var(--space-3) var(--space-8); display: flex;
121
+ align-items: center; justify-content: space-between; gap: var(--space-4);
122
+ color: var(--accent-red); font-size: var(--text-sm);
123
+ }
124
+ #blocker-banner .banner-title { font-weight: 600; }
125
+ #blocker-banner .banner-list { flex: 1; color: var(--text-secondary); font-size: var(--text-xs); margin-left: var(--space-3); }
126
+ #blocker-banner .banner-dismiss {
127
+ background: none; border: 1px solid rgba(239,68,68,0.4); color: var(--accent-red);
128
+ padding: 2px 10px; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--text-xs); font-family: inherit;
129
+ }
130
+ #blocker-banner .banner-dismiss:hover { background: rgba(239,68,68,0.2); }
131
+ /* #322 Warning banner for parse errors */
132
+ #parse-warning {
133
+ background: rgba(245,158,11,0.12); border-bottom: 1px solid rgba(245,158,11,0.4);
134
+ padding: var(--space-3) var(--space-8); display: flex;
135
+ align-items: center; gap: var(--space-4);
136
+ color: var(--accent-amber); font-size: var(--text-sm);
137
+ }
138
+ /* Stats grid */
139
+ .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-4); margin-bottom: var(--space-8); }
140
+ .stat {
141
+ background: var(--bg-card); border: 1px solid var(--border); border-left: 4px solid var(--rihal-gold);
142
+ padding: var(--space-5) var(--space-6); border-radius: var(--radius-md);
143
+ }
144
+ .stat .label { color: var(--text-muted); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: var(--space-2); }
145
+ .stat .value { font-size: 28px; font-weight: 700; }
146
+ .stat .sub { color: var(--text-muted); font-size: var(--text-sm); margin-top: var(--space-1); }
147
+ /* Sections */
148
+ section { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg); margin-bottom: var(--space-6); overflow: hidden; }
149
+ section > h2 {
150
+ background: rgba(245,158,11,0.08); padding: var(--space-4) var(--space-6);
151
+ font-size: var(--text-sm); text-transform: uppercase; letter-spacing: 0.1em;
152
+ color: var(--rihal-gold); border-bottom: 1px solid var(--border);
153
+ display: flex; align-items: center; gap: 10px;
154
+ }
155
+ section .body { padding: var(--space-6); }
156
+ /* Agent cards */
157
+ .agents { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--space-3); }
158
+ .agent-card {
159
+ background: rgba(59,130,246,0.05); border: 1px solid var(--border);
160
+ border-radius: var(--radius-md); padding: var(--space-4); transition: transform 0.2s;
161
+ }
162
+ .agent-card:hover { transform: translateY(-2px); border-color: var(--rihal-gold); }
163
+ .agent-card .name { font-weight: 600; font-size: var(--text-base); margin-bottom: var(--space-1); }
164
+ .agent-card .arabic { color: var(--rihal-gold); font-size: 14px; }
165
+ .agent-card .role { color: var(--text-muted); font-size: var(--text-xs); margin-top: var(--space-2); }
166
+ .agent-card.active { background: rgba(16,185,129,0.1); border-color: var(--accent-green); }
167
+ .real-badge {
168
+ display: inline-block; background: rgba(16,185,129,0.2); color: var(--accent-green);
169
+ padding: 1px 6px; border-radius: 8px; font-size: 9px;
170
+ text-transform: uppercase; letter-spacing: 0.05em; vertical-align: middle; margin-left: 4px;
171
+ }
172
+ /* #304 Agent type badge */
173
+ .type-badge {
174
+ display: inline-block; background: rgba(59,130,246,0.15); color: var(--accent-blue);
175
+ padding: 1px 6px; border-radius: 8px; font-size: 9px;
176
+ text-transform: uppercase; letter-spacing: 0.05em; vertical-align: middle; margin-left: 4px;
177
+ }
178
+ /* Items */
179
+ .phase-list, .decision-list, .progress-list { display: flex; flex-direction: column; gap: var(--space-3); }
180
+ .item {
181
+ background: rgba(255,255,255,0.02); border: 1px solid var(--border);
182
+ border-left: 3px solid var(--accent-blue); padding: var(--space-4) var(--space-5);
183
+ border-radius: var(--radius-sm);
184
+ }
185
+ .item .item-title { font-weight: 600; margin-bottom: var(--space-2); }
186
+ .item .item-meta { color: var(--text-muted); font-size: var(--text-xs); margin-bottom: var(--space-2); }
187
+ .item-clickable { cursor: pointer; }
188
+ .item-clickable:hover { background: var(--bg-hover); border-color: var(--accent-blue); }
189
+ .empty { color: var(--text-muted); text-align: center; padding: var(--space-8); font-style: italic; }
190
+ /* #316 Actionable empty states */
191
+ .empty-action {
192
+ display: inline-block; margin-top: var(--space-3);
193
+ background: var(--bg-hover); border: 1px solid var(--border);
194
+ padding: var(--space-2) var(--space-4); border-radius: var(--radius-md);
195
+ color: var(--accent-blue); font-size: var(--text-sm); font-style: normal;
196
+ }
197
+ .tag {
198
+ display: inline-block; background: rgba(245,158,11,0.15); color: var(--rihal-gold);
199
+ padding: 2px 10px; border-radius: 12px; font-size: 11px;
200
+ text-transform: uppercase; letter-spacing: 0.05em; margin-right: 6px;
201
+ }
202
+ .status-chip {
203
+ display: inline-flex; align-items: center; gap: 4px;
204
+ padding: 2px 8px; border-radius: 99px; font-size: var(--text-xs);
205
+ font-weight: 500; letter-spacing: 0.04em; text-transform: lowercase;
206
+ }
207
+ .status-chip.complete { background: rgba(16,185,129,0.15); color: var(--accent-green); }
208
+ .status-chip.active,
209
+ .status-chip.in_progress { background: rgba(59,130,246,0.15); color: var(--accent-blue); }
210
+ .status-chip.blocked { background: rgba(239,68,68,0.15); color: var(--accent-red); }
211
+ /* Fix #314: 'planned' gets its own class */
212
+ .status-chip.planned { background: rgba(96,96,104,0.2); color: var(--text-muted); }
213
+ .status-chip.todo { background: rgba(96,96,104,0.2); color: var(--text-muted); }
214
+ .status-chip.other { background: rgba(96,96,104,0.2); color: var(--text-muted); }
215
+ /* Progress bar */
216
+ .progress-bar {
217
+ height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; width: 100%;
218
+ }
219
+ .progress-bar-fill {
220
+ height: 100%; border-radius: 3px; transition: width 0.3s ease;
221
+ background: var(--accent-green);
222
+ }
223
+ /* File tree */
224
+ .file-tree { font-size: var(--text-xs); }
225
+ .file-tree-group { margin-bottom: var(--space-3); }
226
+ .file-tree-group summary {
227
+ color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.07em;
228
+ font-size: 10px; padding: var(--space-1) var(--space-2); cursor: pointer; list-style: none;
229
+ }
230
+ .file-tree-item {
231
+ display: block; padding: 3px var(--space-3); color: var(--text-secondary);
232
+ cursor: pointer; border-radius: var(--radius-sm); overflow: hidden;
233
+ text-overflow: ellipsis; white-space: nowrap; font-family: 'SF Mono', Monaco, Consolas, monospace;
234
+ }
235
+ .file-tree-item:hover { color: var(--text-primary); background: var(--bg-hover); }
236
+ .file-tree-item.selected { color: var(--accent-blue); background: rgba(59,130,246,0.1); }
237
+ /* #300 File modification date */
238
+ .file-tree-date { color: var(--text-muted); font-size: 9px; margin-left: 4px; }
239
+ /* Markdown render */
240
+ .md-render {
241
+ background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg);
242
+ padding: var(--space-8); max-width: 860px; line-height: 1.7;
243
+ }
244
+ .md-render h1, .md-render h2, .md-render h3 { margin: var(--space-6) 0 var(--space-3); }
245
+ .md-render code { background: var(--bg-hover); padding: 2px 6px; border-radius: var(--radius-sm); font-size: var(--text-sm); }
246
+ .md-render pre { background: var(--bg-hover); padding: var(--space-4); border-radius: var(--radius-md); overflow-x: auto; }
247
+ .md-render a { color: var(--accent-blue); }
248
+ .md-render ul, .md-render ol { margin-left: var(--space-6); margin-bottom: var(--space-3); }
249
+ /* #302 Syntax highlighting for fenced code blocks */
250
+ .md-render pre code {
251
+ background: none; padding: 0; display: block;
252
+ color: var(--text-secondary); font-size: var(--text-sm);
253
+ font-family: "SF Mono", Monaco, Consolas, monospace;
254
+ }
255
+ .md-render pre code .kw { color: #c678dd; }
256
+ .md-render pre code .str { color: #98c379; }
257
+ .md-render pre code .cm { color: #5c6370; font-style: italic; }
258
+ /* Filter bar */
259
+ .filter-bar { margin-bottom: var(--space-6); display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap; }
260
+ .filter-input {
261
+ width: 100%; max-width: 360px; background: var(--bg-card);
262
+ border: 1px solid var(--border); border-radius: var(--radius-md);
263
+ color: var(--text-primary); font-size: var(--text-sm);
264
+ padding: var(--space-2) var(--space-3); outline: none; font-family: inherit;
265
+ }
266
+ .filter-input:focus { border-color: var(--accent-blue); }
267
+ .filter-input::placeholder { color: var(--text-muted); }
268
+ /* #296 Filter select */
269
+ .filter-select {
270
+ background: var(--bg-card); border: 1px solid var(--border);
271
+ border-radius: var(--radius-md); color: var(--text-primary);
272
+ font-size: var(--text-sm); padding: var(--space-2) var(--space-3);
273
+ font-family: inherit; outline: none;
274
+ }
275
+ .view-title { font-size: var(--text-lg); font-weight: 600; margin-bottom: var(--space-6); }
276
+ /* Breadcrumb */
277
+ .breadcrumb { margin-bottom: var(--space-5); }
278
+ .back-btn {
279
+ background: var(--bg-card); border: 1px solid var(--border); color: var(--text-secondary);
280
+ padding: var(--space-2) var(--space-4); border-radius: var(--radius-md);
281
+ cursor: pointer; font-size: var(--text-sm); font-family: inherit; transition: all 0.15s;
282
+ }
283
+ .back-btn:hover { color: var(--text-primary); border-color: var(--accent-blue); }
284
+ /* Entity detail */
285
+ .entity-header { margin-bottom: var(--space-6); }
286
+ .entity-title { font-size: var(--text-xl); font-weight: 700; margin-bottom: var(--space-4); }
287
+ .attr-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: var(--space-3); }
288
+ .attr-item {
289
+ background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-md);
290
+ padding: var(--space-3) var(--space-4); display: flex; flex-direction: column; gap: 4px;
291
+ }
292
+ .attr-label { font-size: var(--text-xs); color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; }
293
+ .attr-value { font-size: var(--text-sm); font-weight: 500; }
294
+ /* Tree */
295
+ .tree-container { padding: 0; }
296
+ .tree-ms { border-left: none !important; margin-left: 0 !important; }
297
+ .tree-node { border-left: 1px solid var(--border); margin-left: var(--space-4); }
298
+ .tree-row {
299
+ display: flex; align-items: center; gap: var(--space-2);
300
+ padding: var(--space-2) var(--space-3); cursor: pointer;
301
+ border-radius: var(--radius-sm); transition: background 0.1s; user-select: none;
302
+ }
303
+ .tree-row:hover { background: var(--bg-hover); }
304
+ .task-leaf > .tree-row { cursor: default; }
305
+ .tree-chevron { color: var(--text-muted); font-size: 10px; width: 14px; flex-shrink: 0; }
306
+ .tree-icon { flex-shrink: 0; }
307
+ .tree-label { flex: 1; font-size: var(--text-sm); }
308
+ .tree-badge { color: var(--text-muted); font-size: var(--text-xs); flex-shrink: 0; }
309
+ .tree-ms > .tree-row .tree-label { font-weight: 700; font-size: var(--text-base); color: var(--rihal-gold); }
310
+ .tree-children { padding-left: var(--space-3); }
311
+ /* #311 Tree animation */
312
+ .tree-children { overflow: hidden; transition: max-height 0.25s ease; }
313
+ /* #315 Loading skeleton */
314
+ .skeleton {
315
+ background: linear-gradient(90deg, var(--bg-hover) 25%, var(--border) 50%, var(--bg-hover) 75%);
316
+ background-size: 200% 100%;
317
+ animation: shimmer 1.5s infinite;
318
+ border-radius: var(--radius-md); height: 80px; margin-bottom: var(--space-3);
319
+ }
320
+ @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
321
+ /* #298 File path header */
322
+ .file-path-header {
323
+ font-family: 'SF Mono', Monaco, Consolas, monospace;
324
+ font-size: var(--text-sm); color: var(--text-muted);
325
+ padding: var(--space-3) 0; margin-bottom: var(--space-4);
326
+ border-bottom: 1px solid var(--border);
327
+ display: flex; align-items: center; gap: var(--space-3);
328
+ }
329
+ .file-path-header .copy-btn {
330
+ background: var(--bg-hover); border: 1px solid var(--border);
331
+ color: var(--text-secondary); padding: 2px 8px; border-radius: var(--radius-sm);
332
+ cursor: pointer; font-size: var(--text-xs); font-family: inherit;
333
+ }
334
+ .file-path-header .copy-btn:hover { color: var(--text-primary); }
335
+ /* Footer */
336
+ footer {
337
+ text-align: center; padding: var(--space-8); color: var(--text-muted); font-size: var(--text-sm);
338
+ border-top: 1px solid var(--border); margin-top: 48px;
339
+ }
340
+ footer .arabic { color: var(--rihal-gold); font-size: 16px; margin-bottom: var(--space-2); }
341
+ code {
342
+ background: rgba(255,255,255,0.05); padding: 2px 6px;
343
+ border-radius: var(--radius-sm); font-size: var(--text-sm);
344
+ font-family: "SF Mono", Monaco, Consolas, monospace;
345
+ }
346
+ h1, h2, h3 { line-height: 1.3; }
347
+ p { margin-bottom: 10px; }
348
+ ul { margin-left: 20px; margin-bottom: 10px; }
349
+ /* Velocity bar */
350
+ .velocity-bar { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2); }
351
+ .velocity-bar-label { font-size: var(--text-xs); color: var(--text-muted); width: 60px; text-align: right; }
352
+ .velocity-bar-track { flex: 1; height: 14px; background: var(--border); border-radius: 7px; overflow: hidden; position: relative; }
353
+ .velocity-bar-fill { height: 100%; border-radius: 7px; background: var(--accent-blue); }
354
+ .velocity-bar-val { font-size: var(--text-xs); color: var(--text-secondary); width: 50px; }
355
+ /* #280 Completion ring */
356
+ .completion-ring { position: relative; width: 64px; height: 64px; }
357
+ .completion-ring svg { transform: rotate(-90deg); }
358
+ .completion-ring .ring-text {
359
+ position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
360
+ font-size: var(--text-sm); font-weight: 700;
361
+ }
362
+ /* #323 Responsive */
363
+ @media (max-width: 768px) {
364
+ .sidebar { display: none; }
365
+ .content-area { width: 100%; }
366
+ .view { padding: var(--space-4); }
367
+ header { padding: var(--space-3) var(--space-4); flex-wrap: wrap; gap: var(--space-2); }
368
+ .brand .icon { font-size: 28px; }
369
+ .brand h1 { font-size: var(--text-lg); }
370
+ .stats { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
371
+ .agents { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
372
+ .attr-grid { grid-template-columns: 1fr 1fr; }
373
+ }
374
+ /* Command hints accordion */
375
+ .cmd-hints {
376
+ margin-top: var(--space-4); border: 1px solid var(--border); border-radius: var(--radius-md);
377
+ background: var(--bg-card); overflow: hidden;
378
+ }
379
+ .cmd-hints summary {
380
+ padding: var(--space-2) var(--space-3); cursor: pointer; font-size: var(--text-sm);
381
+ color: var(--text-muted); font-weight: 500; list-style: none;
382
+ display: flex; align-items: center; gap: 6px; user-select: none;
383
+ }
384
+ .cmd-hints summary::-webkit-details-marker { display: none; }
385
+ .cmd-hints summary::before { content: '▶'; font-size: 10px; transition: transform 0.2s; }
386
+ .cmd-hints[open] summary::before { transform: rotate(90deg); }
387
+ .cmd-hints summary:hover { color: var(--text-primary); background: var(--bg-hover); }
388
+ .cmd-hints-list { padding: var(--space-2) 0; }
389
+ .cmd-hint-item {
390
+ display: flex; align-items: baseline; gap: var(--space-3); padding: var(--space-2) var(--space-3);
391
+ cursor: pointer; transition: background 0.15s;
392
+ }
393
+ .cmd-hint-item:hover { background: var(--bg-hover); }
394
+ .cmd-hint-item .cmd-text {
395
+ font-family: 'SF Mono', Monaco, Consolas, monospace; font-size: var(--text-xs);
396
+ color: var(--accent-blue); white-space: nowrap; font-weight: 500;
397
+ }
398
+ .cmd-hint-item .cmd-desc {
399
+ font-size: var(--text-xs); color: var(--text-muted); flex: 1;
400
+ }
401
+ .cmd-hint-item .cmd-copy {
402
+ font-size: 10px; color: var(--text-muted); opacity: 0; transition: opacity 0.15s; margin-left: auto;
403
+ }
404
+ .cmd-hint-item:hover .cmd-copy { opacity: 1; }
405
+ /* Toast notification (for copy feedback) */
406
+ .toast {
407
+ position: fixed; bottom: 20px; right: 20px; background: var(--accent-green);
408
+ color: #fff; padding: var(--space-2) var(--space-4); border-radius: var(--radius-md);
409
+ font-size: var(--text-sm); z-index: 1000; opacity: 0; transition: opacity 0.3s;
410
+ pointer-events: none;
411
+ }
412
+ .toast.show { opacity: 1; }
413
+ </style>`;
414
+ }
415
+
416
+ module.exports = { renderCss };
@@ -0,0 +1,230 @@
1
+ /**
2
+ * HTML shell — composes the full page from CSS, views, and client JS.
3
+ */
4
+ const { renderCss } = require('./css');
5
+ const { renderClientJs } = require('./client');
6
+
7
+ function esc(s) { return String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
8
+
9
+ function renderHtml(state) {
10
+ const projectName = state.projectName || 'No project initialized';
11
+ const currentPhase = state.currentPhase || '—';
12
+ const currentSprint = state.currentSprint || null;
13
+ const phaseCount = (state.raw?.phases || []).length;
14
+ const decisionCount = (state.raw?.decisions || []).length;
15
+ const artifactCount = state.planningFiles.length;
16
+
17
+ const agents = [
18
+ { name: 'Sadiq Damani', arabic: 'صادق', role: 'Director of Strategy', real: true, type: 'leadership' },
19
+ { name: 'Waleed Al Harthi', arabic: 'وليد', role: 'CTO', real: true, type: 'leadership' },
20
+ { name: 'Ahmed Al Hassani', arabic: 'أحمد الحسني', role: 'Technology & Development Director', real: true, type: 'leadership' },
21
+ { name: 'Nasser', arabic: 'ناصر', role: 'Engineering Manager', real: true, type: 'leadership' },
22
+ { name: 'Hussain', arabic: 'حسين', role: 'PM + Scrum Master', type: 'product' },
23
+ { name: 'Layla', arabic: 'ليلى', role: 'Lead UX Designer', type: 'design' },
24
+ { name: 'Zahra', arabic: 'زهرة', role: 'Branding & Creative Director', type: 'design' },
25
+ { name: 'Omar', arabic: 'عمر', role: 'Full-Stack Engineer', type: 'engineering' },
26
+ { name: 'Haitham Al Khamiyasi', arabic: 'هيثم', role: 'Senior Frontend', real: true, type: 'engineering' },
27
+ { name: 'Yousef', arabic: 'يوسف', role: 'Senior Backend', type: 'engineering' },
28
+ { name: 'Zayd', arabic: 'زيد', role: 'ML Engineer', type: 'engineering' },
29
+ { name: 'Fatima', arabic: 'فاطمة', role: 'QA Lead', type: 'quality' },
30
+ { name: 'Khalid', arabic: 'خالد', role: 'DevOps', type: 'engineering' },
31
+ { name: 'Noor', arabic: 'نور', role: 'Scribe', type: 'support' },
32
+ { name: 'Mariam', arabic: 'مريم', role: 'Marketing Lead', type: 'product' },
33
+ { name: 'Raees', arabic: 'رئيس', role: 'Orchestration Director', type: 'system' },
34
+ { name: 'Majlis', arabic: 'مجلس', role: 'Consulting Council', type: 'system' },
35
+ { name: 'Diwan', arabic: 'ديوان', role: 'Dashboard Registry', type: 'system' },
36
+ ];
37
+
38
+ // #305: separate real vs AI agents
39
+ const realAgents = agents.filter(a => a.real);
40
+ const aiAgents = agents.filter(a => !a.real);
41
+
42
+ function agentCard(a) {
43
+ const filterText = (a.name + ' ' + a.role + ' ' + a.arabic + ' ' + a.type).toLowerCase();
44
+ // #303: link to SKILL.md
45
+ const skillName = a.name.split(' ')[0].toLowerCase();
46
+ return `<div class="agent-card" data-filter-text="${filterText}" onclick="viewAgentSkill('${skillName}')" style="cursor:pointer;">
47
+ <div class="name">${esc(a.name)}${a.real ? ' <span class="real-badge">real</span>' : ''} <span class="type-badge">${esc(a.type)}</span></div>
48
+ <div class="arabic">${a.arabic}</div>
49
+ <div class="role">${esc(a.role)}</div>
50
+ </div>`;
51
+ }
52
+
53
+ return `<!DOCTYPE html>
54
+ <html lang="en" dir="ltr">
55
+ <head>
56
+ <meta charset="UTF-8">
57
+ <meta name="viewport" content="width=device-width, initial-scale=1">
58
+ <title>Majlis — ${esc(projectName)}</title>
59
+ <link rel="preconnect" href="https://fonts.googleapis.com">
60
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
61
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"><\/script>
62
+ ${renderCss()}
63
+ </head>
64
+ <body>
65
+ <div class="app-shell">
66
+ <aside class="sidebar">
67
+ <div class="sidebar-project">
68
+ <div class="project-label">Project</div>
69
+ ${esc(projectName)}
70
+ </div>
71
+ <nav>
72
+ <div class="nav-section">Overview</div>
73
+ <button class="nav-link" data-view="overview">🏠 Overview</button>
74
+ <button class="nav-link" data-view="roadmap">🗺 Roadmap</button>
75
+ <div class="nav-section">Planning</div>
76
+ <button class="nav-link" data-view="milestones">🎯 Milestones</button>
77
+ <button class="nav-link" data-view="phases">📋 Phases</button>
78
+ <button class="nav-link" data-view="sprints">⚡ Sprints</button>
79
+ <button class="nav-link" data-view="tasks">✓ Tasks</button>
80
+ <div class="nav-section">Workspace</div>
81
+ <button class="nav-link" data-view="files">📄 Files</button>
82
+ <button class="nav-link" data-view="agents">🤝 Agents</button>
83
+ <button class="nav-link" data-view="decisions">⚖ Decisions</button>
84
+ </nav>
85
+ <div id="sidebar-file-tree" style="margin-top:var(--space-4);padding:0 var(--space-2);"></div>
86
+ </aside>
87
+ <div class="content-area" id="main-content">
88
+ <header>
89
+ <div class="brand">
90
+ <div class="icon">🕌</div>
91
+ <div>
92
+ <h1>Majlis — The Council</h1>
93
+ <div class="arabic">مجلس · ${esc(projectName)}</div>
94
+ </div>
95
+ </div>
96
+ <div class="header-actions">
97
+ <span class="live" id="live-dot"></span>
98
+ <span id="updated-ago" style="font-size:var(--text-sm);color:var(--text-secondary);">just now</span>
99
+ &nbsp;·&nbsp;
100
+ <button class="header-btn" id="refresh-btn" onclick="manualRefresh()">↺ Refresh</button>
101
+ <button class="header-btn" id="theme-btn" onclick="toggleTheme()" title="Toggle dark/light">☀️</button>
102
+ <button class="header-btn" onclick="copyUrl()" title="Copy URL">🔗</button>
103
+ <button class="header-btn" onclick="exportSnapshot()" title="Export snapshot">📥</button>
104
+ </div>
105
+ </header>
106
+
107
+ ${state.rawParseError ? `<div id="parse-warning">⚠️ <strong>state.json parse error:</strong> ${esc(state.rawParseError)} — Dashboard showing partial data.</div>` : ''}
108
+
109
+ ${state.blockers.length > 0 ? `
110
+ <div id="blocker-banner">
111
+ <span class="banner-title">🚧 ${state.blockers.length} Blocker${state.blockers.length > 1 ? 's' : ''}</span>
112
+ <span class="banner-list">${state.blockers.map(b => esc(typeof b === 'string' ? b : (b.title || ''))).join(' · ')}</span>
113
+ <button class="banner-dismiss" onclick="dismissBlockers()">Dismiss</button>
114
+ </div>` : ''}
115
+
116
+ <div id="view-overview" class="view active">
117
+ ${!state.exists ? `
118
+ <div class="empty" style="padding:80px;background:var(--bg-card);border-radius:var(--radius-lg);">
119
+ <h2 style="color:var(--rihal-gold);margin-bottom:16px;">No .rihal/ directory found</h2>
120
+ <p>Run the <code>*kickoff</code> workflow to initialize a project.</p>
121
+ <div class="empty-action">npx rcode install</div>
122
+ </div>
123
+ ` : `
124
+ <div class="stats">
125
+ <div class="stat">
126
+ <div class="label">Current Phase</div>
127
+ <div class="value">${esc(currentPhase)}</div>
128
+ <div class="sub">${phaseCount} total phases${currentSprint ? ` · Sprint ${esc(currentSprint)}` : ''}</div>
129
+ </div>
130
+ <div class="stat">
131
+ <div class="label">Milestone</div>
132
+ <div class="value" style="font-size:16px;padding-top:6px;" id="stat-milestone">${esc(state.milestone || '—')}</div>
133
+ <div class="sub">&nbsp;</div>
134
+ </div>
135
+ <div class="stat">
136
+ <div class="label">Decisions (ADRs)</div>
137
+ <div class="value">${decisionCount}</div>
138
+ <div class="sub">Architecture records</div>
139
+ </div>
140
+ <div class="stat">
141
+ <div class="label">Planning Files</div>
142
+ <div class="value">${artifactCount}</div>
143
+ <div class="sub">SPRINT, CONTEXT, VERIFY, RESEARCH</div>
144
+ </div>
145
+ ${state.blockers.length > 0 ? `
146
+ <div class="stat" style="border-left-color:var(--accent-red);">
147
+ <div class="label" style="color:var(--accent-red);">Blockers</div>
148
+ <div class="value" style="color:var(--accent-red);">${state.blockers.length}</div>
149
+ <div class="sub">Active blockers</div>
150
+ </div>` : ''}
151
+ ${state.councilSessions > 0 ? `
152
+ <div class="stat">
153
+ <div class="label">Council Sessions</div>
154
+ <div class="value">${state.councilSessions}</div>
155
+ <div class="sub">Recorded sessions</div>
156
+ </div>` : ''}
157
+ </div>
158
+
159
+ <div id="view-overview-dynamic"></div>
160
+
161
+ <section>
162
+ <h2>🎯 Active Context</h2>
163
+ <div class="body">
164
+ ${state.context
165
+ ? `<div class="item-preview" style="max-height:none;">${esc(state.context)}</div>`
166
+ : `<div class="empty">No active context.<div class="empty-action">Run context-build workflow</div></div>`}
167
+ </div>
168
+ </section>
169
+ `}
170
+ </div>
171
+
172
+ <div id="view-roadmap" class="view"></div>
173
+ <div id="view-milestones" class="view"></div>
174
+ <div id="view-phases" class="view"></div>
175
+ <div id="view-sprints" class="view"></div>
176
+ <div id="view-tasks" class="view"></div>
177
+
178
+ <div id="view-files" class="view">
179
+ <div class="view-title">Files</div>
180
+ <div id="file-list-inline"></div>
181
+ <div id="file-view"></div>
182
+ </div>
183
+
184
+ <div id="view-agents" class="view">
185
+ <div class="view-title">Agents</div>
186
+ <div class="filter-bar">
187
+ <input class="filter-input" type="text" placeholder="Filter…" oninput="filterItems(this,'agents-list')">
188
+ </div>
189
+ <div id="agents-list">
190
+ <div style="font-size:var(--text-sm);font-weight:600;color:var(--rihal-gold);margin-bottom:var(--space-3);">Team Members</div>
191
+ <div class="agents" style="margin-bottom:var(--space-6);">
192
+ ${realAgents.map(agentCard).join('')}
193
+ </div>
194
+ <div style="font-size:var(--text-sm);font-weight:600;color:var(--accent-blue);margin-bottom:var(--space-3);">AI Agents</div>
195
+ <div class="agents">
196
+ ${aiAgents.map(agentCard).join('')}
197
+ </div>
198
+ </div>
199
+ </div>
200
+
201
+ <div id="view-decisions" class="view"></div>
202
+
203
+ <footer>
204
+ <div class="arabic">رحلة البناء · The Journey of Building</div>
205
+ <div>Rihal Code · View-Only Dashboard · <kbd>R</kbd> refresh · <kbd>1-9</kbd> switch views · <kbd>F</kbd> filter</div>
206
+ </footer>
207
+ </div>
208
+ </div>
209
+ <div class="toast" id="toast"></div>
210
+ <script>
211
+ // #303: view agent skill file
212
+ function viewAgentSkill(name) {
213
+ // Try to find matching file in file tree
214
+ var items = document.querySelectorAll('.file-tree-item');
215
+ for (var i = 0; i < items.length; i++) {
216
+ if ((items[i].dataset.path || '').toLowerCase().includes(name)) {
217
+ items[i].click();
218
+ return;
219
+ }
220
+ }
221
+ // Fallback
222
+ navTo('files');
223
+ }
224
+ <\/script>
225
+ ${renderClientJs(state)}
226
+ </body>
227
+ </html>`;
228
+ }
229
+
230
+ module.exports = { renderHtml };