Add today-view plugin with Tana, ICS, and Donetick data sources
Implements an e-ink daily dashboard plugin ("today") with four sections:
date/focus/success header, agenda timeline, chores checklist, and
due/overdue task lists.
Data sources:
- Focus & success text: Tana daily note (src/sources/tana.ts)
- Due/overdue tasks: Tana task search (src/sources/tana.ts)
- Agenda events: ICS calendar feeds (src/sources/ics.ts)
- Chores: Donetick API (src/sources/donetick.ts)
All sources fetch in parallel and fall back gracefully on error.
Tests use mock HTTP servers with synthetic data — no real services needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ddcb03d3dd
commit
2e34246d14
12 changed files with 1665 additions and 11 deletions
|
|
@ -125,3 +125,98 @@ describe('calendar plugin rendering', () => {
|
|||
expect(html).not.toContain('BEZET');
|
||||
});
|
||||
});
|
||||
|
||||
// --- Today plugin test data ---
|
||||
|
||||
import type { TodayData } from './todayview';
|
||||
|
||||
const SAMPLE_TODAY: TodayData = {
|
||||
focus: 'Ship the Q2 retrospective deck — clarity over completeness.',
|
||||
success: 'Three deep-work blocks. One real conversation. Inbox under 10.',
|
||||
agenda: [
|
||||
{ start: '08:30', end: '09:00', title: 'Morning planning', where: 'Office', kind: 'solo' },
|
||||
{ start: '09:30', end: '10:15', title: 'Design review', where: 'Meet', kind: 'call' },
|
||||
{ start: '11:00', end: '12:00', title: '1:1 with Priya', where: 'Walk', kind: 'in-person' },
|
||||
{ start: '13:30', end: '15:00', title: 'Deep work — deck', where: 'Library', kind: 'focus block' },
|
||||
{ start: '16:00', end: '16:30', title: 'Eng standup', where: 'Zoom', kind: 'call' },
|
||||
],
|
||||
chores: [
|
||||
{ text: 'Make the bed' },
|
||||
{ text: '20 min reading' },
|
||||
{ text: 'Water the plants' },
|
||||
{ text: 'Walk · 30 min' },
|
||||
{ text: 'Journal · 3 lines' },
|
||||
{ text: 'Lights out by 23:00' },
|
||||
],
|
||||
todos_due: [
|
||||
{ text: 'Review Atlas spec comments', tag: 'Atlas', est: '30m', done: false },
|
||||
{ text: 'Draft retro deck — sections 1–3', tag: 'Deck', est: '1h', done: false },
|
||||
{ text: 'Reply to Marcus re: contract', tag: 'Ops', est: '10m', done: false },
|
||||
{ text: 'Submit June expenses', tag: 'Admin', est: '15m', done: false },
|
||||
],
|
||||
todos_overdue: [
|
||||
{ text: 'File Q1 receipts', tag: 'Admin', age: '3d' },
|
||||
{ text: 'Renew domain · seva.dev', tag: 'Ops', age: '1d' },
|
||||
{ text: 'Book dentist', tag: 'Health', age: '9d' },
|
||||
],
|
||||
};
|
||||
|
||||
describe('today plugin', () => {
|
||||
test('renders all sections', async () => {
|
||||
const plugin = createPlugin('today', SAMPLE_TODAY);
|
||||
const html = await plugin.render('full', fakeModel);
|
||||
|
||||
// Top bar
|
||||
expect(html).toContain('FOCUS');
|
||||
expect(html).toContain('SUCCESS');
|
||||
expect(html).toContain('retrospective deck');
|
||||
|
||||
// Agenda
|
||||
expect(html).toContain('Agenda');
|
||||
expect(html).toContain('Morning planning');
|
||||
expect(html).toContain('Eng standup');
|
||||
|
||||
// Chores
|
||||
expect(html).toContain('Chores');
|
||||
expect(html).toContain('Make the bed');
|
||||
expect(html).toContain('Water the plants');
|
||||
|
||||
// Tasks
|
||||
expect(html).toContain('Due today');
|
||||
expect(html).toContain('Overdue');
|
||||
expect(html).toContain('Review Atlas spec comments');
|
||||
expect(html).toContain('File Q1 receipts');
|
||||
});
|
||||
|
||||
test('renders with minimal data', async () => {
|
||||
const plugin = createPlugin('today', {
|
||||
focus: 'Custom focus text',
|
||||
success: 'Custom success text',
|
||||
agenda: [
|
||||
{ start: '09:00', end: '10:00', title: 'Custom Event', where: 'Room', kind: 'call' },
|
||||
],
|
||||
chores: [{ text: 'Custom chore' }],
|
||||
todos_due: [{ text: 'Custom task', tag: 'Test', est: '5m', done: false }],
|
||||
todos_overdue: [],
|
||||
});
|
||||
const html = await plugin.render('full', fakeModel);
|
||||
|
||||
expect(html).toContain('Custom focus text');
|
||||
expect(html).toContain('Custom Event');
|
||||
expect(html).toContain('Custom chore');
|
||||
expect(html).toContain('Custom task');
|
||||
});
|
||||
|
||||
test('computes hash after render', async () => {
|
||||
const plugin = createPlugin('today', SAMPLE_TODAY);
|
||||
await plugin.render('full', fakeModel);
|
||||
expect(plugin.hash).toBeTruthy();
|
||||
});
|
||||
|
||||
test('renders today date', async () => {
|
||||
const plugin = createPlugin('today', SAMPLE_TODAY);
|
||||
const html = await plugin.render('full', fakeModel);
|
||||
const dayStr = String(new Date().getDate()).padStart(2, '0');
|
||||
expect(html).toContain(dayStr);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue