Add Insights, Observe-first, compare, handout layouts, richer search, backup nudge
- Commentary and study aids wait for 15 words of your own Observe notes. - Insights page: this week's own work, themes across studies, words you keep studying, what slipped in recalls, open follow-ups. - Recall can record what slipped; session notes (teach it back, who came, group notes, follow-ups); "Pray about this" from Apply. - Compare any two passages side by side. - Handout layouts: standard, questions only, large print, note-taking. - Search understands "in <book>", "last week/month/year" and a year. - Home shows the last backup age and nudges after two weeks. - Phone: the stage bar scrolls instead of clipping Script and Export. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2ddc4e1ff6
commit
d8e522dd06
24 files changed
+540
-28
No files matched your search
@@ -0,0 +1,48 @@
|
||||
import { test, expect } from 'vitest';
|
||||
import { weeklySummary, themeIndex, wordIndex, slippedList, followUpList } from './insights.js';
|
||||
import { WEEK_MS } from './recall.js';
|
||||
|
||||
const NOW = 100 * WEEK_MS;
|
||||
const text = (n) => Array.from({ length: n }, (_, i) => `w${i}`).join(' ');
|
||||
const chunk = (id, extra = {}) => ({ id, startVerse: 1, endVerse: 5, observation: text(20), editedAt: NOW - 1000, ...extra });
|
||||
const study = (chunks, id = 'p1', title = 'Romans') => ({ id, title, translation: 'BSB', chapters: [{ book: 'Romans', bookAbbrev: 'Rom', chapter: 8, chunks }] });
|
||||
|
||||
test('the weekly summary counts only chunks edited this week', () => {
|
||||
const pasted = text(10);
|
||||
const projects = [study([
|
||||
chunk('a', { teachBack: 'x', recalls: [{ at: NOW - 1000, rating: 'solid' }] }),
|
||||
chunk('b', { observation: `${text(10)} ${pasted}`, aiPastes: { observation: [pasted] }, editedAt: NOW - 500 }),
|
||||
chunk('old', { editedAt: NOW - 3 * WEEK_MS, recalls: [{ at: NOW - 3 * WEEK_MS, rating: 'solid' }] }),
|
||||
])];
|
||||
const s = weeklySummary(projects, NOW);
|
||||
expect(s).toMatchObject({ chunksTouched: 2, ownWords: 30, recalls: 1, teachBacks: 1 });
|
||||
expect(s.ownShare).toBe(75); // 100% and 50%, averaged
|
||||
expect(weeklySummary([], NOW).ownShare).toBeNull();
|
||||
});
|
||||
|
||||
test('tags group notes from different studies into themes, most used first', () => {
|
||||
const p1 = study([chunk('a', { tags: ['Grace'] }), chunk('b', { tags: ['hope'] })]);
|
||||
const p2 = study([chunk('c', { tags: ['grace '] })], 'p2', 'John');
|
||||
const themes = themeIndex([p1, p2]);
|
||||
expect(themes.map((t) => [t.tag, t.count])).toEqual([['Grace', 2], ['hope', 1]]);
|
||||
expect(themes[0].items.map((i) => i.projectTitle)).toEqual(['Romans', 'John']);
|
||||
});
|
||||
|
||||
test('words that come up in more than one place are listed', () => {
|
||||
const w = (n, g) => ({ id: n, strongNumber: n, lexeme: g, englishGloss: g });
|
||||
const projects = [study([chunk('a', { greekWords: [w('G26', 'agape'), w('G4102', 'pistis')] }), chunk('b', { greekWords: [w('g26', 'agape')] })])];
|
||||
const list = wordIndex(projects);
|
||||
expect(list).toHaveLength(1);
|
||||
expect(list[0]).toMatchObject({ strong: 'G26', count: 2 });
|
||||
expect(wordIndex(projects, { minCount: 1 })).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('slipped lines and follow-ups are collected, open follow-ups first', () => {
|
||||
const projects = [study([chunk('a', {
|
||||
recalls: [{ at: 5, gaps: '- the serpent\nwho spoke', rating: 'partly' }, { at: 9, gaps: '', rating: 'solid' }],
|
||||
followUps: [{ id: '1', text: 'done thing', done: true }, { id: '2', text: 'call Sam', done: false }],
|
||||
})])];
|
||||
expect(slippedList(projects)).toHaveLength(1);
|
||||
expect(slippedList(projects)[0].items).toEqual(['the serpent', 'who spoke']);
|
||||
expect(followUpList(projects).map((f) => f.text)).toEqual(['call Sam', 'done thing']);
|
||||
});
|
||||
Reference in new issue
Block a user