Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions tools/v1/individual/email-to-todo-converter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,28 @@ Do not wire this tool into the main app, routing, inbox architecture, wallet cor

## Contributor Setup

This tool does not ship executable code yet. Until a feature issue adds the
implementation, contributors should use the local documentation in this folder
as the launch contract:
This tool now ships a self-contained implementation plus tests and docs. Use the
local documentation in this folder as the launch contract:

- `specs.md` defines the behavior and folder ownership boundary.
- `docs/test-plan.md` lists the acceptance scenarios that future tests should
cover.
- `docs/API.md` documents the local UI, helper, and data contracts.
- `docs/fixtures.md` describes the fixture emails and expected task outputs.
- `REVIEW_NOTES.md` gives reviewers a quick checklist for this isolated work.

## Intended Usage

The tool converts an email into one or more actionable tasks. A future feature
implementation should accept a normalized email object, extract the task title,
due date, priority, source metadata, and completion state, then return a
reviewable task draft without mutating the mailbox or main application state.
The tool converts an email into one or more actionable tasks. It accepts a
normalized email object, extracts the task title, due date, priority, and
source metadata, then returns a reviewable task draft without mutating the
mailbox or main application state.

## Known Limitations

- No production code is present in this folder yet.
- The documented tests are a plan, not an executable suite.
- The implementation remains isolated from the main app until a future
integration issue allows wiring.
- The component is still review-first; saving to an external task system is not
wired up.
- Main app routing, inbox integration, and persistence are intentionally out of
scope until a future integration issue allows them.
10 changes: 5 additions & 5 deletions tools/v1/individual/email-to-todo-converter/REVIEW_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# Review Notes

This issue is documentation and test-plan work for the isolated
Email-to-Todo Converter folder.
This issue covers the isolated Email-to-Todo Converter implementation,
including docs, deterministic extraction behavior, and test coverage.

## What Changed

- Replaced generated placeholder text in `specs.md` with the V1 individual tool
contract.
- Added a contributor-facing setup, usage, and limitations section to
`README.md`.
- Added `docs/test-plan.md` with unit, component, and non-goal coverage.
- Added `docs/fixtures.md` with representative email inputs and expected task
draft outcomes.
- Added `docs/test-plan.md`, `docs/API.md`, and `docs/fixtures.md` to document
the local contract.
- Added the deterministic UI helpers and review-first component under `ui/`.

## Review Checklist

Expand Down
93 changes: 93 additions & 0 deletions tools/v1/individual/email-to-todo-converter/docs/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Email-to-Todo Converter API

## Overview

This folder exposes a small, deterministic UI surface plus pure helper
functions. The implementation stays local to the tool folder and does not depend
on the main app shell, routing, inbox, wallet, Stellar, or database layers.

## UI

### `EmailToTodoConverter`

```ts
interface EmailToTodoConverterProps {
email: NormalizedEmail | null;
onSaveDraft?: (draft: TaskDraft) => void;
idPrefix?: string;
}
```

Renders the current email, a convert action, validation/error feedback, and a
reviewable task draft.

### Behavior

- Disabled when no convertible email is available.
- Announces loading, success, and error states through accessible live regions.
- Preserves review-before-save behavior; nothing is persisted automatically.

## Helpers

### `buildTaskTitle(email)`

Extracts a task title from the subject when it is actionable. If the subject is
generic, it falls back to the first actionable sentence in the body.

### `buildTaskNotes(email)`

Produces a normalized notes field from the full body text.

### `detectPriority(email)`

Returns `high` for urgent language and `normal` otherwise.

### `suggestDueDate(email, priority)`

Uses an explicit due date when one is present in the email text. Otherwise, it
falls back to a deterministic offset from `receivedAt`.

### `buildTaskDraft(email)`

Produces the full draft object, including source metadata and suggested due
date/priority.

### `hasConvertibleContent(email)`

Checks whether a selected email contains enough content to convert.

## Data Types

### `NormalizedEmail`

```ts
interface NormalizedEmail {
id?: string;
subject: string;
sender: string;
receivedAt: string;
body: string;
labels?: string[];
}
```

### `TaskDraft`

```ts
interface TaskDraft {
title: string;
notes: string;
sourceEmailId?: string;
sourceSubject: string;
sourceSender: string;
sourceReceivedAt: string;
suggestedDueDate: string;
suggestedPriority: "normal" | "high";
}
```

## Notes

