create-pmos 0.107.0 → 0.109.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/layout.md +2 -2
- 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/dest-settings.js +9 -2
- package/workspace/public/pm/dock-sessions.js +9 -2
- package/workspace/public/pm/dock.js +11 -0
- package/workspace/public/pm/frame-copy.js +2 -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/panel-newsess.js +5 -1
- 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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* pm/notes-task.js — Make task: a meeting note's next step as a plan item of the note's initiative (meeting-notes Run 12,
|
|
2
|
+
* mockup board 3 "Next step to task in one click").
|
|
3
|
+
*
|
|
4
|
+
* makeTask(note, { text, owner?, due? }, { repo?, btn? }) → Promise<item id | null>
|
|
5
|
+
*
|
|
6
|
+
* One press, no form: the item goes into the initiative's first stage through the Breakdown's own writer (POST
|
|
7
|
+
* /api/plan-items, as the PM), with the owner when the line names one and the due day when it is a date. A toast says
|
|
8
|
+
* where it went and offers Undo (the item is withdrawn). A note with no initiative gets a toast that says to link one.
|
|
9
|
+
*/
|
|
10
|
+
import { toast, SEL } from './primitives.js';
|
|
11
|
+
import { pmGet, pmPost } from './http.js';
|
|
12
|
+
import { wsOf } from './data.js';
|
|
13
|
+
|
|
14
|
+
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
15
|
+
const iniName = (i) => (i ? i.title || i.id : null);
|
|
16
|
+
const label = (btn, text, done) => { if (!btn) return; const l = btn.querySelector(SEL.btnLabel); if (l) l.textContent = text; btn.disabled = !!done; };
|
|
17
|
+
|
|
18
|
+
export async function makeTask(n, x, { repo, btn } = {}) {
|
|
19
|
+
const ini = n.initiative;
|
|
20
|
+
const ws = ini && (ini.ws || n.ws || (repo ? wsOf(repo) : null));
|
|
21
|
+
if (!ini || !ini.id || !ws) { toast({ text: 'Link the note to an initiative first. Make task adds the step to its breakdown.' }); return null; }
|
|
22
|
+
if (btn) btn.disabled = true;
|
|
23
|
+
const lay = await pmGet('/api/plan-items?ladder=0&ws=' + encodeURIComponent(ws) + '&id=' + encodeURIComponent(ini.id));
|
|
24
|
+
const stage = lay && lay.ok && (lay.stages || [])[0];
|
|
25
|
+
if (!stage) { if (btn) btn.disabled = false; toast({ text: 'The task was not made: ' + ((lay && lay.error) || 'the initiative has no stage'), tone: 'destructive' }); return null; }
|
|
26
|
+
const ev = { ev: 'planned', initiative: ini.id, title: String(x.text || '').slice(0, 200), stage: stage.id, note: 'From the meeting note "' + (n.title || 'Untitled note') + '"' };
|
|
27
|
+
if (x.owner) ev.owner = /^(you|me)$/i.test(x.owner) ? 'you' : 'person:' + String(x.owner).slice(0, 60);
|
|
28
|
+
if (x.due && DATE_RE.test(x.due)) ev.due = x.due;
|
|
29
|
+
const r = await pmPost('/api/plan-items', { ws, events: [ev] });
|
|
30
|
+
if (!r || !r.ok) { if (btn) btn.disabled = false; toast({ text: 'The task was not made' + ((r && r.error) ? ': ' + r.error : ''), tone: 'destructive' }); return null; }
|
|
31
|
+
const item = (r.written && r.written[0] && r.written[0].item) || null;
|
|
32
|
+
label(btn, 'Task made', true);
|
|
33
|
+
toast({ text: 'Added to ' + iniName(ini) + ', under ' + (stage.label || stage.id), actionLabel: item ? 'Undo' : undefined,
|
|
34
|
+
onAction: item ? async () => { const u = await pmPost('/api/plan-items', { ws, events: [{ ev: 'withdrawn', item, initiative: ini.id }] }); if (u && u.ok) label(btn, 'Make task', false); } : undefined });
|
|
35
|
+
return item;
|
|
36
|
+
}
|
|
@@ -47,7 +47,6 @@ export const VIEWS = {
|
|
|
47
47
|
// meeting-notes Run 1: typed notes, dictation, meeting transcripts and summaries; the PM's own, the same in every repository
|
|
48
48
|
notes: { label: 'notes', mode: null, scoped: false, palette: true, hint: 'type, dictate or record a meeting' },
|
|
49
49
|
// meeting-notes Run 11: notes menu with two-column view grouped by initiative (R47)
|
|
50
|
-
notesmenu: { label: 'notes menu', mode: null, scoped: false, hint: 'view and organize all notes by initiative' },
|
|
51
50
|
};
|
|
52
51
|
|
|
53
52
|
/** The view a list mode opens as, or null when the mode is not a view. `product` is the list itself. */
|
|
@@ -97,7 +96,7 @@ function modes() {
|
|
|
97
96
|
* every `data-*` hook the suites use, and only adds the board's frame around it. A view with no board module draws as before. */
|
|
98
97
|
export const BOARD_MODULES = {
|
|
99
98
|
today: './view-today.js', validate: './view-validate.js', addrepo: './view-addrepo.js', livenotes: './view-livenotes.js',
|
|
100
|
-
notes: './view-notes.js',
|
|
99
|
+
notes: './view-notes.js',
|
|
101
100
|
};
|
|
102
101
|
const boardMods = {}; // view -> undefined (not tried) · null (loading or absent) · module
|
|
103
102
|
function boardOf(view) {
|
|
@@ -34,7 +34,7 @@ import { state, setState, FIXTURE } from './state.js';
|
|
|
34
34
|
import * as FX from './fixture.js';
|
|
35
35
|
import { openTerm as openTermTab } from './panel-initiatives.js';
|
|
36
36
|
import { startHere } from './panel-trees.js';
|
|
37
|
-
import { sessionDefaults } from './dock.js';
|
|
37
|
+
import { sessionDefaults, setSessionDefaults, adoptProfileDefault } from './dock.js';
|
|
38
38
|
import { button } from './primitives.js';
|
|
39
39
|
import { flagPicker, flagsLine, flagsMissingValue, flagsWire } from './flag-picker.js';
|
|
40
40
|
|
|
@@ -184,6 +184,8 @@ function refreshReads() {
|
|
|
184
184
|
}
|
|
185
185
|
if (typeof dataMod.loadProfiles === 'function') {
|
|
186
186
|
dataMod.loadProfiles().then((r) => {
|
|
187
|
+
// the server's Claude default wins over this browser's copy (dock.js adoptProfileDefault)
|
|
188
|
+
adoptProfileDefault(r);
|
|
187
189
|
setState({ profiles: r || null });
|
|
188
190
|
// Preselect from the server's remembered preference — never from a hardcoded name. A
|
|
189
191
|
// preference naming a descriptor that no longer reads preselects NOTHING rather than
|
|
@@ -414,6 +416,8 @@ async function setDefault() {
|
|
|
414
416
|
if (!dataMod || typeof dataMod.preferProfile !== 'function') return;
|
|
415
417
|
const r = await dataMod.preferProfile(state.newSessAgent || 'shell', state.preflightProfile === undefined ? null : state.preflightProfile);
|
|
416
418
|
if (!r || !r.ok) { setState({ newSessError: (r && r.error) || 'the default could not be saved' }); return; }
|
|
419
|
+
// the Claude default is one default: Settings and a dock start read it too, so they follow this choice
|
|
420
|
+
if ((state.newSessAgent || 'shell') === 'claude') setSessionDefaults({ profile: state.preflightProfile || null });
|
|
417
421
|
setState((s) => ({ newSessError: null, profiles: s.profiles ? { ...s.profiles, prefs: r.prefs || {} } : s.profiles }));
|
|
418
422
|
}
|
|
419
423
|
|
|
@@ -53,6 +53,8 @@ export const DESTINATIONS = Object.freeze([
|
|
|
53
53
|
// Not a pm-os-absorb run (run: null): docs-three-way-sync added it — is each linked document the same as its copies.
|
|
54
54
|
Object.freeze({ id: 'docs', label: 'Docs', key: 'g d', module: 'dest-docs.js', run: null }),
|
|
55
55
|
// Not a pm-os-absorb run (run: null): people-directory added it — the people the PM works with in a repository.
|
|
56
|
+
// Not a pm-os-absorb run (run: null): meeting-notes Run 12 added it — every meeting note by initiative, and the note.
|
|
57
|
+
Object.freeze({ id: 'notes', label: 'Notes', key: 'g n', module: 'dest-notes.js', run: null }),
|
|
56
58
|
Object.freeze({ id: 'people', label: 'People', key: 'g p', module: 'dest-people.js', run: null }),
|
|
57
59
|
Object.freeze({ id: 'settings', label: 'Settings', key: null, module: 'dest-settings.js', run: 3 }),
|
|
58
60
|
]);
|
|
@@ -21,13 +21,16 @@
|
|
|
21
21
|
import { el, setChildren } from './dom.js';
|
|
22
22
|
import { button, needsDot, needsCount } from './primitives.js';
|
|
23
23
|
import { COPY } from './frame-copy.js';
|
|
24
|
-
import
|
|
24
|
+
import * as dock from './note-dock.js';
|
|
25
|
+
import { iconButton } from './primitives.js';
|
|
26
|
+
import { el as h } from './dom.js';
|
|
25
27
|
|
|
26
28
|
/** The destinations the bar names, in order. Settings sits at the end of the bar, after Sync. Docs joined the bar at the
|
|
27
29
|
* PM's word (2026-10-06, docs-three-way-sync: "I thought Docs is one of the main menu"). People joined after Docs when the
|
|
28
30
|
* PM approved the People mockup and said build it (2026-10-06, people-directory). My work joined beside Agenda when the PM
|
|
29
|
-
* approved the mockup that puts it there and said build it (2026-10-07, my-work)
|
|
30
|
-
|
|
31
|
+
* approved the mockup that puts it there and said build it (2026-10-07, my-work). Notes joined between Docs and People with the
|
|
32
|
+
* Record control before Settings, as the "Notes: next features" mockup the PM approved (2026-10-07, meeting-notes Run 12). */
|
|
33
|
+
export const BAR_DESTS = Object.freeze(['today', 'mywork', 'initiatives', 'board', 'reports', 'docs', 'notes', 'people']);
|
|
31
34
|
|
|
32
35
|
/** Is this sync reading "not in sync"? Behind, ahead, records not yet recorded, or `remote_stale`: no fetch in the last
|
|
33
36
|
* few minutes, so another machine's push would be invisible here and "in sync" cannot be claimed. An unreadable status
|
|
@@ -38,7 +41,10 @@ export function unsyncedOf(st) {
|
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
/** The lucide symbol each place wears in the bar (UI comfort, 2026-10-07: a word alone is slow to find; the phone's tab bar already wears these). */
|
|
41
|
-
const NAV_ICON = Object.freeze({ today: 'house', mywork: 'list-checks', initiatives: 'target', board: 'square-kanban', reports: 'chart-column', docs: 'file-text', people: 'users', settings: 'settings' });
|
|
44
|
+
const NAV_ICON = Object.freeze({ today: 'house', mywork: 'list-checks', initiatives: 'target', board: 'square-kanban', reports: 'chart-column', docs: 'file-text', notes: 'notebook-pen', people: 'users', settings: 'settings' });
|
|
45
|
+
|
|
46
|
+
const fmtClock = (ms) => { const t = Math.max(0, Math.floor(ms / 1000)); const m = Math.floor(t / 60); return (m >= 60 ? Math.floor(m / 60) + ':' + String(m % 60).padStart(2, '0') : String(m)) + ':' + String(t % 60).padStart(2, '0'); };
|
|
47
|
+
const dockHighlight = () => (dock.highlightBusy ? dock.highlightBusy() : null);
|
|
42
48
|
|
|
43
49
|
export function createTopbar({ go, openPalette, openView, repoMenu, sync } = {}) {
|
|
44
50
|
const node = el('header', { class: 'fr-bar', 'data-shell': 'top', 'data-fr': 'bar' });
|
|
@@ -47,12 +53,6 @@ export function createTopbar({ go, openPalette, openView, repoMenu, sync } = {})
|
|
|
47
53
|
let repoLabel = null;
|
|
48
54
|
const repoSlot = el('div', { class: 'fr-bar__repo' });
|
|
49
55
|
|
|
50
|
-
const recordingIndicator = el('span', {
|
|
51
|
-
class: 'fr-recording-dot', 'data-fr-recording': '',
|
|
52
|
-
title: 'Notes are being recorded',
|
|
53
|
-
'aria-hidden': 'true',
|
|
54
|
-
});
|
|
55
|
-
|
|
56
56
|
const count = needsCount('0', { 'data-today-badge': '' });
|
|
57
57
|
const dot = needsDot();
|
|
58
58
|
const links = {};
|
|
@@ -87,11 +87,36 @@ export function createTopbar({ go, openPalette, openView, repoMenu, sync } = {})
|
|
|
87
87
|
});
|
|
88
88
|
search.appendChild(el('kbd', { class: 'fr-kbd' }, COPY.searchKey));
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
/* The Record control (meeting-notes Run 12, mockup board 2), right of the bar before Settings. Idle: Record. Recording: the
|
|
91
|
+
* red dot and the clock (a press opens the note), the highlight and Pause. Paused: the clock and Resume. Stop is in the note. */
|
|
92
|
+
const recSlot = el('div', { class: 'fr-rec', 'data-fr': 'record' });
|
|
93
|
+
let recKey = '';
|
|
94
|
+
let recTick = null;
|
|
95
|
+
const recClock = () => { const st = dock.recordState(); return st.started ? fmtClock(Date.now() - st.started) : ''; };
|
|
96
|
+
function drawRecord() {
|
|
97
|
+
const st = dock.recordState();
|
|
98
|
+
const busy = st.mode === 'recording' && dockHighlight();
|
|
99
|
+
const key = st.mode + ':' + st.id + ':' + (busy ? busy.state + busy.open : '');
|
|
100
|
+
if (key === recKey) return;
|
|
101
|
+
recKey = key;
|
|
102
|
+
clearInterval(recTick); recTick = null;
|
|
103
|
+
if (st.mode === 'idle') {
|
|
104
|
+
setChildren(recSlot, [button({ label: 'Record', variant: 'outline', icon: 'mic', attrs: { class: 'fr-rec__start', 'data-fr-rec': 'idle', title: 'Start a note and record the meeting' }, onClick: (e) => dock.recordNew(e && e.currentTarget) })]);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (st.mode === 'paused') {
|
|
108
|
+
setChildren(recSlot, [button({ label: 'Resume', variant: 'outline', icon: 'mic', attrs: { class: 'fr-rec__start', 'data-fr-rec': 'paused', title: 'Resume recording ' + (st.title || 'the note') }, onClick: () => dock.resumeNow() })]);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const clock = h('span', { class: 'fr-rec__clock', 'data-fr-rec-clock': '' }, recClock());
|
|
112
|
+
recTick = setInterval(() => { clock.textContent = recClock(); }, 1000);
|
|
113
|
+
const open = button({ label: h('span', { class: 'fr-rec__txt' }, h('span', { class: 'fr-rec-dot', 'aria-hidden': 'true' }), clock), variant: 'ghost',
|
|
114
|
+
attrs: { class: 'fr-rec__seg fr-rec__open', 'data-fr-rec': 'recording', 'aria-label': 'Recording ' + (st.title || 'a note') + '. Open the note.' }, onClick: () => dock.openDock(st.id) });
|
|
115
|
+
const hl = iconButton({ icon: 'highlighter', ariaLabel: busy ? 'Highlight in progress: show it' : 'Highlight the last 30 seconds and what is said next',
|
|
116
|
+
attrs: { class: 'fr-rec__seg' + (busy ? ' fr-rec__seg--busy' : ''), 'data-fr-rec-highlight': busy ? busy.state : '' }, onClick: () => dock.highlightNow() });
|
|
117
|
+
const pause = iconButton({ icon: 'pause', ariaLabel: 'Pause recording', attrs: { class: 'fr-rec__seg', 'data-fr-rec-pause': '' }, onClick: () => dock.pauseNow() });
|
|
118
|
+
setChildren(recSlot, [h('div', { class: 'fr-rec__group', role: 'group', 'aria-label': 'Recording' }, open, hl, pause)]);
|
|
119
|
+
}
|
|
95
120
|
|
|
96
121
|
let syncBtn = null;
|
|
97
122
|
let syncLabel = null;
|
|
@@ -129,8 +154,8 @@ export function createTopbar({ go, openPalette, openView, repoMenu, sync } = {})
|
|
|
129
154
|
syncLabel = want;
|
|
130
155
|
syncBtn = want ? syncButton(want, !!model.syncing) : null;
|
|
131
156
|
}
|
|
132
|
-
|
|
133
|
-
setChildren(node, [repoSlot,
|
|
157
|
+
drawRecord();
|
|
158
|
+
setChildren(node, [repoSlot, nav, search, syncBtn, recSlot, links.settings]);
|
|
134
159
|
}
|
|
135
160
|
|
|
136
161
|
update({});
|
|
@@ -45,8 +45,6 @@ import { pmGet, pmPost } from './http.js';
|
|
|
45
45
|
import { mdRender } from './markdown.js';
|
|
46
46
|
import { icon } from './icons.js';
|
|
47
47
|
import { button, iconButton, field, row, sheet, empty, menu, toast, ensureComponentsCss } from './primitives.js';
|
|
48
|
-
import { startHighlight, updateListenProgress, formatListenRemaining, addHighlightToNote } from '/lib/notes-highlight.js';
|
|
49
|
-
import { checkConflict, collectRepoPassages, formatConflict } from '/lib/notes-conflicts.js';
|
|
50
48
|
|
|
51
49
|
const SAVE_MS = 700; // a pause this long in typing saves the note
|
|
52
50
|
const POLL_MS = 1500; // while listening, and until the last line is labelled, the note is read this often
|
|
@@ -739,161 +737,119 @@ async function startSpeech(ctx, n, mode, install = false) {
|
|
|
739
737
|
|
|
740
738
|
/* ── highlighting during recording (Run 11) ─────────────────────────────────────── */
|
|
741
739
|
/** Start a highlight: capture last 30s, listen forward for the chosen duration. */
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
if (highlight) {
|
|
763
|
-
highlight.state = 'transcribing';
|
|
764
|
-
ctx.setState({});
|
|
765
|
-
// Simulate transcription
|
|
766
|
-
setTimeout(() => {
|
|
767
|
-
if (highlight) {
|
|
768
|
-
highlight.text = 'Transcribed highlight text here.';
|
|
769
|
-
highlight.state = 'labeling';
|
|
770
|
-
ctx.setState({});
|
|
771
|
-
// Simulate labeling
|
|
772
|
-
setTimeout(() => {
|
|
773
|
-
if (highlight) {
|
|
774
|
-
highlight.label = 'Statement';
|
|
775
|
-
highlight.state = 'checking';
|
|
776
|
-
ctx.setState({});
|
|
777
|
-
// Simulate checking
|
|
778
|
-
setTimeout(() => {
|
|
779
|
-
if (highlight) {
|
|
780
|
-
highlight.state = 'done';
|
|
781
|
-
ctx.setState({});
|
|
782
|
-
}
|
|
783
|
-
}, 500);
|
|
784
|
-
}
|
|
785
|
-
}, 300);
|
|
786
|
-
}
|
|
787
|
-
}, 500);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
}, 100);
|
|
791
|
-
}
|
|
792
|
-
}, 100);
|
|
793
|
-
} catch (e) {
|
|
794
|
-
banner = { tone: 'bad', text: 'Highlight failed: ' + e.message };
|
|
795
|
-
ctx.setState({});
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
/** Close the highlight pop-up (work continues in background). */
|
|
800
|
-
function closeHighlight(ctx) {
|
|
801
|
-
highlight = null;
|
|
802
|
-
if (highlightTimer) clearInterval(highlightTimer);
|
|
803
|
-
highlightTimer = null;
|
|
740
|
+
/* ── highlights (meeting-notes Run 12, mockup board 0) ──────────────────────────────────────────────────────────────
|
|
741
|
+
* A press keeps the last 30 seconds and listens for 30 s, 1 min or 2 min more (the PM can change it while it listens).
|
|
742
|
+
* When the window closes and its audio is transcribed, POST /api/notes/highlight makes the highlight from the transcript
|
|
743
|
+
* lines said in the window: their words, the strongest label among them (Jev's when it is on), and the closest file in
|
|
744
|
+
* the repository with Jev's comparison when the check is on. The pop-up shows each step as it happens; closing it does not
|
|
745
|
+
* stop the work, and the highlight is kept in the note. Nothing here is made up: a window with no transcribed line says so. */
|
|
746
|
+
const HL_BEFORE_MS = 30 * 1000;
|
|
747
|
+
const HL_AFTER = [['30s', 30 * 1000, '30 s'], ['1min', 60 * 1000, '1 min'], ['2min', 120 * 1000, '2 min']];
|
|
748
|
+
const hlAfterMs = () => (HL_AFTER.find((x) => x[0] === highlightListenDuration) || HL_AFTER[0])[1];
|
|
749
|
+
let hlOpen = false;
|
|
750
|
+
/** Highlight on the note that is listening: from the Record control, the note's own button, or H. */
|
|
751
|
+
export function highlightNow(ctx, id) {
|
|
752
|
+
const n = cache.get(id);
|
|
753
|
+
const at = liveMs(n);
|
|
754
|
+
if (at === null) return false;
|
|
755
|
+
if (highlight && highlight.state === 'listening') { hlOpen = true; ctx.setState({}); return true; }
|
|
756
|
+
highlight = { id: n.id, title: n.title || '', at, from: Math.max(0, at - HL_BEFORE_MS), state: 'listening', pressed: Date.now(), result: null, error: null };
|
|
757
|
+
hlOpen = true;
|
|
758
|
+
clearInterval(highlightTimer);
|
|
759
|
+
highlightTimer = setInterval(() => hlTick(ctx), 500);
|
|
804
760
|
ctx.setState({});
|
|
761
|
+
return true;
|
|
805
762
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
763
|
+
function hlTick(ctx) {
|
|
764
|
+
const h = highlight;
|
|
765
|
+
if (!h) { clearInterval(highlightTimer); highlightTimer = null; return; }
|
|
766
|
+
const live = cache.get(h.id) && listening(cache.get(h.id));
|
|
767
|
+
// the window closes when its time is up, or when listening stops first
|
|
768
|
+
if (h.state === 'listening' && (Date.now() - h.pressed >= hlAfterMs() || !live)) {
|
|
769
|
+
clearInterval(highlightTimer); highlightTimer = null;
|
|
770
|
+
h.to = h.at + Math.min(hlAfterMs(), Date.now() - h.pressed);
|
|
771
|
+
finishHighlight(ctx, h);
|
|
814
772
|
}
|
|
773
|
+
const t = typeof document !== 'undefined' && document.querySelector('[data-nt-hl-left]');
|
|
774
|
+
if (t) t.textContent = hlLeft(h); else ctx.setState({});
|
|
815
775
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
});
|
|
827
|
-
banner = { tone: 'good', text: 'Task created: ' + title };
|
|
828
|
-
ctx.setState({});
|
|
829
|
-
setTimeout(() => { banner = null; ctx.setState({}); }, 2000);
|
|
830
|
-
} catch (e) {
|
|
831
|
-
banner = { tone: 'bad', text: 'Failed to create task: ' + e.message };
|
|
832
|
-
ctx.setState({});
|
|
776
|
+
const hlLeft = (h) => fmtTime(Math.min(Date.now() - h.pressed, hlAfterMs())) + ' of ' + (HL_AFTER.find((x) => x[0] === highlightListenDuration) || HL_AFTER[0])[2];
|
|
777
|
+
async function finishHighlight(ctx, h) {
|
|
778
|
+
h.state = 'transcribing'; ctx.setState({});
|
|
779
|
+
// the window's last audio is still being transcribed: wait for it (at most 30 s)
|
|
780
|
+
const t0 = Date.now();
|
|
781
|
+
for (;;) {
|
|
782
|
+
const r = await loadNote(ctx, h.id);
|
|
783
|
+
const done = r && r.ok && !(r.audio && r.audio.pending) && (r.note.transcript || []).every((x) => !(typeof x.t === 'number' && x.t <= h.to && x.open));
|
|
784
|
+
if (done || Date.now() - t0 > 30000 || highlight !== h) break;
|
|
785
|
+
await new Promise((res) => setTimeout(res, 1000));
|
|
833
786
|
}
|
|
787
|
+
if (highlight !== h) return;
|
|
788
|
+
h.state = 'checking'; ctx.setState({});
|
|
789
|
+
const r = await pmPost('/api/notes/highlight', { id: h.id, from: h.from, to: h.to });
|
|
790
|
+
if (highlight !== h) return;
|
|
791
|
+
if (r && r.ok) { h.result = r.highlight; h.checked = r.checked; h.jev = r.jev; h.state = 'done'; await loadNote(ctx, h.id); }
|
|
792
|
+
else { h.state = 'failed'; h.error = (r && r.error) || 'the highlight was not made'; }
|
|
793
|
+
hlOpen = true;
|
|
794
|
+
ctx.setState({});
|
|
834
795
|
}
|
|
796
|
+
function closeHighlight(ctx) {
|
|
797
|
+
// closing while it still works only hides the pop-up; a finished one is let go
|
|
798
|
+
if (highlight && (highlight.state === 'done' || highlight.state === 'failed')) highlight = null;
|
|
799
|
+
hlOpen = false;
|
|
800
|
+
ctx.setState({});
|
|
801
|
+
}
|
|
802
|
+
async function removeHighlight(ctx, h) {
|
|
803
|
+
if (h.result) await pmPost('/api/notes/highlight-remove', { id: h.id, hid: h.result.id });
|
|
804
|
+
highlight = null; hlOpen = false;
|
|
805
|
+
await loadNote(ctx, h.id);
|
|
806
|
+
ctx.setState({});
|
|
807
|
+
}
|
|
808
|
+
/** Is a highlight being made, and is its result waiting to be seen? (the Record control's dot) */
|
|
809
|
+
export const highlightBusy = () => (highlight ? { state: highlight.state, open: hlOpen } : null);
|
|
810
|
+
export function openHighlight(ctx) { if (highlight) { hlOpen = true; ctx.setState({}); } }
|
|
835
811
|
|
|
836
|
-
/**
|
|
837
|
-
function
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
{
|
|
812
|
+
/** The highlight's pop-up (pm/note-dock.js draws it on every place), or null when there is none or it is closed. */
|
|
813
|
+
export function highlightPanel(ctx) {
|
|
814
|
+
const h = highlight;
|
|
815
|
+
if (!h || !hlOpen) return null;
|
|
816
|
+
const order = ['listening', 'transcribing', 'checking', 'done'];
|
|
817
|
+
const at = order.indexOf(h.state);
|
|
818
|
+
const step = (k, label, extra) => {
|
|
819
|
+
const i = order.indexOf(k);
|
|
820
|
+
const done = h.state === 'done' || (at > i && h.state !== 'failed');
|
|
821
|
+
const now = h.state === k;
|
|
822
|
+
return el('li', { class: 'nt-hl-step' + (done ? ' nt-hl-step--done' : now ? ' nt-hl-step--now' : ''), 'data-nt-hl-step': k },
|
|
823
|
+
el('span', { class: 'nt-hl-step__mk' }, icon(done ? 'check' : 'circle-dot')), el('span', null, label), extra || null);
|
|
824
|
+
};
|
|
825
|
+
const durs = el('div', { class: 'nt-hl-durs', role: 'group', 'aria-label': 'Listen after' }, HL_AFTER.map(([k, , label]) => button({ label, variant: highlightListenDuration === k ? 'secondary' : 'ghost', size: 'sm',
|
|
826
|
+
onClick: () => { highlightListenDuration = k; ctx.setState({}); }, attrs: { 'data-nt-duration': k, 'aria-pressed': highlightListenDuration === k ? 'true' : 'false' } })));
|
|
827
|
+
const body = [
|
|
828
|
+
el('div', { class: 'nt-hl-head' }, el('h3', { class: 'nt-hl-title' }, 'Highlight'), el('span', { class: 'nt-hl-at' }, fmtTime(h.from) + (h.to ? '–' + fmtTime(h.to) : ' →')),
|
|
829
|
+
h.result ? el('span', { class: 'nt-hl-tag' }, h.result.label) : null),
|
|
830
|
+
el('ol', { class: 'nt-hl-steps' },
|
|
831
|
+
el('li', { class: 'nt-hl-step nt-hl-step--done' }, el('span', { class: 'nt-hl-step__mk' }, icon('check')), el('span', null, 'Kept the last 30 seconds')),
|
|
832
|
+
step('listening', 'Listening', h.state === 'listening' ? el('span', { class: 'nt-hl-left', 'data-nt-hl-left': '' }, hlLeft(h)) : null),
|
|
833
|
+
step('transcribing', 'Transcribe the window'),
|
|
834
|
+
step('checking', 'Label it and check the repository'),
|
|
835
|
+
),
|
|
836
|
+
h.state === 'listening' ? durs : null,
|
|
847
837
|
];
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
});
|
|
863
|
-
|
|
864
|
-
const durationOptions = el('div', { class: 'nt-highlight__durations' });
|
|
865
|
-
['30s', '1min', '2min'].forEach((dur) => {
|
|
866
|
-
const opt = button({
|
|
867
|
-
label: dur,
|
|
868
|
-
variant: highlightListenDuration === dur ? 'primary' : 'secondary',
|
|
869
|
-
onClick: () => {
|
|
870
|
-
highlightListenDuration = dur;
|
|
871
|
-
ctx.setState({});
|
|
872
|
-
},
|
|
873
|
-
attrs: { class: 'nt-highlight__duration-btn', 'data-nt-duration': dur }
|
|
874
|
-
});
|
|
875
|
-
durationOptions.appendChild(opt);
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
const actions = el('div', { class: 'nt-highlight__actions' });
|
|
879
|
-
actions.appendChild(button({
|
|
880
|
-
label: highlight.state === 'done' ? 'Accept' : 'Cancel',
|
|
881
|
-
variant: 'primary',
|
|
882
|
-
onClick: () => {
|
|
883
|
-
if (highlight.state === 'done') acceptHighlight(ctx, n);
|
|
884
|
-
else closeHighlight(ctx);
|
|
885
|
-
},
|
|
886
|
-
attrs: { 'data-nt-highlight-accept': '' }
|
|
887
|
-
}));
|
|
888
|
-
|
|
889
|
-
const popup = el('div', { class: 'nt-highlight__popup', 'data-nt-part': 'highlight-popup' },
|
|
890
|
-
el('div', { class: 'nt-highlight__header' }, el('h3', {}, 'Recording Highlight')),
|
|
891
|
-
progress,
|
|
892
|
-
highlight.state === 'listening' ? durationOptions : null,
|
|
893
|
-
actions
|
|
894
|
-
);
|
|
895
|
-
|
|
896
|
-
return popup;
|
|
838
|
+
if (h.state === 'done' && h.result) {
|
|
839
|
+
const c = h.result.check;
|
|
840
|
+
body.push(el('p', { class: 'nt-hl-text', 'data-nt-hl-text': '' }, h.result.text));
|
|
841
|
+
body.push(el('p', { class: 'nt-hint', 'data-nt-hl-check': c ? c.verdict : 'none' }, c && c.doc
|
|
842
|
+
? (c.verdict === 'differs' ? 'Differs from ' : c.verdict === 'agrees' ? 'Agrees with ' : 'Closest file: ') + c.doc + (c.line ? ':' + c.line : '') + (c.by === 'jev' ? '' : ' (found on this machine; turn on the Jev check in Notes settings to compare)')
|
|
843
|
+
: h.checked ? 'No file in the repository talks about this.' : 'Not checked: the note is not linked to a repository.'));
|
|
844
|
+
}
|
|
845
|
+
if (h.state === 'failed') body.push(el('p', { class: 'nt-hint nt-hint--bad', 'data-nt-hl-failed': '' }, 'No highlight: ' + h.error + '.'));
|
|
846
|
+
const acts = [];
|
|
847
|
+
if (h.state === 'listening') acts.push(button({ label: 'Stop listening now', variant: 'outline', size: 'sm', onClick: () => { h.pressed = Date.now() - hlAfterMs(); hlTick(ctx); }, attrs: { 'data-nt-hl-stop': '' } }));
|
|
848
|
+
if (h.state === 'done') acts.push(button({ label: 'Done', variant: 'primary', size: 'sm', onClick: () => closeHighlight(ctx), attrs: { 'data-nt-hl-done': '' } }),
|
|
849
|
+
button({ label: 'Remove', variant: 'ghost', size: 'sm', onClick: () => removeHighlight(ctx, h), attrs: { 'data-nt-hl-remove': '' } }));
|
|
850
|
+
else acts.push(button({ label: 'Close', variant: 'ghost', size: 'sm', onClick: () => closeHighlight(ctx), attrs: { 'data-nt-hl-close': '' } }));
|
|
851
|
+
body.push(el('div', { class: 'nt-hl-acts' }, acts));
|
|
852
|
+
return el('section', { class: 'nt-hl', role: 'dialog', 'aria-label': 'Highlight', 'data-nt-part': 'highlight-popup', 'data-nt-hl': h.state }, body);
|
|
897
853
|
}
|
|
898
854
|
|
|
899
855
|
async function stopRecording(ctx) {
|
|
@@ -909,6 +865,17 @@ async function stopRecording(ctx) {
|
|
|
909
865
|
ctx.setState({});
|
|
910
866
|
}
|
|
911
867
|
|
|
868
|
+
/** Stop in the note: listening stops and the note leaves the Record control at the top right (pm/note-dock.js). */
|
|
869
|
+
async function stopAndFinish(ctx) {
|
|
870
|
+
const d = await import('./note-dock.js');
|
|
871
|
+
if (d.recordState().mode !== 'idle') await d.finish(); else await stopRecording(ctx);
|
|
872
|
+
}
|
|
873
|
+
/** Resume in the note: the Record control holds it again and it listens. */
|
|
874
|
+
async function resumeOn(n) {
|
|
875
|
+
const d = await import('./note-dock.js');
|
|
876
|
+
await d.recordNote(n.id, n.title);
|
|
877
|
+
}
|
|
878
|
+
|
|
912
879
|
/* ── the two sheets: what to listen to, and settings ─────────────────────────────────────────── */
|
|
913
880
|
async function chooseSources(ctx, n) {
|
|
914
881
|
const eng = engine && engine.ws === wsOf(ctx) && engine.whisper ? engine : await loadEngine(ctx);
|
|
@@ -1197,13 +1164,13 @@ function header(ctx, n, ed, opts = {}) {
|
|
|
1197
1164
|
el('span', { 'data-nt-sources': '' }, ' · ' + (live.state === 'starting' || live.state === 'checking' ? 'starting' : ['mic', 'meeting'].filter((s) => live.sources.includes(s)).join(' + '))),
|
|
1198
1165
|
el('span', { class: 'nt-sr' }, ' Notes are being taken.'));
|
|
1199
1166
|
// Highlight button (Run 11)
|
|
1200
|
-
acts.push(iconButton({ icon: '
|
|
1201
|
-
acts.push(button({ label: 'Stop', variant: 'outline', size: 'sm', onClick: () =>
|
|
1167
|
+
acts.push(iconButton({ icon: 'highlighter', ariaLabel: 'Highlight the last 30 seconds and what is said next', onClick: () => highlightNow(ctx, n.id), attrs: { 'data-nt-highlight': '' } }));
|
|
1168
|
+
acts.push(button({ label: 'Stop', variant: 'outline', size: 'sm', onClick: () => stopAndFinish(ctx), attrs: { 'data-nt-stop': '' } }));
|
|
1202
1169
|
} else {
|
|
1203
1170
|
const len = (n.transcript || []).length ? fmtTime(Math.max(...(n.transcript || []).map((x) => x.t || 0))) : '';
|
|
1204
1171
|
status = el('span', { class: 'nt-live' }, el('b', null, 'Meeting note'), len ? el('span', null, len) : null);
|
|
1205
1172
|
if (!n.private) acts.push((n.transcript || []).length
|
|
1206
|
-
? button({ label: '
|
|
1173
|
+
? button({ label: 'Resume', variant: 'primary', size: 'sm', onClick: () => resumeOn(n), attrs: { 'data-nt-listen': 'again' } })
|
|
1207
1174
|
: button({ label: 'Listen', variant: 'primary', size: 'sm', onClick: () => chooseSources(ctx, n), attrs: { 'data-nt-listen': '' } }));
|
|
1208
1175
|
}
|
|
1209
1176
|
if (opts.onMinimize) acts.push(button({ label: 'Minimize', variant: 'ghost', size: 'sm', onClick: opts.onMinimize, attrs: { 'data-nt-minimize': '' } }));
|
|
@@ -1244,7 +1211,7 @@ function itemRow(ctx, n, key, title, x) {
|
|
|
1244
1211
|
const actions = [];
|
|
1245
1212
|
// Add "Make task" button for action items (Run 11)
|
|
1246
1213
|
if (key === 'action') {
|
|
1247
|
-
actions.push(iconButton({ icon: 'plus', ariaLabel: 'Make a task from this', onClick: () => makeTask(
|
|
1214
|
+
actions.push(iconButton({ icon: 'plus', ariaLabel: 'Make a task from this', onClick: (e) => import('./notes-task.js').then((m) => m.makeTask(n, x, { repo: ctx.state && ctx.state.product, btn: e && e.currentTarget })), size: 'sm', attrs: { 'data-nt-make-task': '' } }));
|
|
1248
1215
|
}
|
|
1249
1216
|
// Add remove buttons for unpinned items
|
|
1250
1217
|
if (bullet) {
|
|
@@ -1557,11 +1524,6 @@ export function renderBoard(inner, ctx) {
|
|
|
1557
1524
|
wrap.appendChild(el('div', { class: 'nt-body2' }, noteColumn(ctx, n, ed), transcriptColumn(ctx, n)));
|
|
1558
1525
|
if (pl) queueMicrotask(paintNow);
|
|
1559
1526
|
if (((n.agent && n.agent.started && !n.agent.markdown) || finalPending(n)) && !agentTimer) pollAgent(ctx, n.id);
|
|
1560
|
-
// Add highlight pop-up if active
|
|
1561
|
-
if (highlight && n.id === currentId) {
|
|
1562
|
-
const hlPopup = highlightPopup(ctx, n);
|
|
1563
|
-
if (hlPopup) wrap.appendChild(hlPopup);
|
|
1564
|
-
}
|
|
1565
1527
|
}
|
|
1566
1528
|
inner.appendChild(wrap);
|
|
1567
1529
|
keep();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* lucide-sprite.js — GENERATED by workspace/third-party/build-lucide-sprite.js from lucide-static 1.52.0.
|
|
2
|
-
* Do not edit by hand. The icons: grip-vertical, plus, check, square, square-check-big, x, undo-2, flag, bot, chevron-right, chevron-down, ellipsis, circle-alert, history, arrow-up, arrow-down, diamond, settings, search, refresh-cw, chevron-left, sun, moon, arrow-left, arrow-right, globe, columns-2, arrow-up-down, arrow-left-right, panel-top, panel-left, maximize-2, minimize-2, git-branch, clock, bold, italic, code, link, type, heading, list, list-checks, text-quote, file-code, git-compare, cloud, share-2, house, target, square-kanban, file-text, square-terminal, calendar, chart-column, users, git-merge, mail, message-square, ticket, ban.
|
|
2
|
+
* Do not edit by hand. The icons: grip-vertical, plus, check, square, square-check-big, x, undo-2, flag, bot, chevron-right, chevron-down, ellipsis, circle-alert, history, arrow-up, arrow-down, diamond, settings, search, refresh-cw, chevron-left, sun, moon, arrow-left, arrow-right, globe, columns-2, arrow-up-down, arrow-left-right, panel-top, panel-left, maximize-2, minimize-2, git-branch, clock, bold, italic, code, link, type, heading, list, list-checks, text-quote, file-code, git-compare, cloud, share-2, house, target, square-kanban, file-text, square-terminal, calendar, chart-column, users, git-merge, mail, message-square, ticket, ban, notebook-pen, mic, pause, play, highlighter, circle-dot.
|
|
3
3
|
*
|
|
4
4
|
* ISC License
|
|
5
5
|
*
|
|
@@ -107,5 +107,11 @@ export const SPRITE = '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true
|
|
|
107
107
|
+ '<symbol id="lucide-message-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z" /></symbol>'
|
|
108
108
|
+ '<symbol id="lucide-ticket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z" /><path d="M13 5v2" /><path d="M13 17v2" /><path d="M13 11v2" /></symbol>'
|
|
109
109
|
+ '<symbol id="lucide-ban" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M4.929 4.929 19.07 19.071" /></symbol>'
|
|
110
|
+
+ '<symbol id="lucide-notebook-pen" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13.4 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.4" /><path d="M2 6h4" /><path d="M2 10h4" /><path d="M2 14h4" /><path d="M2 18h4" /><path d="M21.378 5.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z" /></symbol>'
|
|
111
|
+
+ '<symbol id="lucide-mic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19v3" /><path d="M19 10v2a7 7 0 0 1-14 0v-2" /><rect x="9" y="2" width="6" height="13" rx="3" /></symbol>'
|
|
112
|
+
+ '<symbol id="lucide-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /><rect x="5" y="3" width="5" height="18" rx="1" /></symbol>'
|
|
113
|
+
+ '<symbol id="lucide-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>'
|
|
114
|
+
+ '<symbol id="lucide-highlighter" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 11-6 6v3h9l3-3" /><path d="m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4" /></symbol>'
|
|
115
|
+
+ '<symbol id="lucide-circle-dot" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1" /><circle cx="12" cy="12" r="10" /></symbol>'
|
|
110
116
|
+ '</svg>';
|
|
111
|
-
export const ICONS = ["grip-vertical","plus","check","square","square-check-big","x","undo-2","flag","bot","chevron-right","chevron-down","ellipsis","circle-alert","history","arrow-up","arrow-down","diamond","settings","search","refresh-cw","chevron-left","sun","moon","arrow-left","arrow-right","globe","columns-2","arrow-up-down","arrow-left-right","panel-top","panel-left","maximize-2","minimize-2","git-branch","clock","bold","italic","code","link","type","heading","list","list-checks","text-quote","file-code","git-compare","cloud","share-2","house","target","square-kanban","file-text","square-terminal","calendar","chart-column","users","git-merge","mail","message-square","ticket","ban"];
|
|
117
|
+
export const ICONS = ["grip-vertical","plus","check","square","square-check-big","x","undo-2","flag","bot","chevron-right","chevron-down","ellipsis","circle-alert","history","arrow-up","arrow-down","diamond","settings","search","refresh-cw","chevron-left","sun","moon","arrow-left","arrow-right","globe","columns-2","arrow-up-down","arrow-left-right","panel-top","panel-left","maximize-2","minimize-2","git-branch","clock","bold","italic","code","link","type","heading","list","list-checks","text-quote","file-code","git-compare","cloud","share-2","house","target","square-kanban","file-text","square-terminal","calendar","chart-column","users","git-merge","mail","message-square","ticket","ban","notebook-pen","mic","pause","play","highlighter","circle-dot"];
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"doc-category.test.js",
|
|
43
43
|
"doc-category-corrections.test.js",
|
|
44
44
|
"notes-highlight.test.js",
|
|
45
|
+
"notes-page.test.js",
|
|
45
46
|
"notes-conflicts.test.js",
|
|
46
47
|
"proactive-prepare.test.js",
|
|
47
48
|
"learn.test.js",
|
|
@@ -149,6 +150,7 @@
|
|
|
149
150
|
"doc-category.test.js": 1,
|
|
150
151
|
"doc-category-corrections.test.js": 1,
|
|
151
152
|
"notes-highlight.test.js": 1,
|
|
153
|
+
"notes-page.test.js": 1,
|
|
152
154
|
"notes-conflicts.test.js": 1,
|
|
153
155
|
"proactive-prepare.test.js": 9,
|
|
154
156
|
"learn.test.js": 3,
|
|
@@ -206,7 +208,7 @@
|
|
|
206
208
|
"pm-r29-filespad-test.js": 5,
|
|
207
209
|
"pm-r29-newsession-alignment-test.js": 8,
|
|
208
210
|
"pm-live-notes-test.js": 24,
|
|
209
|
-
"pm-notes-menu-test.js":
|
|
211
|
+
"pm-notes-menu-test.js": 10,
|
|
210
212
|
"pm-r30-test.js": 4,
|
|
211
213
|
"ui-test.js": 30,
|
|
212
214
|
"plan-items.test.js": 1,
|
|
@@ -12,7 +12,7 @@ The workspace runs with no network (D62). Two third-party browser files are comm
|
|
|
12
12
|
| Package | Version | Licence | Tarball | Tarball sha256 | Shipped file |
|
|
13
13
|
|---|---|---|---|---|---|
|
|
14
14
|
| sortablejs | 1.15.7 | MIT | https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz | `66cbfc8fa18860783584fe0c346baf6cbf3399da72d23d21053425ba232abed4` | `public/vendor/Sortable.min.js`: an unmodified copy of `package/Sortable.min.js` (45 478 bytes, sha256 `bf4241bc73fef7f11c59a283a69fe8051cdd31c6d8ff5a2b9ba219e7831fcf76`; npm integrity `sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==`) |
|
|
15
|
-
| lucide-static | 1.52.0 | ISC, plus the Feather MIT notice | https://registry.npmjs.org/lucide-static/-/lucide-static-1.52.0.tgz | `4d2aa079171e4eba4c704844b8b0c9edb6e4cfb2fb8bd86f7b14353994f04a87` | `public/vendor/lucide-sprite.js`: generated by `build-lucide-sprite.js` (
|
|
15
|
+
| lucide-static | 1.52.0 | ISC, plus the Feather MIT notice | https://registry.npmjs.org/lucide-static/-/lucide-static-1.52.0.tgz | `4d2aa079171e4eba4c704844b8b0c9edb6e4cfb2fb8bd86f7b14353994f04a87` | `public/vendor/lucide-sprite.js`: generated by `build-lucide-sprite.js` (23 263 bytes, sha256 `6866d7d284f4f09139daa9f31cff28c4cc56b6c008aa0b58e6ee0e61907aac2f`). It holds 67 icons: grip-vertical, plus, check, square, square-check-big, x, undo-2, flag, bot, chevron-right, chevron-down, ellipsis, circle-alert, history, arrow-up, arrow-down, diamond, then the eight the frame's chrome draws (Run 3): settings, search, refresh-cw, chevron-left, sun, moon, arrow-left, arrow-right, then globe, columns-2, arrow-up-down and arrow-left-right for the dock strip's web, split and orientation chips (Run 3, C6), then panel-top, panel-left, maximize-2, minimize-2, git-branch and clock for the dock's icon controls and session rows (0.54.2), then bold, italic, code, link, type, heading, list, list-checks, text-quote, file-code, git-compare, cloud and share-2 for the document tab's in-place editor, its Confluence copy and its connections, then house, target, square-kanban, file-text, square-terminal, calendar and chart-column for the phone's tab bar and view switches, then users, git-merge, mail, message-square, ticket and ban for the People place's nav icon, merge button and interaction sources, then notebook-pen, mic, pause, play, highlighter and circle-dot for the Notes place, the Record button and the highlight |
|
|
16
16
|
|
|
17
17
|
The licences are in this folder: [sortablejs-1.15.7.LICENSE](sortablejs-1.15.7.LICENSE) and [lucide-static-1.52.0.LICENSE](lucide-static-1.52.0.LICENSE). The sprite also carries the lucide licence text as its first comment.
|
|
18
18
|
|