egregore-artifacts 0.3.0 → 0.4.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.
@@ -25,7 +25,29 @@ export function parseBoard(input) {
25
25
 
26
26
  const today = new Date().toISOString().split('T')[0];
27
27
 
28
- // Flatten all cards from the hierarchy for person and timeline views
28
+ // Compute most recent Monday for weekly Done-tab auto-cleanse
29
+ const now = new Date();
30
+ const dayOfWeek = now.getDay();
31
+ const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
32
+ const weekStart = new Date(now);
33
+ weekStart.setHours(0, 0, 0, 0);
34
+ weekStart.setDate(weekStart.getDate() - daysSinceMonday);
35
+ const weekStartStr = weekStart.toISOString().split('T')[0];
36
+
37
+ // Auto-archive: filter out done cards completed before this Monday
38
+ for (const activity of data.activities || []) {
39
+ const filterStale = (cards) => (cards || []).filter(
40
+ c => !(c.status === 'done' && c.completedAt && c.completedAt < weekStartStr)
41
+ );
42
+ if (activity.cards) activity.cards = filterStale(activity.cards);
43
+ if (activity.subactivities) {
44
+ for (const sub of activity.subactivities) {
45
+ if (sub.cards) sub.cards = filterStale(sub.cards);
46
+ }
47
+ }
48
+ }
49
+
50
+ // Flatten all cards from the hierarchy
29
51
  const allCards = [];
30
52
  for (const activity of data.activities || []) {
31
53
  if (activity.subactivities) {
@@ -40,9 +62,13 @@ export function parseBoard(input) {
40
62
  }
41
63
  }
42
64
 
43
- // Group by person
65
+ // Split active vs done for different views
66
+ const activeCards = allCards.filter(c => c.status !== 'done');
67
+ const doneCards = allCards.filter(c => c.status === 'done');
68
+
69
+ // Group active cards by person
44
70
  const personMap = {};
45
- for (const card of allCards) {
71
+ for (const card of activeCards) {
46
72
  for (const owner of card.owners || []) {
47
73
  if (!personMap[owner]) personMap[owner] = [];
48
74
  personMap[owner].push(card);
@@ -66,8 +92,8 @@ export function parseBoard(input) {
66
92
  }))
67
93
  .sort((a, b) => b.stats.p0 - a.stats.p0 || b.stats.total - a.stats.total);
68
94
 
69
- // Timeline: cards with dates, sorted by startDate
70
- const timeline = allCards
95
+ // Timeline: active cards with dates, sorted by startDate
96
+ const timeline = activeCards
71
97
  .filter(c => c.startDate || c.dueDate)
72
98
  .sort((a, b) => (a.startDate || a.dueDate || '').localeCompare(b.startDate || b.dueDate || ''));
73
99
 
@@ -102,6 +128,9 @@ export function parseBoard(input) {
102
128
  updatedAt: data.updated,
103
129
  activities: data.activities,
104
130
  allCards,
131
+ activeCards,
132
+ doneCards,
133
+ weekStart: weekStartStr,
105
134
  people,
106
135
  timeline,
107
136
  summary,
@@ -31,6 +31,11 @@ function BoardCardEl({ card, showActivity }) {
31
31
  : 'var(--muted)';
32
32
 
33
33
  return h('div', {
34
+ className: 'eg-board-card',
35
+ 'data-card-id': card.id,
36
+ 'data-original-status': card.status,
37
+ 'data-original-priority': card.priority,
38
+ 'data-original-owners': (card.owners || []).join(','),
34
39
  style: {
35
40
  background: 'var(--surface)',
36
41
  border: '1px solid var(--border)',
@@ -38,10 +43,21 @@ function BoardCardEl({ card, showActivity }) {
38
43
  borderRadius: '8px',
39
44
  padding: '12px 16px',
40
45
  marginBottom: '8px',
46
+ position: 'relative',
41
47
  },
42
48
  },
49
+ // Edit toggle (top-right)
50
+ h('button', {
51
+ className: 'eg-card-edit-btn',
52
+ style: {
53
+ position: 'absolute', top: '8px', right: '8px',
54
+ background: 'none', border: 'none', cursor: 'pointer',
55
+ fontFamily: fonts.mono, fontSize: '11px', color: 'var(--muted)',
56
+ padding: '2px 6px', borderRadius: '3px',
57
+ },
58
+ }, 'edit'),
43
59
  // Title row
44
- h('div', { style: { display: 'flex', alignItems: 'baseline', gap: '8px', marginBottom: '6px' } },
60
+ h('div', { style: { display: 'flex', alignItems: 'baseline', gap: '8px', marginBottom: '6px', paddingRight: '40px' } },
45
61
  h('span', {
46
62
  style: { fontFamily: fonts.mono, fontSize: '11px', color: pColor, fontWeight: 600, flexShrink: 0 },
47
63
  }, PRIORITY_LABELS[card.priority]),
@@ -85,6 +101,54 @@ function BoardCardEl({ card, showActivity }) {
85
101
  card.description && h('p', {
86
102
  style: { fontSize: '13px', color: 'var(--muted)', margin: '6px 0 0', lineHeight: 1.4 },
87
103
  }, card.description),
104
+ // Inline editor (hidden by default)
105
+ h('div', {
106
+ className: 'eg-card-editor',
107
+ style: {
108
+ display: 'none', marginTop: '10px', padding: '10px',
109
+ background: 'var(--neutral-chip)', borderRadius: '6px',
110
+ fontFamily: fonts.mono, fontSize: '12px',
111
+ },
112
+ },
113
+ h('div', { style: { display: 'flex', gap: '12px', alignItems: 'center', flexWrap: 'wrap' } },
114
+ h('label', null,
115
+ h('span', { style: { color: 'var(--muted)', marginRight: '6px' } }, 'status'),
116
+ h('select', {
117
+ className: 'eg-edit-status', 'data-card-id': card.id,
118
+ defaultValue: card.status,
119
+ style: { fontFamily: fonts.mono, fontSize: '12px', padding: '2px 4px' },
120
+ },
121
+ h('option', { value: 'todo' }, 'todo'),
122
+ h('option', { value: 'in-progress' }, 'in-progress'),
123
+ h('option', { value: 'review' }, 'review'),
124
+ h('option', { value: 'done' }, 'done'),
125
+ ),
126
+ ),
127
+ h('label', null,
128
+ h('span', { style: { color: 'var(--muted)', marginRight: '6px' } }, 'priority'),
129
+ h('select', {
130
+ className: 'eg-edit-priority', 'data-card-id': card.id,
131
+ defaultValue: String(card.priority),
132
+ style: { fontFamily: fonts.mono, fontSize: '12px', padding: '2px 4px' },
133
+ },
134
+ h('option', { value: '0' }, 'P0'),
135
+ h('option', { value: '1' }, 'P1'),
136
+ h('option', { value: '2' }, 'P2'),
137
+ h('option', { value: '3' }, 'P3'),
138
+ ),
139
+ ),
140
+ h('label', null,
141
+ h('span', { style: { color: 'var(--muted)', marginRight: '6px' } }, 'owners'),
142
+ h('input', {
143
+ className: 'eg-edit-owners', 'data-card-id': card.id,
144
+ type: 'text',
145
+ defaultValue: (card.owners || []).join(', '),
146
+ placeholder: 'comma-separated',
147
+ style: { fontFamily: fonts.mono, fontSize: '12px', padding: '2px 6px', width: '180px' },
148
+ }),
149
+ ),
150
+ ),
151
+ ),
88
152
  );
89
153
  }
90
154
 
@@ -96,7 +160,7 @@ function ActivityView({ activities }) {
96
160
  for (const activity of activities) {
97
161
  if (activity.subactivities) {
98
162
  for (const sub of activity.subactivities) {
99
- const cards = sub.cards || [];
163
+ const cards = (sub.cards || []).filter(c => c.status !== 'done');
100
164
  if (cards.length === 0) continue;
101
165
  sections.push(
102
166
  h('div', { key: `${activity.id}-${sub.id}`, style: { marginBottom: '1.5rem' } },
@@ -114,7 +178,7 @@ function ActivityView({ activities }) {
114
178
  );
115
179
  }
116
180
  }
117
- const cards = activity.cards || [];
181
+ const cards = (activity.cards || []).filter(c => c.status !== 'done');
118
182
  if (cards.length === 0) continue;
119
183
  sections.push(
120
184
  h('div', { key: activity.id, style: { marginBottom: '1.5rem' } },
@@ -137,6 +201,85 @@ function ActivityView({ activities }) {
137
201
 
138
202
  // ── Person View ────────────────────────────────────────────────
139
203
 
204
+ // ── Priority View ──────────────────────────────────────────────
205
+
206
+ function PriorityView({ activeCards }) {
207
+ const groups = [
208
+ { level: 0, label: 'P0 — Breaking', sub: 'Drop everything' },
209
+ { level: 1, label: 'P1 — This cycle', sub: 'The working set' },
210
+ { level: 2, label: 'P2 — Next cycle', sub: 'Scoped, waiting' },
211
+ { level: 3, label: 'P3 — Parked', sub: 'Captured for later' },
212
+ ];
213
+ return h('div', null,
214
+ ...groups.map(g => {
215
+ const cards = (activeCards || []).filter(c => c.priority === g.level);
216
+ if (cards.length === 0) return null;
217
+ const pColor = PRIORITY_COLORS[g.level];
218
+ return h('div', { key: g.level, style: { marginBottom: '1.5rem' } },
219
+ h('div', {
220
+ style: {
221
+ display: 'flex', alignItems: 'baseline', gap: '10px',
222
+ marginBottom: '8px', paddingBottom: '4px',
223
+ borderBottom: `2px solid ${pColor}`,
224
+ },
225
+ },
226
+ h('span', {
227
+ style: {
228
+ fontFamily: fonts.mono, fontSize: '13px', fontWeight: 700,
229
+ color: pColor, textTransform: 'uppercase', letterSpacing: '0.06em',
230
+ },
231
+ }, g.label),
232
+ h('span', {
233
+ style: { fontFamily: fonts.mono, fontSize: '11px', color: 'var(--muted)' },
234
+ }, `${cards.length} · ${g.sub}`),
235
+ ),
236
+ ...cards.map((card, i) => h(BoardCardEl, { key: i, card, showActivity: true })),
237
+ );
238
+ }).filter(Boolean),
239
+ );
240
+ }
241
+
242
+ // ── Done View ──────────────────────────────────────────────────
243
+
244
+ function DoneView({ doneCards, weekStart }) {
245
+ if (!doneCards || doneCards.length === 0) {
246
+ return h('div', { style: { padding: '2rem', textAlign: 'center', color: 'var(--muted)', fontStyle: 'italic' } },
247
+ `Nothing done yet this week. The Done tab clears every Monday.`
248
+ );
249
+ }
250
+ const byActivity = {};
251
+ for (const card of doneCards) {
252
+ const key = card.subactivity ? `${card.activity} · ${card.subactivity}` : card.activity;
253
+ if (!byActivity[key]) byActivity[key] = [];
254
+ byActivity[key].push(card);
255
+ }
256
+ return h('div', null,
257
+ h('div', {
258
+ style: {
259
+ fontFamily: fonts.mono, fontSize: '11px', color: 'var(--muted)',
260
+ marginBottom: '1rem', padding: '8px 12px', background: 'var(--neutral-chip)',
261
+ borderRadius: '6px',
262
+ },
263
+ }, `Showing ${doneCards.length} done this week (since ${weekStart}). This tab auto-clears every Monday.`),
264
+ ...Object.entries(byActivity).map(([activityLabel, cards], i) =>
265
+ h('div', { key: i, style: { marginBottom: '1.5rem' } },
266
+ h('div', {
267
+ style: {
268
+ fontFamily: fonts.mono, fontSize: '12px', textTransform: 'uppercase',
269
+ letterSpacing: '0.06em', color: 'var(--muted)', marginBottom: '8px', paddingBottom: '4px',
270
+ borderBottom: '1px solid var(--border)',
271
+ },
272
+ }, activityLabel),
273
+ ...cards.map((card, j) =>
274
+ h(BoardCardEl, { key: j, card, showActivity: false })
275
+ ),
276
+ )
277
+ ),
278
+ );
279
+ }
280
+
281
+ // ── Person View ────────────────────────────────────────────────
282
+
140
283
  function PersonView({ people }) {
141
284
  return h('div', null,
142
285
  ...people.map((person, pi) =>
@@ -350,25 +493,31 @@ function SummaryBar({ summary }) {
350
493
 
351
494
  // ── Tab Switcher (rendered as HTML with inline JS) ─────────────
352
495
 
353
- function ViewTabs() {
354
- // The tabs work via a small inline script injected in the shell
496
+ function ViewTabs({ doneCount }) {
497
+ const tabs = [
498
+ { key: 'activity', label: 'Activity' },
499
+ { key: 'priority', label: 'Priority' },
500
+ { key: 'person', label: 'Person' },
501
+ { key: 'timeline', label: 'Timeline' },
502
+ { key: 'done', label: `Done${doneCount > 0 ? ` (${doneCount})` : ''}` },
503
+ ];
355
504
  return h('div', {
356
505
  style: {
357
506
  display: 'flex', gap: '4px', marginBottom: '1.5rem',
358
507
  borderBottom: '2px solid var(--border)', paddingBottom: '0',
359
508
  },
360
509
  },
361
- ['Activity', 'Person', 'Timeline'].map(tab =>
510
+ ...tabs.map(tab =>
362
511
  h('button', {
363
- key: tab,
512
+ key: tab.key,
364
513
  className: 'eg-board-tab',
365
- 'data-view': tab.toLowerCase(),
514
+ 'data-view': tab.key,
366
515
  style: {
367
516
  fontFamily: fonts.mono, fontSize: '13px', padding: '8px 16px',
368
517
  background: 'none', border: 'none', borderBottom: '2px solid transparent',
369
518
  marginBottom: '-2px', cursor: 'pointer', color: 'var(--muted)',
370
519
  },
371
- }, tab)
520
+ }, tab.label)
372
521
  ),
373
522
  );
374
523
  }
@@ -396,7 +545,7 @@ export function boardTemplate(data) {
396
545
  sections.push(h(SummaryBar, { key: 'summary', summary: data.summary }));
397
546
 
398
547
  // Tab switcher
399
- sections.push(h(ViewTabs, { key: 'tabs' }));
548
+ sections.push(h(ViewTabs, { key: 'tabs', doneCount: (data.doneCards || []).length }));
400
549
 
401
550
  // Activity view (default visible)
402
551
  sections.push(
@@ -405,6 +554,13 @@ export function boardTemplate(data) {
405
554
  )
406
555
  );
407
556
 
557
+ // Priority view (hidden by default)
558
+ sections.push(
559
+ h('div', { key: 'view-priority', className: 'eg-board-view', 'data-view': 'priority', style: { display: 'none' } },
560
+ h(PriorityView, { activeCards: data.activeCards })
561
+ )
562
+ );
563
+
408
564
  // Person view (hidden by default)
409
565
  sections.push(
410
566
  h('div', { key: 'view-person', className: 'eg-board-view', 'data-view': 'person', style: { display: 'none' } },
@@ -419,6 +575,13 @@ export function boardTemplate(data) {
419
575
  )
420
576
  );
421
577
 
578
+ // Done view (hidden by default)
579
+ sections.push(
580
+ h('div', { key: 'view-done', className: 'eg-board-view', 'data-view': 'done', style: { display: 'none' } },
581
+ h(DoneView, { doneCards: data.doneCards || [], weekStart: data.weekStart })
582
+ )
583
+ );
584
+
422
585
  // Inline script for tab switching
423
586
  sections.push(
424
587
  h('script', {
@@ -446,6 +609,126 @@ export function boardTemplate(data) {
446
609
  })
447
610
  );
448
611
 
612
+ // Floating "Copy changes" button — live editor output
613
+ sections.push(
614
+ h('button', {
615
+ key: 'copy-changes-btn',
616
+ id: 'eg-copy-changes-btn',
617
+ style: {
618
+ position: 'fixed', bottom: '24px', right: '24px', zIndex: 1000,
619
+ background: 'var(--terracotta)', color: 'white', border: 'none',
620
+ borderRadius: '24px', padding: '12px 20px', fontFamily: fonts.mono,
621
+ fontSize: '13px', fontWeight: 600, cursor: 'pointer',
622
+ boxShadow: '0 4px 12px rgba(0,0,0,0.15)', display: 'none',
623
+ },
624
+ }, 'Copy 0 changes')
625
+ );
626
+
627
+ // Interactive editor script — toggle editors, sync across duplicate cards,
628
+ // track changes, produce paste-back command on click
629
+ sections.push(
630
+ h('script', {
631
+ key: 'editor-script',
632
+ dangerouslySetInnerHTML: {
633
+ __html: `
634
+ (function() {
635
+ var editBtns = document.querySelectorAll('.eg-card-edit-btn');
636
+ var copyBtn = document.getElementById('eg-copy-changes-btn');
637
+
638
+ // Toggle editor — scope to clicked card's parent (each card appears in
639
+ // multiple views, so global querySelector would target the wrong one)
640
+ editBtns.forEach(function(btn) {
641
+ btn.addEventListener('click', function() {
642
+ var cardEl = btn.closest('.eg-board-card');
643
+ if (!cardEl) return;
644
+ var editor = cardEl.querySelector('.eg-card-editor');
645
+ if (!editor) return;
646
+ var isOpen = editor.style.display === 'block';
647
+ editor.style.display = isOpen ? 'none' : 'block';
648
+ btn.textContent = isOpen ? 'edit' : 'close';
649
+ });
650
+ });
651
+
652
+ // Sync edits across duplicate card instances (same card in Activity + Priority + Person)
653
+ function syncEdit(event) {
654
+ var input = event.target;
655
+ if (!input.classList) return;
656
+ var cls = input.className || '';
657
+ if (!cls.includes('eg-edit-')) return;
658
+ var cardId = input.dataset.cardId;
659
+ if (!cardId) return;
660
+ var className = cls.split(' ').find(function(c){return c.startsWith('eg-edit-');});
661
+ var selector = '.' + className + '[data-card-id="' + cardId + '"]';
662
+ document.querySelectorAll(selector).forEach(function(el) {
663
+ if (el !== input) el.value = input.value;
664
+ });
665
+ }
666
+ document.addEventListener('change', syncEdit);
667
+ document.addEventListener('input', syncEdit);
668
+
669
+ function collectChanges() {
670
+ var changes = [];
671
+ var seen = {};
672
+ document.querySelectorAll('.eg-board-card').forEach(function(card) {
673
+ var id = card.dataset.cardId;
674
+ if (seen[id]) return;
675
+ seen[id] = true;
676
+ var origStatus = card.dataset.originalStatus;
677
+ var origPriority = card.dataset.originalPriority;
678
+ var origOwners = card.dataset.originalOwners;
679
+ var statusEl = card.querySelector('.eg-edit-status');
680
+ var priorityEl = card.querySelector('.eg-edit-priority');
681
+ var ownersEl = card.querySelector('.eg-edit-owners');
682
+ if (!statusEl) return;
683
+ var newStatus = statusEl.value;
684
+ var newPriority = priorityEl.value;
685
+ var newOwners = ownersEl.value.split(',').map(function(s){return s.trim();}).filter(Boolean).join(',');
686
+ var cardChange = { id: id, changes: [] };
687
+ if (newStatus !== origStatus) cardChange.changes.push('status → ' + newStatus);
688
+ if (newPriority !== origPriority) cardChange.changes.push('priority → P' + newPriority);
689
+ if (newOwners !== origOwners) cardChange.changes.push('owners → [' + newOwners + ']');
690
+ if (cardChange.changes.length > 0) changes.push(cardChange);
691
+ });
692
+ return changes;
693
+ }
694
+
695
+ function updateCopyBtn() {
696
+ var changes = collectChanges();
697
+ if (changes.length === 0) {
698
+ copyBtn.style.display = 'none';
699
+ return;
700
+ }
701
+ copyBtn.style.display = 'block';
702
+ var total = changes.reduce(function(n, c) { return n + c.changes.length; }, 0);
703
+ copyBtn.textContent = 'Copy ' + total + ' change' + (total === 1 ? '' : 's');
704
+ }
705
+ document.addEventListener('change', updateCopyBtn);
706
+ document.addEventListener('input', updateCopyBtn);
707
+
708
+ copyBtn.addEventListener('click', function() {
709
+ var changes = collectChanges();
710
+ var lines = ['Apply these board changes:', ''];
711
+ changes.forEach(function(c) {
712
+ lines.push('- ' + c.id + ':');
713
+ c.changes.forEach(function(ch) { lines.push(' ' + ch); });
714
+ });
715
+ var text = lines.join('\\n');
716
+ navigator.clipboard.writeText(text).then(function() {
717
+ var original = copyBtn.textContent;
718
+ copyBtn.textContent = '✓ Copied — paste in Claude';
719
+ copyBtn.style.background = '#2e7d32';
720
+ setTimeout(function() {
721
+ copyBtn.textContent = original;
722
+ copyBtn.style.background = 'var(--terracotta)';
723
+ }, 2000);
724
+ });
725
+ });
726
+ })();
727
+ `,
728
+ },
729
+ })
730
+ );
731
+
449
732
  // Footer
450
733
  sections.push(
451
734
  h(ArtifactFooter, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "egregore-artifacts",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Generate branded HTML artifacts from Egregore data",
5
5
  "type": "module",
6
6
  "license": "MIT",