- The draft is intentionally review-first.
- Saving is delegated to the host application through `onSaveDraft`.
- The helper functions are deterministic and suitable for unit testing.
10 changes: 5 additions & 5 deletions tools/v1/individual/email-to-todo-converter/docs/test-plan.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Email-to-Todo Converter Test Plan

This folder does not contain executable tool code yet, so this document is the
folder-local test plan for issue #358. Convert each scenario below into unit or
component tests when the feature implementation lands.
This folder contains the isolated tool implementation for issue #358, and this
document is the folder-local test plan for the converter behavior. Use the
scenarios below as the acceptance contract for unit or component tests.

## Unit Scenarios

1. Extracts a task title from a direct request in the email subject.
2. Extracts a task title from the first actionable sentence in the body when the
subject is generic.
3. Preserves the source sender, source subject, and received timestamp in task
metadata.
3. Preserves the source email id, sender, subject, and received timestamp in
task metadata when available.
4. Converts explicit due dates such as "by Friday" or "due 2026-07-01" into a
normalized due-date field.
5. Leaves the due-date field empty when the email has no deadline.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,9 @@ describe("detectPriority", () => {
expect(detectPriority(baseEmail({ subject: "URGENT: sign the contract" }))).toBe("high");
});

it("returns medium when a soft keyword is present", () => {
expect(detectPriority(baseEmail({ subject: "Reminder: timesheet" }))).toBe("medium");
});

it("returns low when no priority keywords are present", () => {
it("returns normal when no urgent keyword is present", () => {
expect(detectPriority(baseEmail({ subject: "Lunch menu", body: "Soup and salad." }))).toBe(
"low",
"normal",
);
});
});
Expand All @@ -45,12 +41,24 @@ describe("suggestDueDate", () => {
});

it("uses the default offset for lower priorities", () => {
expect(suggestDueDate(baseEmail(), "low")).toBe("2026-01-13");
expect(suggestDueDate(baseEmail(), "normal")).toBe("2026-01-13");
expect(DEFAULT_DUE_DATE_OFFSET_DAYS).toBe(3);
});

it("returns an empty string for an unparseable timestamp", () => {
expect(suggestDueDate(baseEmail({ receivedAt: "not-a-date" }), "low")).toBe("");
expect(suggestDueDate(baseEmail({ receivedAt: "not-a-date" }), "normal")).toBe("");
});

it("uses an explicit due date when one is present", () => {
expect(
suggestDueDate(
baseEmail({
subject: "Please review the invoice by Friday",
body: "Please review the attached invoice by Friday and let me know if anything is missing.",
}),
"normal",
),
).toBe("2026-01-16");
});
});

Expand All @@ -61,14 +69,44 @@ describe("buildTaskDraft", () => {
const draft = buildTaskDraft(email);
expect(draft.title).toBe("Project kickoff notes");
expect(draft.sourceSender).toBe("alex@example.com");
expect(draft.suggestedPriority).toBe("low");
expect(draft.suggestedPriority).toBe("normal");
});

it("preserves the source email id when present", () => {
const draft = buildTaskDraft(baseEmail({ id: "email-direct-request" }));
expect(draft.sourceEmailId).toBe("email-direct-request");
});

it("extracts a title from a direct request in the subject", () => {
const draft = buildTaskDraft(
baseEmail({
subject: "Please review the invoice by Friday",
body: "Please review the attached invoice by Friday and let me know if anything is missing.",
}),
);
expect(draft.title).toBe("Review the invoice");
expect(draft.suggestedDueDate).toBe("2026-01-17");
});

it("uses the first actionable sentence in the body when the subject is generic", () => {
const draft = buildTaskDraft(
baseEmail({
subject: "Weekly product updates",
body: "Here are this week's product updates.\nPlease follow up with the partner today.",
}),
);
expect(draft.title).toBe("Follow up with the partner");
expect(draft.suggestedDueDate).toBe("2026-01-10");
});

it("falls back to the first body line when the subject is empty", () => {
it("strips trailing deadline words from a body-derived title", () => {
const draft = buildTaskDraft(
baseEmail({ subject: " ", body: "Call the bank about the invoice." }),
baseEmail({
subject: "Weekly product updates",
body: "Please call the bank today.",
}),
);
expect(draft.title).toBe("Call the bank about the invoice.");
expect(draft.title).toBe("Call the bank");
});

it("falls back to a placeholder when subject and body are empty", () => {
Expand Down
Loading
Loading