create-pmos 0.107.0 → 0.108.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/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/guide/notes.md +6 -4
- package/workspace/lib/notes.js +98 -1
- package/workspace/lib/routes-notes.js +32 -11
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/app.js +2 -2
- package/workspace/public/pm/components.css +315 -163
- package/workspace/public/pm/dest-notes.js +383 -0
- package/workspace/public/pm/frame-copy.js +1 -0
- package/workspace/public/pm/note-dock.js +68 -22
- package/workspace/public/pm/notes-links.js +3 -3
- package/workspace/public/pm/notes-task.js +36 -0
- package/workspace/public/pm/panel-cockpit.js +1 -2
- package/workspace/public/pm/registry.js +2 -0
- package/workspace/public/pm/topbar.js +42 -17
- package/workspace/public/pm/view-notes.js +120 -158
- package/workspace/public/vendor/lucide-sprite.js +8 -2
- package/workspace/test-files.json +3 -1
- package/workspace/third-party/README.md +1 -1
- package/workspace/public/pm/view-notes-menu.js +0 -317
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
/* pm/view-notes-menu.js — the Notes menu: all notes grouped by initiative (Run 11, R47).
|
|
2
|
-
*
|
|
3
|
-
* Two-column layout:
|
|
4
|
-
* left all notes grouped by initiative; click an initiative title to collapse/expand;
|
|
5
|
-
* click a note to open its detail on the right
|
|
6
|
-
* right the note detail (same single column as the full-screen note, Run 7)
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { el, setChildren } from './dom.js';
|
|
10
|
-
import { askConfirm } from './dialogs.js';
|
|
11
|
-
import { pmGet, pmPost } from './http.js';
|
|
12
|
-
import { icon } from './icons.js';
|
|
13
|
-
import { button, iconButton, empty, toast, ensureComponentsCss } from './primitives.js';
|
|
14
|
-
import * as viewNotes from './view-notes.js';
|
|
15
|
-
|
|
16
|
-
ensureComponentsCss();
|
|
17
|
-
|
|
18
|
-
let cache = new Map();
|
|
19
|
-
let list = null;
|
|
20
|
-
let selectedId = null;
|
|
21
|
-
let expandedInitiatives = new Set();
|
|
22
|
-
|
|
23
|
-
async function loadList() {
|
|
24
|
-
const r = await pmGet('/api/notes');
|
|
25
|
-
if (r && r.ok) {
|
|
26
|
-
list = { notes: r.notes || [] };
|
|
27
|
-
} else {
|
|
28
|
-
list = { notes: [], error: (r && r.error) || 'the notes could not be read' };
|
|
29
|
-
}
|
|
30
|
-
return list;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function loadNote(id) {
|
|
34
|
-
const r = await pmGet('/api/notes/one?id=' + encodeURIComponent(id));
|
|
35
|
-
if (r && r.ok) {
|
|
36
|
-
cache.set(id, r.note);
|
|
37
|
-
return r.note;
|
|
38
|
-
}
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function groupByInitiative(notes) {
|
|
43
|
-
const groups = {};
|
|
44
|
-
notes.forEach((n) => {
|
|
45
|
-
// a note's initiative is a link object ({ id, title, by }), never a bare string
|
|
46
|
-
const key = n.initiative ? (n.initiative.title || n.initiative.id) : '(no initiative)';
|
|
47
|
-
if (!groups[key]) groups[key] = [];
|
|
48
|
-
groups[key].push(n);
|
|
49
|
-
});
|
|
50
|
-
return Object.entries(groups).sort((a, b) => {
|
|
51
|
-
if (a[0] === '(no initiative)') return 1;
|
|
52
|
-
if (b[0] === '(no initiative)') return -1;
|
|
53
|
-
return a[0].localeCompare(b[0]);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function renderNoteRow(note, isSelected, onClick) {
|
|
58
|
-
// the list row carries summary_lines (up to three plain lines); its `summary` is only a has-a-summary flag
|
|
59
|
-
const summaryText = (note.summary_lines || []).slice(0, 2).join(' ').substring(0, 60);
|
|
60
|
-
|
|
61
|
-
return el('div', {
|
|
62
|
-
class: 'nt-menu-row' + (isSelected ? ' nt-menu-row--selected' : ''),
|
|
63
|
-
'data-nt-menu-row': note.id,
|
|
64
|
-
onClick,
|
|
65
|
-
},
|
|
66
|
-
el('div', { class: 'nt-menu-row-title' }, note.title || 'Untitled note'),
|
|
67
|
-
el('div', { class: 'nt-menu-row-date' }, new Date(note.updated_at).toLocaleDateString()),
|
|
68
|
-
summaryText && el('div', { class: 'nt-menu-row-summary' }, summaryText),
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** The note's summary as plain text: the agent's markdown, else the local overview lines. */
|
|
73
|
-
function summaryOf(note) {
|
|
74
|
-
if (note.agent && note.agent.markdown) return String(note.agent.markdown);
|
|
75
|
-
const ov = note.summary && note.summary.sections && note.summary.sections.overview;
|
|
76
|
-
return Array.isArray(ov) ? ov.map((x) => String((x && x.text) || '')).filter(Boolean).join('\n') : '';
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function renderLeftPanel(notes, selectedId, onSelectNote, onToggleInitiative) {
|
|
80
|
-
const groups = groupByInitiative(notes);
|
|
81
|
-
|
|
82
|
-
return el('div', { class: 'nt-menu-left' },
|
|
83
|
-
...groups.map(([initiative, initiativeNotes]) => {
|
|
84
|
-
const isExpanded = expandedInitiatives.has(initiative);
|
|
85
|
-
return el('div', { class: 'nt-menu-group' },
|
|
86
|
-
el('div', {
|
|
87
|
-
class: 'nt-menu-group-title',
|
|
88
|
-
onClick: () => onToggleInitiative(initiative),
|
|
89
|
-
},
|
|
90
|
-
icon(isExpanded ? 'chevron-down' : 'chevron-right', { class: 'nt-menu-group-icon' }),
|
|
91
|
-
el('span', {}, initiative),
|
|
92
|
-
),
|
|
93
|
-
isExpanded && el('div', { class: 'nt-menu-group-items' },
|
|
94
|
-
...initiativeNotes.map((note) =>
|
|
95
|
-
renderNoteRow(note, note.id === selectedId, () => onSelectNote(note.id)),
|
|
96
|
-
),
|
|
97
|
-
),
|
|
98
|
-
);
|
|
99
|
-
}),
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// the cockpit calls renderBoard(inner, ctx) and ignores what it returns: the view draws into `inner`
|
|
104
|
-
export function renderBoard(inner, ctx) {
|
|
105
|
-
ensureComponentsCss();
|
|
106
|
-
const root = el('div', { class: 'nt-menu' });
|
|
107
|
-
inner.appendChild(root);
|
|
108
|
-
|
|
109
|
-
async function render() {
|
|
110
|
-
if (!list) await loadList();
|
|
111
|
-
const note = selectedId ? cache.get(selectedId) : null;
|
|
112
|
-
|
|
113
|
-
const leftPanel = renderLeftPanel(
|
|
114
|
-
list.notes || [],
|
|
115
|
-
selectedId,
|
|
116
|
-
async (id) => {
|
|
117
|
-
selectedId = id;
|
|
118
|
-
await loadNote(id);
|
|
119
|
-
render();
|
|
120
|
-
},
|
|
121
|
-
(initiative) => {
|
|
122
|
-
if (expandedInitiatives.has(initiative)) {
|
|
123
|
-
expandedInitiatives.delete(initiative);
|
|
124
|
-
} else {
|
|
125
|
-
expandedInitiatives.add(initiative);
|
|
126
|
-
}
|
|
127
|
-
render();
|
|
128
|
-
},
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
const rightPanel = note
|
|
132
|
-
? await (async () => {
|
|
133
|
-
try {
|
|
134
|
-
const noteModule = await import('./view-notes.js');
|
|
135
|
-
// Parse sections from the note's data
|
|
136
|
-
let actions = [];
|
|
137
|
-
let decisions = [];
|
|
138
|
-
let questions = [];
|
|
139
|
-
let highlights = [];
|
|
140
|
-
let conflicts = [];
|
|
141
|
-
if (note.final_state) {
|
|
142
|
-
if (note.final_state.actions) actions = note.final_state.actions;
|
|
143
|
-
if (note.final_state.decisions) decisions = note.final_state.decisions;
|
|
144
|
-
if (note.final_state.questions) questions = note.final_state.questions;
|
|
145
|
-
}
|
|
146
|
-
if (note.highlights) highlights = note.highlights;
|
|
147
|
-
if (note.conflicts) conflicts = note.conflicts;
|
|
148
|
-
|
|
149
|
-
const parts = [];
|
|
150
|
-
|
|
151
|
-
// Header with title, date, initiative, and action buttons
|
|
152
|
-
parts.push(
|
|
153
|
-
el('div', { class: 'nt-detail-header' },
|
|
154
|
-
el('div', { class: 'nt-detail-title-bar' },
|
|
155
|
-
el('div', { class: 'nt-detail-title' }, note.title || 'Untitled note'),
|
|
156
|
-
el('div', { class: 'nt-detail-actions-bar' },
|
|
157
|
-
iconButton({ icon: 'play', ariaLabel: 'Resume recording', attrs: { class: 'nt-detail-resume' } }),
|
|
158
|
-
el('div', { class: 'nt-menu-dots' },
|
|
159
|
-
iconButton({ icon: 'more-vertical', ariaLabel: 'More options', attrs: { class: 'nt-detail-more' } })
|
|
160
|
-
)
|
|
161
|
-
)
|
|
162
|
-
),
|
|
163
|
-
el('div', { class: 'nt-detail-meta' },
|
|
164
|
-
el('span', { class: 'nt-detail-date' }, new Date(note.updated_at).toLocaleDateString()),
|
|
165
|
-
note.initiative && el('span', { class: 'nt-detail-initiative' }, typeof note.initiative === 'string' ? note.initiative : (note.initiative.title || note.initiative.id)),
|
|
166
|
-
),
|
|
167
|
-
)
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
// Summary/Overview
|
|
171
|
-
parts.push(
|
|
172
|
-
el('div', { class: 'nt-detail-summary' },
|
|
173
|
-
el('h3', { class: 'nt-detail-section-title' }, 'Overview'),
|
|
174
|
-
note.summary
|
|
175
|
-
? el('div', { class: 'nt-detail-summary-text' }, note.summary)
|
|
176
|
-
: el('p', { class: 'nt-hint' }, 'No summary yet'),
|
|
177
|
-
)
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
// Conflicts section
|
|
181
|
-
if (conflicts && conflicts.length > 0) {
|
|
182
|
-
parts.push(
|
|
183
|
-
el('div', { class: 'nt-detail-conflicts' },
|
|
184
|
-
el('h3', { class: 'nt-detail-section-title' }, `Conflicts (${conflicts.length})`),
|
|
185
|
-
el('div', { class: 'nt-detail-conflicts-list' },
|
|
186
|
-
...conflicts.map((conflict) =>
|
|
187
|
-
el('div', { class: 'nt-detail-conflict-item' },
|
|
188
|
-
el('div', { class: 'nt-conflict-text' }, conflict.text || ''),
|
|
189
|
-
el('div', { class: 'nt-conflict-details' },
|
|
190
|
-
conflict.file && el('span', { class: 'nt-conflict-file' }, conflict.file),
|
|
191
|
-
iconButton({ icon: 'eye', ariaLabel: 'Review conflict', attrs: { class: 'nt-conflict-review' } })
|
|
192
|
-
)
|
|
193
|
-
)
|
|
194
|
-
)
|
|
195
|
-
)
|
|
196
|
-
)
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Highlights section
|
|
201
|
-
if (highlights && highlights.length > 0) {
|
|
202
|
-
parts.push(
|
|
203
|
-
el('div', { class: 'nt-detail-highlights' },
|
|
204
|
-
el('h3', { class: 'nt-detail-section-title' }, `Highlights (${highlights.length})`),
|
|
205
|
-
el('div', { class: 'nt-detail-highlights-list' },
|
|
206
|
-
...highlights.map((h) =>
|
|
207
|
-
el('div', { class: 'nt-detail-highlight-item' },
|
|
208
|
-
el('div', { class: 'nt-highlight-text' }, h.text || ''),
|
|
209
|
-
el('div', { class: 'nt-highlight-meta' },
|
|
210
|
-
el('span', { class: 'nt-highlight-label' }, h.label || ''),
|
|
211
|
-
h.timestamp && el('span', { class: 'nt-highlight-time' }, h.timestamp),
|
|
212
|
-
iconButton({ icon: 'play', ariaLabel: 'Play audio', attrs: { class: 'nt-highlight-play' } })
|
|
213
|
-
)
|
|
214
|
-
)
|
|
215
|
-
)
|
|
216
|
-
)
|
|
217
|
-
)
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Decisions section
|
|
222
|
-
if (decisions && decisions.length > 0) {
|
|
223
|
-
parts.push(
|
|
224
|
-
el('div', { class: 'nt-detail-decisions' },
|
|
225
|
-
el('h3', { class: 'nt-detail-section-title' }, `Decisions (${decisions.length})`),
|
|
226
|
-
el('div', { class: 'nt-detail-decisions-list' },
|
|
227
|
-
...decisions.map((decision, idx) =>
|
|
228
|
-
el('div', { class: 'nt-detail-decision-item' },
|
|
229
|
-
el('div', { class: 'nt-decision-text' }, decision.text || decision),
|
|
230
|
-
el('div', { class: 'nt-decision-actions' },
|
|
231
|
-
iconButton({ icon: 'edit', ariaLabel: 'Edit decision', attrs: { class: 'nt-decision-edit' } })
|
|
232
|
-
)
|
|
233
|
-
)
|
|
234
|
-
)
|
|
235
|
-
)
|
|
236
|
-
)
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Open questions section
|
|
241
|
-
if (questions && questions.length > 0) {
|
|
242
|
-
parts.push(
|
|
243
|
-
el('div', { class: 'nt-detail-questions' },
|
|
244
|
-
el('h3', { class: 'nt-detail-section-title' }, `Open questions (${questions.length})`),
|
|
245
|
-
el('div', { class: 'nt-detail-questions-list' },
|
|
246
|
-
...questions.map((question) =>
|
|
247
|
-
el('div', { class: 'nt-detail-question-item' },
|
|
248
|
-
el('div', { class: 'nt-question-text' }, question.text || question),
|
|
249
|
-
el('div', { class: 'nt-question-actions' },
|
|
250
|
-
button({ label: 'Draft answer', variant: 'ghost', size: 'sm' })
|
|
251
|
-
)
|
|
252
|
-
)
|
|
253
|
-
)
|
|
254
|
-
)
|
|
255
|
-
)
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Actions/Next steps section
|
|
260
|
-
if (actions && actions.length > 0) {
|
|
261
|
-
parts.push(
|
|
262
|
-
el('div', { class: 'nt-detail-actions' },
|
|
263
|
-
el('h3', { class: 'nt-detail-section-title' }, `Next steps (${actions.length})`),
|
|
264
|
-
el('div', { class: 'nt-detail-actions-list' },
|
|
265
|
-
...actions.map((action) =>
|
|
266
|
-
el('div', { class: 'nt-detail-action-row' },
|
|
267
|
-
el('div', { class: 'nt-detail-action-text' }, action.text || ''),
|
|
268
|
-
el('div', { class: 'nt-action-meta' },
|
|
269
|
-
action.owner && el('span', { class: 'nt-action-owner' }, `Owner: ${action.owner}`),
|
|
270
|
-
action.due && el('span', { class: 'nt-action-due' }, `Due: ${action.due}`),
|
|
271
|
-
),
|
|
272
|
-
iconButton({
|
|
273
|
-
icon: 'plus',
|
|
274
|
-
ariaLabel: 'Create task',
|
|
275
|
-
attrs: { class: 'nt-detail-make-task' },
|
|
276
|
-
onClick: async () => {
|
|
277
|
-
try {
|
|
278
|
-
const result = await pmPost('/api/notes/create-task', {
|
|
279
|
-
id: note.id,
|
|
280
|
-
text: action.text,
|
|
281
|
-
owner: action.owner,
|
|
282
|
-
due: action.due,
|
|
283
|
-
});
|
|
284
|
-
if (result && result.ok) {
|
|
285
|
-
toast({ text: 'Task created' });
|
|
286
|
-
} else {
|
|
287
|
-
toast({ text: 'Could not create task' });
|
|
288
|
-
}
|
|
289
|
-
} catch (e) {
|
|
290
|
-
console.error('Task creation error:', e);
|
|
291
|
-
toast({ text: 'Error creating task' });
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
}),
|
|
295
|
-
)
|
|
296
|
-
)
|
|
297
|
-
)
|
|
298
|
-
)
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Files section (placeholder for future)
|
|
303
|
-
// Discussion/Transcript section (collapsible placeholder)
|
|
304
|
-
|
|
305
|
-
return el('div', { class: 'nt-menu-right' }, ...parts);
|
|
306
|
-
} catch (e) {
|
|
307
|
-
console.error('Note detail error:', e);
|
|
308
|
-
return el('div', { class: 'nt-menu-right' }, empty({ text: 'Could not load note detail' }));
|
|
309
|
-
}
|
|
310
|
-
})()
|
|
311
|
-
: el('div', { class: 'nt-menu-right' }, empty({ text: 'Select a note to view details' }));
|
|
312
|
-
|
|
313
|
-
setChildren(root, [leftPanel, rightPanel]);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
render();
|
|
317
|
-
}
|