egregore-artifacts 0.1.0 → 0.3.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/bin/cli.js +61 -9
- package/lib/components.js +2 -2
- package/lib/index.js +9 -2
- package/lib/markdown.js +54 -17
- package/lib/parsers/board.js +109 -0
- package/lib/parsers/document.js +101 -0
- package/lib/parsers/network.js +73 -0
- package/lib/registry.js +15 -15
- package/lib/shell.js +160 -9
- package/lib/templates/activity.js +28 -28
- package/lib/templates/board.js +459 -0
- package/lib/templates/document.js +52 -0
- package/lib/templates/handoff.js +9 -9
- package/lib/templates/network.js +189 -0
- package/lib/tokens.js +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// Board data → React element tree with three views (activity, person, timeline)
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ArtifactHeader, SectionCard, ArtifactFooter } from '../components.js';
|
|
4
|
+
import { fonts } from '../tokens.js';
|
|
5
|
+
|
|
6
|
+
const h = React.createElement;
|
|
7
|
+
|
|
8
|
+
// ── Priority & Status helpers ──────────────────────────────────
|
|
9
|
+
// All colors resolved as CSS var() strings so dark mode overrides apply.
|
|
10
|
+
|
|
11
|
+
const PRIORITY_COLORS = {
|
|
12
|
+
0: 'var(--terracotta)', // P0 — breaking
|
|
13
|
+
1: 'var(--green-p1)', // P1 — this cycle (green)
|
|
14
|
+
2: 'var(--blue-muted)', // P2 — next cycle
|
|
15
|
+
3: 'var(--muted)', // P3 — parked
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const PRIORITY_LABELS = { 0: 'P0', 1: 'P1', 2: 'P2', 3: 'P3' };
|
|
19
|
+
const STATUS_LABELS = { 'todo': 'To Do', 'in-progress': 'In Progress', 'review': 'Review', 'done': 'Done' };
|
|
20
|
+
const STATUS_SIGILS = { 'todo': '□', 'in-progress': '▸', 'review': '◎', 'done': '✓' };
|
|
21
|
+
|
|
22
|
+
// ── Card component ─────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
function BoardCardEl({ card, showActivity }) {
|
|
25
|
+
const pColor = PRIORITY_COLORS[card.priority] || 'var(--muted)';
|
|
26
|
+
const statusBg = card.status === 'done' ? 'var(--success-bg)'
|
|
27
|
+
: card.status === 'in-progress' ? 'var(--terracotta-chip)'
|
|
28
|
+
: 'var(--neutral-chip)';
|
|
29
|
+
const statusFg = card.status === 'done' ? 'var(--success-fg)'
|
|
30
|
+
: card.status === 'in-progress' ? 'var(--terracotta)'
|
|
31
|
+
: 'var(--muted)';
|
|
32
|
+
|
|
33
|
+
return h('div', {
|
|
34
|
+
style: {
|
|
35
|
+
background: 'var(--surface)',
|
|
36
|
+
border: '1px solid var(--border)',
|
|
37
|
+
borderLeft: `4px solid ${pColor}`,
|
|
38
|
+
borderRadius: '8px',
|
|
39
|
+
padding: '12px 16px',
|
|
40
|
+
marginBottom: '8px',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
// Title row
|
|
44
|
+
h('div', { style: { display: 'flex', alignItems: 'baseline', gap: '8px', marginBottom: '6px' } },
|
|
45
|
+
h('span', {
|
|
46
|
+
style: { fontFamily: fonts.mono, fontSize: '11px', color: pColor, fontWeight: 600, flexShrink: 0 },
|
|
47
|
+
}, PRIORITY_LABELS[card.priority]),
|
|
48
|
+
h('span', {
|
|
49
|
+
style: { fontSize: '14px', fontWeight: 500, color: 'var(--black)', flex: 1 },
|
|
50
|
+
}, card.title),
|
|
51
|
+
),
|
|
52
|
+
// Meta row
|
|
53
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' } },
|
|
54
|
+
// Status badge
|
|
55
|
+
h('span', {
|
|
56
|
+
style: {
|
|
57
|
+
fontFamily: fonts.mono, fontSize: '11px', padding: '2px 8px',
|
|
58
|
+
borderRadius: '4px', background: statusBg, color: statusFg,
|
|
59
|
+
},
|
|
60
|
+
}, `${STATUS_SIGILS[card.status] || ''} ${STATUS_LABELS[card.status] || card.status}`),
|
|
61
|
+
// Owners
|
|
62
|
+
...(card.owners || []).map((owner, i) =>
|
|
63
|
+
h('span', {
|
|
64
|
+
key: `o-${i}`,
|
|
65
|
+
style: {
|
|
66
|
+
fontFamily: fonts.mono, fontSize: '11px', color: 'var(--blue-muted)',
|
|
67
|
+
background: 'var(--blue-chip)', padding: '2px 6px', borderRadius: '3px',
|
|
68
|
+
},
|
|
69
|
+
}, owner)
|
|
70
|
+
),
|
|
71
|
+
// Activity badge (for person view)
|
|
72
|
+
showActivity && card.activity && h('span', {
|
|
73
|
+
style: { fontFamily: fonts.mono, fontSize: '10px', color: 'var(--muted)' },
|
|
74
|
+
}, card.subactivity ? `${card.activity} · ${card.subactivity}` : card.activity),
|
|
75
|
+
// Quest link
|
|
76
|
+
card.quest && h('span', {
|
|
77
|
+
style: { fontFamily: fonts.mono, fontSize: '10px', color: 'var(--terracotta)' },
|
|
78
|
+
}, `→ ${card.quest}`),
|
|
79
|
+
// Due date
|
|
80
|
+
card.dueDate && h('span', {
|
|
81
|
+
style: { fontFamily: fonts.mono, fontSize: '10px', color: 'var(--muted)' },
|
|
82
|
+
}, `due ${card.dueDate}`),
|
|
83
|
+
),
|
|
84
|
+
// Description
|
|
85
|
+
card.description && h('p', {
|
|
86
|
+
style: { fontSize: '13px', color: 'var(--muted)', margin: '6px 0 0', lineHeight: 1.4 },
|
|
87
|
+
}, card.description),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── Activity View ──────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
function ActivityView({ activities }) {
|
|
94
|
+
const sections = [];
|
|
95
|
+
|
|
96
|
+
for (const activity of activities) {
|
|
97
|
+
if (activity.subactivities) {
|
|
98
|
+
for (const sub of activity.subactivities) {
|
|
99
|
+
const cards = sub.cards || [];
|
|
100
|
+
if (cards.length === 0) continue;
|
|
101
|
+
sections.push(
|
|
102
|
+
h('div', { key: `${activity.id}-${sub.id}`, style: { marginBottom: '1.5rem' } },
|
|
103
|
+
h('div', {
|
|
104
|
+
style: {
|
|
105
|
+
fontFamily: fonts.mono, fontSize: '12px', textTransform: 'uppercase',
|
|
106
|
+
letterSpacing: '0.06em', color: 'var(--muted)', marginBottom: '8px', paddingBottom: '4px',
|
|
107
|
+
borderBottom: '1px solid var(--border)',
|
|
108
|
+
},
|
|
109
|
+
}, `${activity.label} · ${sub.label}`),
|
|
110
|
+
...cards.sort((a, b) => a.priority - b.priority).map((card, i) =>
|
|
111
|
+
h(BoardCardEl, { key: i, card, showActivity: false })
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const cards = activity.cards || [];
|
|
118
|
+
if (cards.length === 0) continue;
|
|
119
|
+
sections.push(
|
|
120
|
+
h('div', { key: activity.id, style: { marginBottom: '1.5rem' } },
|
|
121
|
+
h('div', {
|
|
122
|
+
style: {
|
|
123
|
+
fontFamily: fonts.mono, fontSize: '12px', textTransform: 'uppercase',
|
|
124
|
+
letterSpacing: '0.06em', color: 'var(--muted)', marginBottom: '8px', paddingBottom: '4px',
|
|
125
|
+
borderBottom: '1px solid var(--border)',
|
|
126
|
+
},
|
|
127
|
+
}, activity.label),
|
|
128
|
+
...cards.sort((a, b) => a.priority - b.priority).map((card, i) =>
|
|
129
|
+
h(BoardCardEl, { key: i, card, showActivity: false })
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return h('div', null, ...sections);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Person View ────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
function PersonView({ people }) {
|
|
141
|
+
return h('div', null,
|
|
142
|
+
...people.map((person, pi) =>
|
|
143
|
+
h('div', { key: pi, style: { marginBottom: '1.5rem' } },
|
|
144
|
+
h('div', {
|
|
145
|
+
style: {
|
|
146
|
+
display: 'flex', alignItems: 'baseline', gap: '12px',
|
|
147
|
+
fontFamily: fonts.mono, fontSize: '12px', textTransform: 'uppercase',
|
|
148
|
+
letterSpacing: '0.06em', color: 'var(--muted)', marginBottom: '8px', paddingBottom: '4px',
|
|
149
|
+
borderBottom: '1px solid var(--border)',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
h('span', { style: { color: 'var(--black)', fontWeight: 600 } }, person.name),
|
|
153
|
+
h('span', null, `${person.stats.total} cards`),
|
|
154
|
+
person.stats.p0 > 0 && h('span', { style: { color: 'var(--terracotta)' } }, `${person.stats.p0} P0`),
|
|
155
|
+
person.stats.inProgress > 0 && h('span', null, `${person.stats.inProgress} active`),
|
|
156
|
+
),
|
|
157
|
+
...person.cards.map((card, ci) =>
|
|
158
|
+
h(BoardCardEl, { key: ci, card, showActivity: true })
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Timeline View ──────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
function shortLabel(title) {
|
|
168
|
+
// Take first meaningful words, drop filler like "—", stop at 30 chars
|
|
169
|
+
const cleaned = title.replace(/\s*—\s*.*$/, '').replace(/\s*\+\s*.*$/, '');
|
|
170
|
+
if (cleaned.length <= 30) return cleaned;
|
|
171
|
+
const words = cleaned.split(/\s+/);
|
|
172
|
+
let result = '';
|
|
173
|
+
for (const w of words) {
|
|
174
|
+
if ((result + ' ' + w).trim().length > 28) break;
|
|
175
|
+
result = (result + ' ' + w).trim();
|
|
176
|
+
}
|
|
177
|
+
return result || words[0];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function TimelineView({ timeline, allCards }) {
|
|
181
|
+
if (timeline.length === 0) {
|
|
182
|
+
return h('p', { style: { color: 'var(--muted)', fontStyle: 'italic' } }, 'No cards with dates set.');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Find date range
|
|
186
|
+
const dates = timeline.flatMap(c => [c.startDate, c.dueDate].filter(Boolean));
|
|
187
|
+
const minDate = dates.reduce((a, b) => a < b ? a : b);
|
|
188
|
+
const maxDate = dates.reduce((a, b) => a > b ? a : b);
|
|
189
|
+
|
|
190
|
+
// Snap to week boundaries (Monday)
|
|
191
|
+
const startRaw = new Date(minDate + 'T00:00:00');
|
|
192
|
+
const startDay = startRaw.getDay();
|
|
193
|
+
const mondayOffset = startDay === 0 ? -6 : 1 - startDay;
|
|
194
|
+
const start = new Date(startRaw.getTime() + mondayOffset * 24 * 60 * 60 * 1000);
|
|
195
|
+
|
|
196
|
+
// Ensure at least 2 full weeks
|
|
197
|
+
const twoWeeksOut = new Date(start.getTime() + 14 * 24 * 60 * 60 * 1000);
|
|
198
|
+
const endRaw = new Date(maxDate + 'T00:00:00');
|
|
199
|
+
const effectiveEnd = endRaw > twoWeeksOut ? endRaw : twoWeeksOut;
|
|
200
|
+
// Snap end to Sunday
|
|
201
|
+
const endDay = effectiveEnd.getDay();
|
|
202
|
+
const sundayAdd = endDay === 0 ? 0 : 7 - endDay;
|
|
203
|
+
const end = new Date(effectiveEnd.getTime() + sundayAdd * 24 * 60 * 60 * 1000);
|
|
204
|
+
const totalDays = Math.ceil((end - start) / (1000 * 60 * 60 * 24));
|
|
205
|
+
|
|
206
|
+
const LABEL_WIDTH = '240px';
|
|
207
|
+
const ROW_HEIGHT = '32px';
|
|
208
|
+
|
|
209
|
+
function dayOffset(dateStr) {
|
|
210
|
+
if (!dateStr) return 0;
|
|
211
|
+
const d = new Date(dateStr + 'T00:00:00');
|
|
212
|
+
return Math.max(0, Math.ceil((d - start) / (1000 * 60 * 60 * 24)));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function formatDate(d) {
|
|
216
|
+
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Generate week markers
|
|
220
|
+
const weeks = [];
|
|
221
|
+
const cursor = new Date(start.getTime());
|
|
222
|
+
while (cursor <= end) {
|
|
223
|
+
weeks.push({
|
|
224
|
+
label: formatDate(cursor),
|
|
225
|
+
offset: Math.ceil((cursor - start) / (1000 * 60 * 60 * 24)),
|
|
226
|
+
});
|
|
227
|
+
cursor.setDate(cursor.getDate() + 7);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Today marker
|
|
231
|
+
const today = new Date().toISOString().split('T')[0];
|
|
232
|
+
const todayOffset = dayOffset(today);
|
|
233
|
+
const todayPct = (todayOffset / totalDays) * 100;
|
|
234
|
+
|
|
235
|
+
return h('div', { style: { width: '100%' } },
|
|
236
|
+
// Header row: label column + week markers
|
|
237
|
+
h('div', {
|
|
238
|
+
style: { display: 'flex', height: '24px', marginBottom: '4px', borderBottom: '1px solid var(--border)' },
|
|
239
|
+
},
|
|
240
|
+
// Empty label column
|
|
241
|
+
h('div', { style: { width: LABEL_WIDTH, flexShrink: 0 } }),
|
|
242
|
+
// Week header area
|
|
243
|
+
h('div', { style: { flex: 1, position: 'relative' } },
|
|
244
|
+
...weeks.map((w, i) =>
|
|
245
|
+
h('span', {
|
|
246
|
+
key: `wh-${i}`,
|
|
247
|
+
style: {
|
|
248
|
+
position: 'absolute', left: `${(w.offset / totalDays) * 100}%`,
|
|
249
|
+
fontFamily: fonts.mono, fontSize: '11px', color: 'var(--muted)', fontWeight: 500, whiteSpace: 'nowrap',
|
|
250
|
+
},
|
|
251
|
+
}, w.label)
|
|
252
|
+
),
|
|
253
|
+
todayPct >= 0 && todayPct <= 100 && h('span', {
|
|
254
|
+
style: {
|
|
255
|
+
position: 'absolute', left: `${todayPct}%`, transform: 'translateX(-50%)',
|
|
256
|
+
fontFamily: fonts.mono, fontSize: '10px', color: 'var(--terracotta)', fontWeight: 700,
|
|
257
|
+
},
|
|
258
|
+
}, 'today'),
|
|
259
|
+
),
|
|
260
|
+
),
|
|
261
|
+
// Card rows
|
|
262
|
+
...timeline.map((card, i) => {
|
|
263
|
+
const startOff = dayOffset(card.startDate || card.dueDate);
|
|
264
|
+
const endOff = dayOffset(card.dueDate || card.startDate);
|
|
265
|
+
const barLeft = (startOff / totalDays) * 100;
|
|
266
|
+
const barWidth = Math.max(((endOff - startOff) / totalDays) * 100, 2);
|
|
267
|
+
const pColor = PRIORITY_COLORS[card.priority] || 'var(--muted)';
|
|
268
|
+
const label = shortLabel(card.title);
|
|
269
|
+
|
|
270
|
+
return h('div', {
|
|
271
|
+
key: i,
|
|
272
|
+
style: { display: 'flex', alignItems: 'center', height: ROW_HEIGHT, marginBottom: '1px' },
|
|
273
|
+
},
|
|
274
|
+
// Left label: priority + short name + owners
|
|
275
|
+
h('div', {
|
|
276
|
+
style: {
|
|
277
|
+
width: LABEL_WIDTH, flexShrink: 0, display: 'flex', alignItems: 'center', gap: '6px',
|
|
278
|
+
paddingRight: '12px', overflow: 'hidden',
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
h('span', {
|
|
282
|
+
style: { fontFamily: fonts.mono, fontSize: '10px', color: pColor, fontWeight: 700, flexShrink: 0 },
|
|
283
|
+
}, PRIORITY_LABELS[card.priority]),
|
|
284
|
+
h('span', {
|
|
285
|
+
style: {
|
|
286
|
+
fontSize: '12px', color: 'var(--dark)', whiteSpace: 'nowrap',
|
|
287
|
+
overflow: 'hidden', textOverflow: 'ellipsis', flex: 1,
|
|
288
|
+
},
|
|
289
|
+
}, label),
|
|
290
|
+
h('span', {
|
|
291
|
+
style: { fontFamily: fonts.mono, fontSize: '10px', color: 'var(--muted)', flexShrink: 0, whiteSpace: 'nowrap' },
|
|
292
|
+
}, (card.owners || []).slice(0, 2).join(', ')),
|
|
293
|
+
),
|
|
294
|
+
// Gantt area
|
|
295
|
+
h('div', { style: { flex: 1, position: 'relative', height: '100%' } },
|
|
296
|
+
// Week grid lines
|
|
297
|
+
...weeks.map((w, wi) =>
|
|
298
|
+
h('div', {
|
|
299
|
+
key: `g-${wi}`,
|
|
300
|
+
style: {
|
|
301
|
+
position: 'absolute', left: `${(w.offset / totalDays) * 100}%`,
|
|
302
|
+
top: 0, bottom: 0, width: '1px', background: 'var(--border)', opacity: 0.3,
|
|
303
|
+
},
|
|
304
|
+
})
|
|
305
|
+
),
|
|
306
|
+
// Today line
|
|
307
|
+
todayPct >= 0 && todayPct <= 100 && h('div', {
|
|
308
|
+
style: {
|
|
309
|
+
position: 'absolute', left: `${todayPct}%`, top: 0, bottom: 0,
|
|
310
|
+
width: '2px', background: 'var(--terracotta)', opacity: 0.5, zIndex: 1,
|
|
311
|
+
},
|
|
312
|
+
}),
|
|
313
|
+
// Bar
|
|
314
|
+
h('div', {
|
|
315
|
+
style: {
|
|
316
|
+
position: 'absolute', left: `${barLeft}%`, width: `${barWidth}%`,
|
|
317
|
+
top: '6px', bottom: '6px', borderRadius: '4px',
|
|
318
|
+
background: pColor, opacity: card.status === 'done' ? 0.25 : 0.75,
|
|
319
|
+
minWidth: '6px',
|
|
320
|
+
},
|
|
321
|
+
}),
|
|
322
|
+
),
|
|
323
|
+
);
|
|
324
|
+
}),
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ── Summary Metrics ────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
function SummaryBar({ summary }) {
|
|
331
|
+
const { byPriority, byStatus, totalCards } = summary;
|
|
332
|
+
return h('div', {
|
|
333
|
+
style: {
|
|
334
|
+
display: 'flex', gap: '16px', flexWrap: 'wrap', padding: '12px 0',
|
|
335
|
+
fontFamily: fonts.mono, fontSize: '12px', color: 'var(--muted)',
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
h('span', { style: { fontWeight: 600, color: 'var(--black)' } }, `${totalCards} cards`),
|
|
339
|
+
byPriority.P0 > 0 && h('span', { style: { color: PRIORITY_COLORS[0] } }, `${byPriority.P0} P0`),
|
|
340
|
+
byPriority.P1 > 0 && h('span', { style: { color: PRIORITY_COLORS[1] } }, `${byPriority.P1} P1`),
|
|
341
|
+
byPriority.P2 > 0 && h('span', { style: { color: PRIORITY_COLORS[2] } }, `${byPriority.P2} P2`),
|
|
342
|
+
byPriority.P3 > 0 && h('span', null, `${byPriority.P3} P3`),
|
|
343
|
+
h('span', null, '·'),
|
|
344
|
+
h('span', null, `${byStatus['in-progress']} active`),
|
|
345
|
+
h('span', null, `${byStatus.todo} to do`),
|
|
346
|
+
byStatus.review > 0 && h('span', null, `${byStatus.review} in review`),
|
|
347
|
+
byStatus.done > 0 && h('span', null, `${byStatus.done} done`),
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ── Tab Switcher (rendered as HTML with inline JS) ─────────────
|
|
352
|
+
|
|
353
|
+
function ViewTabs() {
|
|
354
|
+
// The tabs work via a small inline script injected in the shell
|
|
355
|
+
return h('div', {
|
|
356
|
+
style: {
|
|
357
|
+
display: 'flex', gap: '4px', marginBottom: '1.5rem',
|
|
358
|
+
borderBottom: '2px solid var(--border)', paddingBottom: '0',
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
['Activity', 'Person', 'Timeline'].map(tab =>
|
|
362
|
+
h('button', {
|
|
363
|
+
key: tab,
|
|
364
|
+
className: 'eg-board-tab',
|
|
365
|
+
'data-view': tab.toLowerCase(),
|
|
366
|
+
style: {
|
|
367
|
+
fontFamily: fonts.mono, fontSize: '13px', padding: '8px 16px',
|
|
368
|
+
background: 'none', border: 'none', borderBottom: '2px solid transparent',
|
|
369
|
+
marginBottom: '-2px', cursor: 'pointer', color: 'var(--muted)',
|
|
370
|
+
},
|
|
371
|
+
}, tab)
|
|
372
|
+
),
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ── Main Template ──────────────────────────────────────────────
|
|
377
|
+
|
|
378
|
+
export function boardTemplate(data) {
|
|
379
|
+
const sections = [];
|
|
380
|
+
|
|
381
|
+
// Header
|
|
382
|
+
sections.push(
|
|
383
|
+
h(ArtifactHeader, {
|
|
384
|
+
key: 'header',
|
|
385
|
+
title: data.title,
|
|
386
|
+
type: 'board',
|
|
387
|
+
date: data.date,
|
|
388
|
+
author: data.updatedBy,
|
|
389
|
+
status: 'active',
|
|
390
|
+
priority: 0,
|
|
391
|
+
projects: [],
|
|
392
|
+
})
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
// Summary bar
|
|
396
|
+
sections.push(h(SummaryBar, { key: 'summary', summary: data.summary }));
|
|
397
|
+
|
|
398
|
+
// Tab switcher
|
|
399
|
+
sections.push(h(ViewTabs, { key: 'tabs' }));
|
|
400
|
+
|
|
401
|
+
// Activity view (default visible)
|
|
402
|
+
sections.push(
|
|
403
|
+
h('div', { key: 'view-activity', className: 'eg-board-view', 'data-view': 'activity' },
|
|
404
|
+
h(ActivityView, { activities: data.activities })
|
|
405
|
+
)
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
// Person view (hidden by default)
|
|
409
|
+
sections.push(
|
|
410
|
+
h('div', { key: 'view-person', className: 'eg-board-view', 'data-view': 'person', style: { display: 'none' } },
|
|
411
|
+
h(PersonView, { people: data.people })
|
|
412
|
+
)
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
// Timeline view (hidden by default)
|
|
416
|
+
sections.push(
|
|
417
|
+
h('div', { key: 'view-timeline', className: 'eg-board-view', 'data-view': 'timeline', style: { display: 'none' } },
|
|
418
|
+
h(TimelineView, { timeline: data.timeline, allCards: data.allCards })
|
|
419
|
+
)
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
// Inline script for tab switching
|
|
423
|
+
sections.push(
|
|
424
|
+
h('script', {
|
|
425
|
+
key: 'tabs-script',
|
|
426
|
+
dangerouslySetInnerHTML: {
|
|
427
|
+
__html: `
|
|
428
|
+
(function() {
|
|
429
|
+
var tabs = document.querySelectorAll('.eg-board-tab');
|
|
430
|
+
var views = document.querySelectorAll('.eg-board-view');
|
|
431
|
+
function activate(viewName) {
|
|
432
|
+
tabs.forEach(function(t) {
|
|
433
|
+
t.style.color = t.dataset.view === viewName ? 'var(--black)' : 'var(--muted)';
|
|
434
|
+
t.style.borderBottomColor = t.dataset.view === viewName ? 'var(--terracotta)' : 'transparent';
|
|
435
|
+
t.style.fontWeight = t.dataset.view === viewName ? '600' : '400';
|
|
436
|
+
});
|
|
437
|
+
views.forEach(function(v) {
|
|
438
|
+
v.style.display = v.dataset.view === viewName ? 'block' : 'none';
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
tabs.forEach(function(t) { t.addEventListener('click', function() { activate(t.dataset.view); }); });
|
|
442
|
+
activate('activity');
|
|
443
|
+
})();
|
|
444
|
+
`,
|
|
445
|
+
},
|
|
446
|
+
})
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
// Footer
|
|
450
|
+
sections.push(
|
|
451
|
+
h(ArtifactFooter, {
|
|
452
|
+
key: 'footer',
|
|
453
|
+
generatedAt: new Date().toISOString(),
|
|
454
|
+
source: 'memory/board/board.json',
|
|
455
|
+
})
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
return h('div', null, ...sections);
|
|
459
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Generic document → React element tree
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ArtifactHeader, SectionCard, TextBlock, ArtifactFooter } from '../components.js';
|
|
4
|
+
|
|
5
|
+
const h = React.createElement;
|
|
6
|
+
|
|
7
|
+
export function documentTemplate(doc) {
|
|
8
|
+
const sections = [];
|
|
9
|
+
|
|
10
|
+
// Header
|
|
11
|
+
sections.push(
|
|
12
|
+
h(ArtifactHeader, {
|
|
13
|
+
key: 'header',
|
|
14
|
+
title: doc.title,
|
|
15
|
+
type: 'document',
|
|
16
|
+
date: doc.date,
|
|
17
|
+
author: doc.author,
|
|
18
|
+
status: doc.status,
|
|
19
|
+
priority: 0,
|
|
20
|
+
projects: doc.frontmatter?.projects || [],
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Preamble (text before first ## heading)
|
|
25
|
+
if (doc.preamble) {
|
|
26
|
+
sections.push(
|
|
27
|
+
h(SectionCard, { key: 'preamble', label: null },
|
|
28
|
+
h(TextBlock, { text: doc.preamble, variant: 'lead' })
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Body sections
|
|
34
|
+
for (const section of doc.sections) {
|
|
35
|
+
sections.push(
|
|
36
|
+
h(SectionCard, { key: `s-${section.heading}`, label: section.heading },
|
|
37
|
+
h(TextBlock, { text: section.body })
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Footer
|
|
43
|
+
sections.push(
|
|
44
|
+
h(ArtifactFooter, {
|
|
45
|
+
key: 'footer',
|
|
46
|
+
generatedAt: new Date().toISOString(),
|
|
47
|
+
source: doc.source,
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return h('div', null, ...sections);
|
|
52
|
+
}
|
package/lib/templates/handoff.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
TextBlock, ArtifactFooter,
|
|
6
6
|
} from '../components.js';
|
|
7
7
|
import { renderMarkdownLite } from '../markdown.js';
|
|
8
|
-
import {
|
|
8
|
+
import { fonts } from '../tokens.js';
|
|
9
9
|
|
|
10
10
|
const h = React.createElement;
|
|
11
11
|
|
|
@@ -38,7 +38,7 @@ export function handoffTemplate(handoff) {
|
|
|
38
38
|
marginBottom: '1.5rem',
|
|
39
39
|
fontFamily: fonts.mono,
|
|
40
40
|
fontSize: '13px',
|
|
41
|
-
color:
|
|
41
|
+
color: 'var(--muted)',
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
44
|
h('span', null, 'To:'),
|
|
@@ -47,9 +47,9 @@ export function handoffTemplate(handoff) {
|
|
|
47
47
|
key: i,
|
|
48
48
|
style: {
|
|
49
49
|
padding: '2px 10px',
|
|
50
|
-
background: '
|
|
50
|
+
background: 'var(--blue-chip)',
|
|
51
51
|
borderRadius: '50px',
|
|
52
|
-
color:
|
|
52
|
+
color: 'var(--blue-muted)',
|
|
53
53
|
fontSize: '12px',
|
|
54
54
|
fontWeight: 500,
|
|
55
55
|
},
|
|
@@ -71,14 +71,14 @@ export function handoffTemplate(handoff) {
|
|
|
71
71
|
marginBottom: '2rem',
|
|
72
72
|
fontFamily: fonts.mono,
|
|
73
73
|
fontSize: '12px',
|
|
74
|
-
color:
|
|
74
|
+
color: 'var(--muted)',
|
|
75
75
|
},
|
|
76
76
|
},
|
|
77
77
|
handoff.source && h('span', null, `Source: ${handoff.source}`),
|
|
78
78
|
handoff.branch && h('span', {
|
|
79
79
|
style: {
|
|
80
80
|
padding: '2px 8px',
|
|
81
|
-
background: '
|
|
81
|
+
background: 'var(--subtle-fill)',
|
|
82
82
|
borderRadius: '4px',
|
|
83
83
|
fontFamily: fonts.mono,
|
|
84
84
|
},
|
|
@@ -143,7 +143,7 @@ export function handoffTemplate(handoff) {
|
|
|
143
143
|
display: 'flex',
|
|
144
144
|
gap: '12px',
|
|
145
145
|
padding: '8px 0',
|
|
146
|
-
borderBottom: i < handoff.nextSteps.length - 1 ? '1px solid
|
|
146
|
+
borderBottom: i < handoff.nextSteps.length - 1 ? '1px solid var(--hairline)' : 'none',
|
|
147
147
|
fontSize: '15px',
|
|
148
148
|
lineHeight: 1.5,
|
|
149
149
|
},
|
|
@@ -154,8 +154,8 @@ export function handoffTemplate(handoff) {
|
|
|
154
154
|
width: '24px',
|
|
155
155
|
height: '24px',
|
|
156
156
|
borderRadius: '50%',
|
|
157
|
-
background:
|
|
158
|
-
color:
|
|
157
|
+
background: 'var(--terracotta)',
|
|
158
|
+
color: 'var(--cream)',
|
|
159
159
|
display: 'flex',
|
|
160
160
|
alignItems: 'center',
|
|
161
161
|
justifyContent: 'center',
|