Skip to content

Commit 5524598

Browse files
committed
Fix Pipeline page state reset, terminal blinking, and runChain completion
- Persist Pipeline form state (course, detail, steps, filters) in State.pipeline so re-navigating to the page restores all values instead of resetting to defaults - Fix terminal blinking: use incremental DOM append instead of full innerHTML rebuild on every rAF tick — only new lines are inserted, partial-line placeholder is updated in-place, eliminating all visual flicker during active output - Increase terminal MAX_LINES from 500 → 2000 to keep more execution history - Fix runChain() never resetting State.running/showStop after the last step completes — now shows "All steps completed" and resets running state correctly
1 parent 040f829 commit 5524598

2 files changed

Lines changed: 127 additions & 37 deletions

File tree

electron/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "auto-note",
3-
"version": "0.9.8",
3+
"version": "0.9.9",
44
"description": "AutoNote — lecture notes generator from Canvas recordings",
55
"homepage": "https://github.com/nodeeeeee/Auto-Note",
66
"author": {

electron/renderer/app.js

Lines changed: 126 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ const State = {
1212
pythonPath: null,
1313
outputDir: null,
1414
dataDir: null,
15+
// Persisted Pipeline page form values (survive navigation)
16+
pipeline: {
17+
courseId: '',
18+
detail: '7',
19+
courseName: '',
20+
lecFilter: '',
21+
stealth: false,
22+
force: false,
23+
steps: { mat: true, vid: true, trans: true, align: true, gen: true },
24+
},
1525
};
1626

1727
// ── Snackbar ──────────────────────────────────────────────────────────────────
@@ -31,11 +41,12 @@ function snack(msg, ok = true) {
3141

3242
// ── Terminal ──────────────────────────────────────────────────────────────────
3343
const Term = (() => {
34-
const MAX_LINES = 500;
44+
const MAX_LINES = 2000;
3545
const ANSI_RE = /\x1b\[[0-9;]*[mABCDEFGHJKSTfr]|\x1b\][^\x07]*\x07|\x1b[()][AB012]/g;
36-
let lines = []; // array of {text, cls}
37-
let currentText = ''; // current line buffer
46+
let lines = []; // array of {text, cls} — committed (newline-terminated)
47+
let currentText = ''; // current partial line buffer
3848
let scheduledRender = false;
49+
let renderedCount = 0; // lines[0..renderedCount-1] are already in the DOM
3950

4051
function classify(text) {
4152
const t = text.toLowerCase().trimStart();
@@ -57,21 +68,43 @@ const Term = (() => {
5768
}
5869

5970
function render() {
60-
const el = document.getElementById('terminal-output');
71+
const el = document.getElementById('terminal-output');
6172
if (!el) return;
6273
const atBottom = el.scrollHeight - el.clientHeight - el.scrollTop < 30;
63-
const frag = document.createDocumentFragment();
64-
// Current partial line
65-
const allLines = [...lines, { text: currentText, cls: '' }];
66-
// Only re-render last N if performance is a concern; here we rebuild all
67-
for (const { text, cls } of allLines) {
68-
const div = document.createElement('div');
69-
div.className = 'term-line' + (cls ? ' ' + cls : '');
70-
div.textContent = text;
71-
frag.appendChild(div);
74+
75+
// If the DOM was wiped (after clear()), reset tracked count
76+
if (el.children.length === 0) renderedCount = 0;
77+
78+
// Append only newly committed lines (don't touch existing DOM nodes)
79+
if (renderedCount < lines.length) {
80+
const frag = document.createDocumentFragment();
81+
for (let i = renderedCount; i < lines.length; i++) {
82+
const { text, cls } = lines[i];
83+
const div = document.createElement('div');
84+
div.className = 'term-line' + (cls ? ' ' + cls : '');
85+
div.textContent = text;
86+
frag.appendChild(div);
87+
}
88+
// Insert before the partial-line placeholder if it exists
89+
const partDiv = el.lastChild;
90+
if (partDiv && partDiv._isPartial) {
91+
el.insertBefore(frag, partDiv);
92+
} else {
93+
el.appendChild(frag);
94+
}
95+
renderedCount = lines.length;
96+
}
97+
98+
// Update the partial-line placeholder in-place (no flicker)
99+
let partDiv = el.lastChild;
100+
if (!partDiv || !partDiv._isPartial) {
101+
partDiv = document.createElement('div');
102+
partDiv.className = 'term-line';
103+
partDiv._isPartial = true;
104+
el.appendChild(partDiv);
72105
}
73-
el.innerHTML = '';
74-
el.appendChild(frag);
106+
partDiv.textContent = currentText;
107+
75108
if (atBottom) el.scrollTop = el.scrollHeight;
76109
}
77110

@@ -84,7 +117,10 @@ const Term = (() => {
84117
const t = currentText;
85118
if (!t.startsWith('<frozen importlib')) {
86119
lines.push({ text: t, cls: classify(t) });
87-
if (lines.length > MAX_LINES) lines.shift();
120+
if (lines.length > MAX_LINES) {
121+
lines.shift();
122+
renderedCount = Math.max(0, renderedCount - 1);
123+
}
88124
}
89125
currentText = '';
90126
} else {
@@ -97,15 +133,20 @@ const Term = (() => {
97133
function write(text, cls = '') {
98134
for (const line of text.split('\n')) {
99135
lines.push({ text: line, cls });
100-
if (lines.length > MAX_LINES) lines.shift();
136+
if (lines.length > MAX_LINES) {
137+
lines.shift();
138+
renderedCount = Math.max(0, renderedCount - 1);
139+
}
101140
}
102141
scheduleRender();
103142
}
104143

105144
function clear() {
106145
lines = [];
107146
currentText = '';
108-
render();
147+
renderedCount = 0;
148+
const el = document.getElementById('terminal-output');
149+
if (el) el.innerHTML = '';
109150
setStatus('');
110151
}
111152

@@ -153,13 +194,14 @@ function statusChip(done, total) {
153194
return chip(`◐ ${done}/${total}`, 'chip-warn');
154195
}
155196

156-
function courseOptions(includeAll = false) {
197+
function courseOptions(includeAll = false, selectedId = '') {
157198
if (!State.courses.length) {
158199
return `<option value="">— no courses, add Canvas token in Settings —</option>`;
159200
}
160-
let html = includeAll ? `<option value="0">All courses</option>` : '';
201+
const sel = v => String(v) === String(selectedId) ? ' selected' : '';
202+
let html = includeAll ? `<option value="0"${sel('0')}>All courses</option>` : '';
161203
for (const c of State.courses) {
162-
html += `<option value="${c.id}">${esc(c.name)} (${c.id})</option>`;
204+
html += `<option value="${c.id}"${sel(c.id)}>${esc(c.name)} (${c.id})</option>`;
163205
}
164206
return html;
165207
}
@@ -252,7 +294,13 @@ function runChain(steps) {
252294
window.api.offProcessEvents();
253295

254296
function runNext() {
255-
if (idx >= steps.length) return;
297+
if (idx >= steps.length) {
298+
State.running = false;
299+
Term.showStop(false);
300+
Term.write('\n✓ All steps completed successfully.', 'ok');
301+
Term.setStatus('✓ done', 'var(--c-success)');
302+
return;
303+
}
256304
const [label, cmd] = steps[idx++];
257305
const stepLabel = `Step ${idx}/${steps.length}: ${label}`;
258306
Term.write(`\n${'─'.repeat(50)}\n▶ ${stepLabel}\n`, 'cmd');
@@ -418,51 +466,52 @@ async function loadDashboardStats() {
418466

419467
// ── Page: Full Pipeline ────────────────────────────────────────────────────────
420468
function buildPipeline() {
421-
const defaultCid = firstCourseId();
469+
const ps = State.pipeline;
470+
const cid = ps.courseId || firstCourseId();
422471
return `
423472
${sectionTitle('Full Pipeline Wizard', '')}
424473
${mkCard(`
425474
<div class="row">
426475
<div class="col expand">
427476
<span class="label">Course</span>
428477
<select id="pp-course" class="select-ctrl">
429-
${courseOptions()}
478+
${courseOptions(false, cid)}
430479
</select>
431480
</div>
432481
</div>
433482
`)}
434483
<div class="row" style="align-items:flex-start">
435484
${mkCard(`
436485
<span class="label">Steps to execute</span>
437-
${mkCheckbox('pp-step-mat', 'Download materials')}
438-
${mkCheckbox('pp-step-vid', 'Download videos')}
439-
${mkCheckbox('pp-step-trans', 'Transcribe videos')}
440-
${mkCheckbox('pp-step-align', 'Align transcripts')}
441-
${mkCheckbox('pp-step-gen', 'Generate study notes')}
486+
${mkCheckbox('pp-step-mat', 'Download materials', ps.steps.mat)}
487+
${mkCheckbox('pp-step-vid', 'Download videos', ps.steps.vid)}
488+
${mkCheckbox('pp-step-trans', 'Transcribe videos', ps.steps.trans)}
489+
${mkCheckbox('pp-step-align', 'Align transcripts', ps.steps.align)}
490+
${mkCheckbox('pp-step-gen', 'Generate study notes', ps.steps.gen)}
442491
`, 'col expand')}
443492
${mkCard(`
444493
<span class="label">Download</span>
445-
${mkSwitch('pp-stealth', 'Stealth mode for downloads')}
494+
${mkSwitch('pp-stealth', 'Stealth mode for downloads', ps.stealth)}
446495
<hr class="divider">
447496
<span class="label">Note generation</span>
448497
<div class="field">
449498
<label class="label">Course name for notes</label>
450-
<input id="pp-course-name" class="input-text" type="text" value="">
499+
<input id="pp-course-name" class="input-text" type="text" value="${esc(ps.courseName)}">
451500
</div>
452501
<div class="row center" style="gap:8px;margin-top:8px">
453-
<span class="slider-value" id="pp-detail-val">7</span>
502+
<span class="slider-value" id="pp-detail-val">${esc(ps.detail)}</span>
454503
<div class="col expand">
455-
<input id="pp-detail" type="range" min="0" max="10" value="7" step="1" class="slider">
504+
<input id="pp-detail" type="range" min="0" max="10" value="${esc(ps.detail)}" step="1" class="slider">
456505
<div style="font-size:10px;color:var(--c-white-35);margin-top:2px">
457506
0-2 Outline · 3-5 Bullets · 6-8 Paragraphs · 9-10 Exhaustive
458507
</div>
459508
</div>
460509
</div>
461510
<div class="field" style="margin-top:8px">
462511
<label class="label">Lecture filter</label>
463-
<input id="pp-lec-filter" class="input-text" type="text" placeholder="1-5 or 1,3,5 (blank=all)">
512+
<input id="pp-lec-filter" class="input-text" type="text" value="${esc(ps.lecFilter)}" placeholder="1-5 or 1,3,5 (blank=all)">
464513
</div>
465-
${mkSwitch('pp-force', 'Force regenerate')}
514+
${mkSwitch('pp-force', 'Force regenerate', ps.force)}
466515
`, 'col expand')}
467516
</div>
468517
<div class="row">
@@ -1127,7 +1176,48 @@ async function attachPageHandlers() {
11271176
if (pg === 1) {
11281177
const detSlider = document.getElementById('pp-detail');
11291178
const detLabel = document.getElementById('pp-detail-val');
1130-
detSlider?.addEventListener('input', () => { detLabel.textContent = detSlider.value; });
1179+
detSlider?.addEventListener('input', () => {
1180+
detLabel.textContent = detSlider.value;
1181+
State.pipeline.detail = detSlider.value;
1182+
});
1183+
1184+
// Persist all form values so they survive navigation
1185+
document.getElementById('pp-course')?.addEventListener('change', e => {
1186+
State.pipeline.courseId = e.target.value;
1187+
const nameEl = document.getElementById('pp-course-name');
1188+
if (nameEl && !nameEl.value.trim()) {
1189+
nameEl.value = courseNameFromId(e.target.value);
1190+
State.pipeline.courseName = nameEl.value;
1191+
}
1192+
});
1193+
document.getElementById('pp-course-name')?.addEventListener('input', e => {
1194+
State.pipeline.courseName = e.target.value;
1195+
});
1196+
document.getElementById('pp-lec-filter')?.addEventListener('input', e => {
1197+
State.pipeline.lecFilter = e.target.value;
1198+
});
1199+
document.getElementById('pp-stealth')?.addEventListener('change', e => {
1200+
State.pipeline.stealth = e.target.checked;
1201+
});
1202+
document.getElementById('pp-force')?.addEventListener('change', e => {
1203+
State.pipeline.force = e.target.checked;
1204+
});
1205+
for (const [k, id] of [
1206+
['mat','pp-step-mat'],['vid','pp-step-vid'],['trans','pp-step-trans'],
1207+
['align','pp-step-align'],['gen','pp-step-gen'],
1208+
]) {
1209+
document.getElementById(id)?.addEventListener('change', e => {
1210+
State.pipeline.steps[k] = e.target.checked;
1211+
});
1212+
}
1213+
1214+
// Auto-fill course name if blank on first render
1215+
const ppCourseEl = document.getElementById('pp-course');
1216+
const ppNameEl = document.getElementById('pp-course-name');
1217+
if (ppCourseEl && ppNameEl && !ppNameEl.value.trim() && ppCourseEl.value) {
1218+
ppNameEl.value = courseNameFromId(ppCourseEl.value);
1219+
State.pipeline.courseName = ppNameEl.value;
1220+
}
11311221

11321222
document.getElementById('pp-run-btn')?.addEventListener('click', async () => {
11331223
const cid = document.getElementById('pp-course')?.value;

0 commit comments

Comments
 (0)