diff --git a/apps/x/apps/renderer/src/components/settings-dialog.tsx b/apps/x/apps/renderer/src/components/settings-dialog.tsx index fb28e2b0..6d6dfbfc 100644 --- a/apps/x/apps/renderer/src/components/settings-dialog.tsx +++ b/apps/x/apps/renderer/src/components/settings-dialog.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { useState, useEffect, useCallback, useMemo } from "react" -import { Server, Key, Shield, Palette, Monitor, Sun, Moon, Loader2, CheckCircle2, Tags, ChevronRight, Plus, X } from "lucide-react" +import { Server, Key, Shield, Palette, Monitor, Sun, Moon, Loader2, CheckCircle2, Tags, Mail, BookOpen, ChevronRight, Plus, X } from "lucide-react" import { Dialog, @@ -18,6 +18,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" +import { Switch } from "@/components/ui/switch" import { cn } from "@/lib/utils" import { useTheme } from "@/contexts/theme-context" import { toast } from "sonner" @@ -700,11 +701,15 @@ interface TagDef { applicability: "email" | "notes" | "both" description: string example?: string + noteEffect?: "create" | "skip" | "none" } -const TAG_TYPE_ORDER = [ - "relationship", "relationship-sub", "topic", "email-type", - "filter", "action", "status", "source", +const NOTE_TAG_TYPE_ORDER = [ + "relationship", "relationship-sub", "topic", "action", "status", "source", +] + +const EMAIL_TAG_TYPE_ORDER = [ + "relationship", "topic", "email-type", "filter", "action", "status", ] const TAG_TYPE_LABELS: Record = { @@ -719,73 +724,183 @@ const TAG_TYPE_LABELS: Record = { } const DEFAULT_TAGS: TagDef[] = [ - { tag: "investor", type: "relationship", applicability: "both", description: "Investors, VCs, or angels", example: "Following up on our meeting — we'd like to move forward with the Series A term sheet." }, - { tag: "customer", type: "relationship", applicability: "both", description: "Paying customers", example: "We're seeing great results with Rowboat. Can we discuss expanding to more teams?" }, - { tag: "prospect", type: "relationship", applicability: "both", description: "Potential customers", example: "Thanks for the demo yesterday. We're interested in starting a pilot." }, - { tag: "partner", type: "relationship", applicability: "both", description: "Business partners", example: "Let's discuss how we can promote the integration to both our user bases." }, - { tag: "vendor", type: "relationship", applicability: "both", description: "Service providers you work with", example: "Here are the updated employment agreements you requested." }, - { tag: "product", type: "relationship", applicability: "both", description: "Products or services you use (automated)", example: "Your AWS bill for January 2025 is now available." }, - { tag: "candidate", type: "relationship", applicability: "both", description: "Job applicants", example: "Thanks for reaching out. I'd love to learn more about the engineering role." }, - { tag: "team", type: "relationship", applicability: "both", description: "Internal team members", example: "Here's the updated roadmap for Q2. Let's discuss in our sync." }, - { tag: "advisor", type: "relationship", applicability: "both", description: "Advisors, mentors, or board members", example: "I've reviewed the deck. Here are my thoughts on the GTM strategy." }, - { tag: "personal", type: "relationship", applicability: "both", description: "Family or friends", example: "Are you coming to Thanksgiving this year? Let me know your travel dates." }, - { tag: "press", type: "relationship", applicability: "both", description: "Journalists or media", example: "I'm writing a piece on AI agents. Would you be available for an interview?" }, - { tag: "community", type: "relationship", applicability: "both", description: "Users, peers, or open source contributors", example: "Love what you're building with Rowboat. Here's a bug I found..." }, - { tag: "government", type: "relationship", applicability: "both", description: "Government agencies", example: "Your Delaware franchise tax is due by March 1, 2025." }, - { tag: "primary", type: "relationship-sub", applicability: "notes", description: "Main contact or decision maker", example: "Sarah Chen — VP Engineering, your main point of contact at Acme." }, - { tag: "secondary", type: "relationship-sub", applicability: "notes", description: "Supporting contact, involved but not the lead", example: "David Kim — Engineer CC'd on customer emails." }, - { tag: "executive-assistant", type: "relationship-sub", applicability: "notes", description: "EA or admin handling scheduling and logistics", example: "Lisa — Sarah's EA who schedules all her meetings." }, - { tag: "cc", type: "relationship-sub", applicability: "notes", description: "Person who's CC'd but not actively engaged", example: "Manager looped in for visibility on deal." }, - { tag: "referred-by", type: "relationship-sub", applicability: "notes", description: "Person who made an introduction or referral", example: "David Park — Investor who intro'd you to Sarah." }, - { tag: "former", type: "relationship-sub", applicability: "notes", description: "Previously held this relationship, no longer active", example: "John — Former customer who churned last year." }, - { tag: "champion", type: "relationship-sub", applicability: "notes", description: "Internal advocate pushing for you", example: "Engineer who loves your product and is selling internally." }, - { tag: "blocker", type: "relationship-sub", applicability: "notes", description: "Person opposing or blocking progress", example: "CFO resistant to spending on new tools." }, - { tag: "sales", type: "topic", applicability: "both", description: "Sales conversations, deals, and revenue", example: "Here's the pricing proposal we discussed. Let me know if you have questions." }, - { tag: "support", type: "topic", applicability: "both", description: "Help requests, issues, and customer support", example: "We're seeing an error when trying to export. Can you help?" }, - { tag: "legal", type: "topic", applicability: "both", description: "Contracts, terms, compliance, and legal matters", example: "Legal has reviewed the MSA. Attached are our requested changes." }, - { tag: "finance", type: "topic", applicability: "both", description: "Money, invoices, payments, banking, and taxes", example: "Your invoice #1234 for $5,000 is attached. Payment due in 30 days." }, - { tag: "hiring", type: "topic", applicability: "both", description: "Recruiting, interviews, and employment", example: "We'd like to move forward with a final round interview. Are you available Thursday?" }, - { tag: "fundraising", type: "topic", applicability: "both", description: "Raising money and investor relations", example: "Thanks for sending the deck. We'd like to schedule a partner meeting." }, - { tag: "travel", type: "topic", applicability: "both", description: "Flights, hotels, trips, and travel logistics", example: "Your flight to Tokyo on March 15 is confirmed. Confirmation #ABC123." }, - { tag: "event", type: "topic", applicability: "both", description: "Conferences, meetups, and gatherings", example: "You're invited to speak at TechCrunch Disrupt. Can you confirm your availability?" }, - { tag: "shopping", type: "topic", applicability: "both", description: "Purchases, orders, and returns", example: "Your order #12345 has shipped. Track it here." }, - { tag: "health", type: "topic", applicability: "both", description: "Medical, wellness, and health-related matters", example: "Your appointment with Dr. Smith is confirmed for Monday at 2pm." }, - { tag: "learning", type: "topic", applicability: "both", description: "Courses, education, and skill-building", example: "Welcome to the Advanced Python course. Here's your access link." }, - { tag: "research", type: "topic", applicability: "both", description: "Research requests and information gathering", example: "Here's the market analysis you requested on the AI agent space." }, - { tag: "intro", type: "email-type", applicability: "both", description: "Warm introduction from someone you know", example: "I'd like to introduce you to Sarah Chen, VP Engineering at Acme." }, - { tag: "followup", type: "email-type", applicability: "both", description: "Following up on a previous conversation", example: "Following up on our call last week. Have you had a chance to review the proposal?" }, - { tag: "scheduling", type: "email-type", applicability: "email", description: "Meeting and calendar scheduling", example: "Are you available for a call next Tuesday at 2pm?" }, - { tag: "cold-outreach", type: "email-type", applicability: "email", description: "Unsolicited contact from someone you don't know", example: "Hi, I noticed your company is growing fast. I'd love to show you how we can help with..." }, - { tag: "newsletter", type: "email-type", applicability: "email", description: "Newsletters, marketing emails, and subscriptions", example: "This week in AI: The latest developments in agent frameworks..." }, - { tag: "notification", type: "email-type", applicability: "email", description: "Automated alerts, receipts, and system notifications", example: "Your password was changed successfully. If this wasn't you, contact support." }, - { tag: "spam", type: "filter", applicability: "email", description: "Junk and unwanted email", example: "Congratulations! You've won $1,000,000..." }, - { tag: "promotion", type: "filter", applicability: "email", description: "Marketing offers and sales pitches", example: "50% off all items this weekend only!" }, - { tag: "social", type: "filter", applicability: "email", description: "Social media notifications", example: "John Smith commented on your post." }, - { tag: "forums", type: "filter", applicability: "email", description: "Mailing lists and group discussions", example: "Re: [dev-list] Question about API design" }, - { tag: "action-required", type: "action", applicability: "both", description: "Needs a response or action from you", example: "Can you send me the pricing by Friday?" }, - { tag: "fyi", type: "action", applicability: "email", description: "Informational only, no action needed", example: "Just wanted to let you know the deal closed. Thanks for your help!" }, - { tag: "urgent", type: "action", applicability: "both", description: "Time-sensitive, needs immediate attention", example: "We need your signature on the contract by EOD today or we lose the deal." }, - { tag: "waiting", type: "action", applicability: "both", description: "Waiting on a response from them" }, - { tag: "unread", type: "status", applicability: "email", description: "Not yet processed" }, - { tag: "to-reply", type: "status", applicability: "email", description: "Need to respond" }, - { tag: "done", type: "status", applicability: "email", description: "Handled, can be archived" }, - { tag: "active", type: "status", applicability: "notes", description: "Currently relevant, recent activity" }, - { tag: "archived", type: "status", applicability: "notes", description: "No longer active, kept for reference" }, - { tag: "stale", type: "status", applicability: "notes", description: "No activity in 60+ days, needs attention or archive" }, - { tag: "email", type: "source", applicability: "notes", description: "Created or updated from email" }, - { tag: "meeting", type: "source", applicability: "notes", description: "Created or updated from meeting transcript" }, - { tag: "browser", type: "source", applicability: "notes", description: "Content captured from web browsing" }, - { tag: "web-search", type: "source", applicability: "notes", description: "Information from web search" }, - { tag: "manual", type: "source", applicability: "notes", description: "Manually entered by user" }, - { tag: "import", type: "source", applicability: "notes", description: "Imported from another system" }, + { tag: "investor", type: "relationship", applicability: "both", noteEffect: "create", description: "Investors, VCs, or angels", example: "Following up on our meeting — we'd like to move forward with the Series A term sheet." }, + { tag: "customer", type: "relationship", applicability: "both", noteEffect: "create", description: "Paying customers", example: "We're seeing great results with Rowboat. Can we discuss expanding to more teams?" }, + { tag: "prospect", type: "relationship", applicability: "both", noteEffect: "create", description: "Potential customers", example: "Thanks for the demo yesterday. We're interested in starting a pilot." }, + { tag: "partner", type: "relationship", applicability: "both", noteEffect: "create", description: "Business partners", example: "Let's discuss how we can promote the integration to both our user bases." }, + { tag: "vendor", type: "relationship", applicability: "both", noteEffect: "create", description: "Service providers you work with", example: "Here are the updated employment agreements you requested." }, + { tag: "product", type: "relationship", applicability: "both", noteEffect: "skip", description: "Products or services you use (automated)", example: "Your AWS bill for January 2025 is now available." }, + { tag: "candidate", type: "relationship", applicability: "both", noteEffect: "create", description: "Job applicants", example: "Thanks for reaching out. I'd love to learn more about the engineering role." }, + { tag: "team", type: "relationship", applicability: "both", noteEffect: "create", description: "Internal team members", example: "Here's the updated roadmap for Q2. Let's discuss in our sync." }, + { tag: "advisor", type: "relationship", applicability: "both", noteEffect: "create", description: "Advisors, mentors, or board members", example: "I've reviewed the deck. Here are my thoughts on the GTM strategy." }, + { tag: "personal", type: "relationship", applicability: "both", noteEffect: "create", description: "Family or friends", example: "Are you coming to Thanksgiving this year? Let me know your travel dates." }, + { tag: "press", type: "relationship", applicability: "both", noteEffect: "create", description: "Journalists or media", example: "I'm writing a piece on AI agents. Would you be available for an interview?" }, + { tag: "community", type: "relationship", applicability: "both", noteEffect: "create", description: "Users, peers, or open source contributors", example: "Love what you're building with Rowboat. Here's a bug I found..." }, + { tag: "government", type: "relationship", applicability: "both", noteEffect: "create", description: "Government agencies", example: "Your Delaware franchise tax is due by March 1, 2025." }, + { tag: "primary", type: "relationship-sub", applicability: "notes", noteEffect: "none", description: "Main contact or decision maker", example: "Sarah Chen — VP Engineering, your main point of contact at Acme." }, + { tag: "secondary", type: "relationship-sub", applicability: "notes", noteEffect: "none", description: "Supporting contact, involved but not the lead", example: "David Kim — Engineer CC'd on customer emails." }, + { tag: "executive-assistant", type: "relationship-sub", applicability: "notes", noteEffect: "none", description: "EA or admin handling scheduling and logistics", example: "Lisa — Sarah's EA who schedules all her meetings." }, + { tag: "cc", type: "relationship-sub", applicability: "notes", noteEffect: "none", description: "Person who's CC'd but not actively engaged", example: "Manager looped in for visibility on deal." }, + { tag: "referred-by", type: "relationship-sub", applicability: "notes", noteEffect: "none", description: "Person who made an introduction or referral", example: "David Park — Investor who intro'd you to Sarah." }, + { tag: "former", type: "relationship-sub", applicability: "notes", noteEffect: "none", description: "Previously held this relationship, no longer active", example: "John — Former customer who churned last year." }, + { tag: "champion", type: "relationship-sub", applicability: "notes", noteEffect: "none", description: "Internal advocate pushing for you", example: "Engineer who loves your product and is selling internally." }, + { tag: "blocker", type: "relationship-sub", applicability: "notes", noteEffect: "none", description: "Person opposing or blocking progress", example: "CFO resistant to spending on new tools." }, + { tag: "sales", type: "topic", applicability: "both", noteEffect: "create", description: "Sales conversations, deals, and revenue", example: "Here's the pricing proposal we discussed. Let me know if you have questions." }, + { tag: "support", type: "topic", applicability: "both", noteEffect: "create", description: "Help requests, issues, and customer support", example: "We're seeing an error when trying to export. Can you help?" }, + { tag: "legal", type: "topic", applicability: "both", noteEffect: "create", description: "Contracts, terms, compliance, and legal matters", example: "Legal has reviewed the MSA. Attached are our requested changes." }, + { tag: "finance", type: "topic", applicability: "both", noteEffect: "create", description: "Money, invoices, payments, banking, and taxes", example: "Your invoice #1234 for $5,000 is attached. Payment due in 30 days." }, + { tag: "hiring", type: "topic", applicability: "both", noteEffect: "create", description: "Recruiting, interviews, and employment", example: "We'd like to move forward with a final round interview. Are you available Thursday?" }, + { tag: "fundraising", type: "topic", applicability: "both", noteEffect: "create", description: "Raising money and investor relations", example: "Thanks for sending the deck. We'd like to schedule a partner meeting." }, + { tag: "travel", type: "topic", applicability: "both", noteEffect: "skip", description: "Flights, hotels, trips, and travel logistics", example: "Your flight to Tokyo on March 15 is confirmed. Confirmation #ABC123." }, + { tag: "event", type: "topic", applicability: "both", noteEffect: "create", description: "Conferences, meetups, and gatherings", example: "You're invited to speak at TechCrunch Disrupt. Can you confirm your availability?" }, + { tag: "shopping", type: "topic", applicability: "both", noteEffect: "skip", description: "Purchases, orders, and returns", example: "Your order #12345 has shipped. Track it here." }, + { tag: "health", type: "topic", applicability: "both", noteEffect: "skip", description: "Medical, wellness, and health-related matters", example: "Your appointment with Dr. Smith is confirmed for Monday at 2pm." }, + { tag: "learning", type: "topic", applicability: "both", noteEffect: "skip", description: "Courses, education, and skill-building", example: "Welcome to the Advanced Python course. Here's your access link." }, + { tag: "research", type: "topic", applicability: "both", noteEffect: "create", description: "Research requests and information gathering", example: "Here's the market analysis you requested on the AI agent space." }, + { tag: "intro", type: "email-type", applicability: "both", noteEffect: "create", description: "Warm introduction from someone you know", example: "I'd like to introduce you to Sarah Chen, VP Engineering at Acme." }, + { tag: "followup", type: "email-type", applicability: "both", noteEffect: "create", description: "Following up on a previous conversation", example: "Following up on our call last week. Have you had a chance to review the proposal?" }, + { tag: "scheduling", type: "email-type", applicability: "email", noteEffect: "skip", description: "Meeting and calendar scheduling", example: "Are you available for a call next Tuesday at 2pm?" }, + { tag: "cold-outreach", type: "email-type", applicability: "email", noteEffect: "skip", description: "Unsolicited contact from someone you don't know", example: "Hi, I noticed your company is growing fast. I'd love to show you how we can help with..." }, + { tag: "newsletter", type: "email-type", applicability: "email", noteEffect: "skip", description: "Newsletters, marketing emails, and subscriptions", example: "This week in AI: The latest developments in agent frameworks..." }, + { tag: "notification", type: "email-type", applicability: "email", noteEffect: "skip", description: "Automated alerts, receipts, and system notifications", example: "Your password was changed successfully. If this wasn't you, contact support." }, + { tag: "spam", type: "filter", applicability: "email", noteEffect: "skip", description: "Junk and unwanted email", example: "Congratulations! You've won $1,000,000..." }, + { tag: "promotion", type: "filter", applicability: "email", noteEffect: "skip", description: "Marketing offers and sales pitches", example: "50% off all items this weekend only!" }, + { tag: "social", type: "filter", applicability: "email", noteEffect: "skip", description: "Social media notifications", example: "John Smith commented on your post." }, + { tag: "forums", type: "filter", applicability: "email", noteEffect: "skip", description: "Mailing lists and group discussions", example: "Re: [dev-list] Question about API design" }, + { tag: "action-required", type: "action", applicability: "both", noteEffect: "create", description: "Needs a response or action from you", example: "Can you send me the pricing by Friday?" }, + { tag: "fyi", type: "action", applicability: "email", noteEffect: "skip", description: "Informational only, no action needed", example: "Just wanted to let you know the deal closed. Thanks for your help!" }, + { tag: "urgent", type: "action", applicability: "both", noteEffect: "create", description: "Time-sensitive, needs immediate attention", example: "We need your signature on the contract by EOD today or we lose the deal." }, + { tag: "waiting", type: "action", applicability: "both", noteEffect: "create", description: "Waiting on a response from them" }, + { tag: "unread", type: "status", applicability: "email", noteEffect: "none", description: "Not yet processed" }, + { tag: "to-reply", type: "status", applicability: "email", noteEffect: "none", description: "Need to respond" }, + { tag: "done", type: "status", applicability: "email", noteEffect: "none", description: "Handled, can be archived" }, + { tag: "active", type: "status", applicability: "notes", noteEffect: "none", description: "Currently relevant, recent activity" }, + { tag: "archived", type: "status", applicability: "notes", noteEffect: "none", description: "No longer active, kept for reference" }, + { tag: "stale", type: "status", applicability: "notes", noteEffect: "none", description: "No activity in 60+ days, needs attention or archive" }, + { tag: "email", type: "source", applicability: "notes", noteEffect: "none", description: "Created or updated from email" }, + { tag: "meeting", type: "source", applicability: "notes", noteEffect: "none", description: "Created or updated from meeting transcript" }, + { tag: "browser", type: "source", applicability: "notes", noteEffect: "none", description: "Content captured from web browsing" }, + { tag: "web-search", type: "source", applicability: "notes", noteEffect: "none", description: "Information from web search" }, + { tag: "manual", type: "source", applicability: "notes", noteEffect: "none", description: "Manually entered by user" }, + { tag: "import", type: "source", applicability: "notes", noteEffect: "none", description: "Imported from another system" }, ] +function TagGroupTable({ + group, + tags, + collapsed, + onToggle, + onAdd, + onUpdate, + onRemove, + getGlobalIndex, + isEmail, +}: { + group: { type: string; label: string; tags: TagDef[] } + tags: TagDef[] + collapsed: boolean + onToggle: () => void + onAdd: () => void + onUpdate: (index: number, field: keyof TagDef, value: string | boolean) => void + onRemove: (index: number) => void + getGlobalIndex: (type: string, localIndex: number) => number + isEmail: boolean +}) { + return ( +
+
+ + +
+ {!collapsed && group.tags.length > 0 && ( +
+
+
Label
+
Description
+
Example
+ {isEmail &&
Skip notes
} +
+
+ {group.tags.map((tag, localIdx) => { + const globalIdx = getGlobalIndex(group.type, localIdx) + return ( +
+ onUpdate(globalIdx, "tag", e.target.value)} + className="h-7 text-xs" + placeholder="tag-name" + title={tag.tag} + /> + onUpdate(globalIdx, "description", e.target.value)} + className="h-7 text-xs" + placeholder="Description" + title={tag.description} + /> + onUpdate(globalIdx, "example", e.target.value)} + className="h-7 text-xs" + placeholder="Example" + title={tag.example || ""} + /> + {isEmail && ( +
+ onUpdate(globalIdx, "noteEffect", checked ? "skip" : "create")} + className="scale-75" + /> +
+ )} + +
+ ) + })} +
+ )} + {!collapsed && group.tags.length === 0 && ( +
No tags in this group
+ )} +
+ ) +} + function NoteTaggingSettings({ dialogOpen }: { dialogOpen: boolean }) { const [tags, setTags] = useState([]) const [originalTags, setOriginalTags] = useState([]) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [collapsedGroups, setCollapsedGroups] = useState>(new Set()) + const [activeSection, setActiveSection] = useState<"notes" | "email">("notes") const hasChanges = JSON.stringify(tags) !== JSON.stringify(originalTags) @@ -808,14 +923,30 @@ function NoteTaggingSettings({ dialogOpen }: { dialogOpen: boolean }) { load() }, [dialogOpen]) - const groups = useMemo(() => { + const noteGroups = useMemo(() => { const map = new Map() for (const tag of tags) { + if (tag.applicability === "email") continue const list = map.get(tag.type) ?? [] list.push(tag) map.set(tag.type, list) } - return TAG_TYPE_ORDER.map(type => ({ + return NOTE_TAG_TYPE_ORDER.filter(type => map.has(type)).map(type => ({ + type, + label: TAG_TYPE_LABELS[type], + tags: map.get(type) ?? [], + })) + }, [tags]) + + const emailGroups = useMemo(() => { + const map = new Map() + for (const tag of tags) { + if (tag.applicability === "notes") continue + const list = map.get(tag.type) ?? [] + list.push(tag) + map.set(tag.type, list) + } + return EMAIL_TAG_TYPE_ORDER.filter(type => map.has(type)).map(type => ({ type, label: TAG_TYPE_LABELS[type], tags: map.get(type) ?? [], @@ -833,7 +964,7 @@ function NoteTaggingSettings({ dialogOpen }: { dialogOpen: boolean }) { return -1 }, [tags]) - const updateTag = useCallback((index: number, field: keyof TagDef, value: string) => { + const updateTag = useCallback((index: number, field: keyof TagDef, value: string | boolean) => { setTags(prev => prev.map((t, i) => i === index ? { ...t, [field]: value } : t)) }, []) @@ -842,14 +973,30 @@ function NoteTaggingSettings({ dialogOpen }: { dialogOpen: boolean }) { }, []) const addTag = useCallback((type: string) => { - const newTag: TagDef = { tag: "", type, applicability: "both", description: "" } + const isEmailSection = activeSection === "email" + const applicability = isEmailSection ? "email" as const : "notes" as const + // For email-only types, always use "email"; for notes-only types, always use "notes"; otherwise use "both" + const emailOnlyTypes = ["email-type", "filter"] + const notesOnlyTypes = ["relationship-sub", "source"] + let finalApplicability: "email" | "notes" | "both" = "both" + if (emailOnlyTypes.includes(type)) finalApplicability = "email" + else if (notesOnlyTypes.includes(type)) finalApplicability = "notes" + else finalApplicability = isEmailSection ? "email" : applicability + + const newTag: TagDef = { + tag: "", + type, + applicability: finalApplicability === "email" && !isEmailSection ? "both" : finalApplicability === "notes" && isEmailSection ? "both" : finalApplicability, + description: "", + noteEffect: isEmailSection ? "create" : "none", + } const lastIndex = tags.reduce((acc, t, i) => t.type === type ? i : acc, -1) if (lastIndex === -1) { setTags(prev => [...prev, newTag]) } else { setTags(prev => [...prev.slice(0, lastIndex + 1), newTag, ...prev.slice(lastIndex + 1)]) } - }, [tags]) + }, [tags, activeSection]) const handleSave = useCallback(async () => { setSaving(true) @@ -890,92 +1037,50 @@ function NoteTaggingSettings({ dialogOpen }: { dialogOpen: boolean }) { ) } + const currentGroups = activeSection === "notes" ? noteGroups : emailGroups + return (
+
+ + +
- {groups.map(group => ( -
-
- - -
- {!collapsedGroups.has(group.type) && group.tags.length > 0 && ( -
-
-
Tag
-
Description
-
Example
-
Applies to
-
-
- {group.tags.map((tag, localIdx) => { - const globalIdx = getGlobalIndex(group.type, localIdx) - return ( -
- updateTag(globalIdx, "tag", e.target.value)} - className="h-7 text-xs" - placeholder="tag-name" - title={tag.tag} - /> - updateTag(globalIdx, "description", e.target.value)} - className="h-7 text-xs" - placeholder="Description" - title={tag.description} - /> - updateTag(globalIdx, "example", e.target.value)} - className="h-7 text-xs" - placeholder="Example" - title={tag.example || ""} - /> - - -
- ) - })} -
- )} - {!collapsedGroups.has(group.type) && group.tags.length === 0 && ( -
No tags in this group
- )} -
+ {currentGroups.map(group => ( + toggleGroup(group.type)} + onAdd={() => addTag(group.type)} + onUpdate={updateTag} + onRemove={removeTag} + getGlobalIndex={getGlobalIndex} + isEmail={activeSection === "email"} + /> ))}
diff --git a/apps/x/packages/core/src/knowledge/note_creation.ts b/apps/x/packages/core/src/knowledge/note_creation.ts index d26bd97f..2d7a6b58 100644 --- a/apps/x/packages/core/src/knowledge/note_creation.ts +++ b/apps/x/packages/core/src/knowledge/note_creation.ts @@ -1,4 +1,5 @@ import { renderNoteTypesBlock } from './note_system.js'; +import { renderNoteEffectRules } from './tag_system.js'; export function getRaw(): string { return `--- @@ -141,18 +142,7 @@ Either: **For emails, read the YAML frontmatter labels and apply these rules:** -**CREATE/UPDATE notes if the email has ANY of these labels:** -- **Relationship:** Investor, Customer, Prospect, Partner, Vendor, Candidate, Team, Advisor, Personal, Press, Community, Government -- **Topic:** Sales, Support, Legal, Finance, Hiring, Fundraising, Event, Research -- **Type:** Intro, Followup -- **Action:** Action Required, Urgent, Waiting - -**SKIP if the email ONLY has these labels (and none from above):** -- **Relationship:** Product -- **Topic:** Travel, Shopping, Health, Learning -- **Type:** Scheduling, Cold Outreach, Newsletter, Notification -- **Filter:** Spam, Promotion, Social, Forums -- **Action:** FYI +${renderNoteEffectRules()} --- @@ -247,22 +237,7 @@ labeled_at: "2026-02-28T12:00:00Z" ## Decision Rules -Check the labels against the create/skip lists: - -**CREATE/UPDATE notes if ANY label matches:** -- relationship: Investor, Customer, Prospect, Partner, Vendor, Candidate, Team, Advisor, Personal, Press, Community, Government -- topics: Sales, Support, Legal, Finance, Hiring, Fundraising, Event, Research -- type: Intro, Followup -- action: Action Required, Urgent, Waiting - -**SKIP if labels ONLY match:** -- relationship: Product -- topics: Travel, Shopping, Health, Learning -- type: Scheduling, Cold Outreach, Newsletter, Notification -- filter: Spam, Promotion, Social, Forums -- action: FYI - -**Logic:** If even one label falls in the "create" list, process the email. Only skip if ALL labels fall in the "skip" list. +${renderNoteEffectRules()} ## Filter Decision Output diff --git a/apps/x/packages/core/src/knowledge/tag_system.ts b/apps/x/packages/core/src/knowledge/tag_system.ts index 01ac4c0e..b8642338 100644 --- a/apps/x/packages/core/src/knowledge/tag_system.ts +++ b/apps/x/packages/core/src/knowledge/tag_system.ts @@ -14,93 +14,97 @@ export type TagType = | 'status' | 'source'; +export type NoteEffect = 'create' | 'skip' | 'none'; + export interface TagDefinition { tag: string; type: TagType; applicability: TagApplicability; description: string; example?: string; + /** Whether an email with this tag should create notes ('create'), be skipped ('skip'), or has no effect on note creation ('none'). */ + noteEffect?: NoteEffect; } // ── Default definitions (used to seed ~/.rowboat/config/tags.json) ────────── const DEFAULT_TAG_DEFINITIONS: TagDefinition[] = [ // ── Relationship (both) ────────────────────────────────────────────── - { tag: 'investor', type: 'relationship', applicability: 'both', description: 'Investors, VCs, or angels', example: 'Following up on our meeting — we\'d like to move forward with the Series A term sheet.' }, - { tag: 'customer', type: 'relationship', applicability: 'both', description: 'Paying customers', example: 'We\'re seeing great results with Rowboat. Can we discuss expanding to more teams?' }, - { tag: 'prospect', type: 'relationship', applicability: 'both', description: 'Potential customers', example: 'Thanks for the demo yesterday. We\'re interested in starting a pilot.' }, - { tag: 'partner', type: 'relationship', applicability: 'both', description: 'Business partners', example: 'Let\'s discuss how we can promote the integration to both our user bases.' }, - { tag: 'vendor', type: 'relationship', applicability: 'both', description: 'Service providers you work with', example: 'Here are the updated employment agreements you requested.' }, - { tag: 'product', type: 'relationship', applicability: 'both', description: 'Products or services you use (automated)', example: 'Your AWS bill for January 2025 is now available.' }, - { tag: 'candidate', type: 'relationship', applicability: 'both', description: 'Job applicants', example: 'Thanks for reaching out. I\'d love to learn more about the engineering role.' }, - { tag: 'team', type: 'relationship', applicability: 'both', description: 'Internal team members', example: 'Here\'s the updated roadmap for Q2. Let\'s discuss in our sync.' }, - { tag: 'advisor', type: 'relationship', applicability: 'both', description: 'Advisors, mentors, or board members', example: 'I\'ve reviewed the deck. Here are my thoughts on the GTM strategy.' }, - { tag: 'personal', type: 'relationship', applicability: 'both', description: 'Family or friends', example: 'Are you coming to Thanksgiving this year? Let me know your travel dates.' }, - { tag: 'press', type: 'relationship', applicability: 'both', description: 'Journalists or media', example: 'I\'m writing a piece on AI agents. Would you be available for an interview?' }, - { tag: 'community', type: 'relationship', applicability: 'both', description: 'Users, peers, or open source contributors', example: 'Love what you\'re building with Rowboat. Here\'s a bug I found...' }, - { tag: 'government', type: 'relationship', applicability: 'both', description: 'Government agencies', example: 'Your Delaware franchise tax is due by March 1, 2025.' }, + { tag: 'investor', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Investors, VCs, or angels', example: 'Following up on our meeting — we\'d like to move forward with the Series A term sheet.' }, + { tag: 'customer', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Paying customers', example: 'We\'re seeing great results with Rowboat. Can we discuss expanding to more teams?' }, + { tag: 'prospect', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Potential customers', example: 'Thanks for the demo yesterday. We\'re interested in starting a pilot.' }, + { tag: 'partner', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Business partners', example: 'Let\'s discuss how we can promote the integration to both our user bases.' }, + { tag: 'vendor', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Service providers you work with', example: 'Here are the updated employment agreements you requested.' }, + { tag: 'product', type: 'relationship', applicability: 'both', noteEffect: 'skip', description: 'Products or services you use (automated)', example: 'Your AWS bill for January 2025 is now available.' }, + { tag: 'candidate', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Job applicants', example: 'Thanks for reaching out. I\'d love to learn more about the engineering role.' }, + { tag: 'team', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Internal team members', example: 'Here\'s the updated roadmap for Q2. Let\'s discuss in our sync.' }, + { tag: 'advisor', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Advisors, mentors, or board members', example: 'I\'ve reviewed the deck. Here are my thoughts on the GTM strategy.' }, + { tag: 'personal', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Family or friends', example: 'Are you coming to Thanksgiving this year? Let me know your travel dates.' }, + { tag: 'press', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Journalists or media', example: 'I\'m writing a piece on AI agents. Would you be available for an interview?' }, + { tag: 'community', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Users, peers, or open source contributors', example: 'Love what you\'re building with Rowboat. Here\'s a bug I found...' }, + { tag: 'government', type: 'relationship', applicability: 'both', noteEffect: 'create', description: 'Government agencies', example: 'Your Delaware franchise tax is due by March 1, 2025.' }, // ── Relationship Sub-Tags (notes only) ─────────────────────────────── - { tag: 'primary', type: 'relationship-sub', applicability: 'notes', description: 'Main contact or decision maker', example: 'Sarah Chen — VP Engineering, your main point of contact at Acme.' }, - { tag: 'secondary', type: 'relationship-sub', applicability: 'notes', description: 'Supporting contact, involved but not the lead', example: 'David Kim — Engineer CC\'d on customer emails.' }, - { tag: 'executive-assistant', type: 'relationship-sub', applicability: 'notes', description: 'EA or admin handling scheduling and logistics', example: 'Lisa — Sarah\'s EA who schedules all her meetings.' }, - { tag: 'cc', type: 'relationship-sub', applicability: 'notes', description: 'Person who\'s CC\'d but not actively engaged', example: 'Manager looped in for visibility on deal.' }, - { tag: 'referred-by', type: 'relationship-sub', applicability: 'notes', description: 'Person who made an introduction or referral', example: 'David Park — Investor who intro\'d you to Sarah.' }, - { tag: 'former', type: 'relationship-sub', applicability: 'notes', description: 'Previously held this relationship, no longer active', example: 'John — Former customer who churned last year.' }, - { tag: 'champion', type: 'relationship-sub', applicability: 'notes', description: 'Internal advocate pushing for you', example: 'Engineer who loves your product and is selling internally.' }, - { tag: 'blocker', type: 'relationship-sub', applicability: 'notes', description: 'Person opposing or blocking progress', example: 'CFO resistant to spending on new tools.' }, + { tag: 'primary', type: 'relationship-sub', applicability: 'notes', noteEffect: 'none', description: 'Main contact or decision maker', example: 'Sarah Chen — VP Engineering, your main point of contact at Acme.' }, + { tag: 'secondary', type: 'relationship-sub', applicability: 'notes', noteEffect: 'none', description: 'Supporting contact, involved but not the lead', example: 'David Kim — Engineer CC\'d on customer emails.' }, + { tag: 'executive-assistant', type: 'relationship-sub', applicability: 'notes', noteEffect: 'none', description: 'EA or admin handling scheduling and logistics', example: 'Lisa — Sarah\'s EA who schedules all her meetings.' }, + { tag: 'cc', type: 'relationship-sub', applicability: 'notes', noteEffect: 'none', description: 'Person who\'s CC\'d but not actively engaged', example: 'Manager looped in for visibility on deal.' }, + { tag: 'referred-by', type: 'relationship-sub', applicability: 'notes', noteEffect: 'none', description: 'Person who made an introduction or referral', example: 'David Park — Investor who intro\'d you to Sarah.' }, + { tag: 'former', type: 'relationship-sub', applicability: 'notes', noteEffect: 'none', description: 'Previously held this relationship, no longer active', example: 'John — Former customer who churned last year.' }, + { tag: 'champion', type: 'relationship-sub', applicability: 'notes', noteEffect: 'none', description: 'Internal advocate pushing for you', example: 'Engineer who loves your product and is selling internally.' }, + { tag: 'blocker', type: 'relationship-sub', applicability: 'notes', noteEffect: 'none', description: 'Person opposing or blocking progress', example: 'CFO resistant to spending on new tools.' }, // ── Topic (both) ───────────────────────────────────────────────────── - { tag: 'sales', type: 'topic', applicability: 'both', description: 'Sales conversations, deals, and revenue', example: 'Here\'s the pricing proposal we discussed. Let me know if you have questions.' }, - { tag: 'support', type: 'topic', applicability: 'both', description: 'Help requests, issues, and customer support', example: 'We\'re seeing an error when trying to export. Can you help?' }, - { tag: 'legal', type: 'topic', applicability: 'both', description: 'Contracts, terms, compliance, and legal matters', example: 'Legal has reviewed the MSA. Attached are our requested changes.' }, - { tag: 'finance', type: 'topic', applicability: 'both', description: 'Money, invoices, payments, banking, and taxes', example: 'Your invoice #1234 for $5,000 is attached. Payment due in 30 days.' }, - { tag: 'hiring', type: 'topic', applicability: 'both', description: 'Recruiting, interviews, and employment', example: 'We\'d like to move forward with a final round interview. Are you available Thursday?' }, - { tag: 'fundraising', type: 'topic', applicability: 'both', description: 'Raising money and investor relations', example: 'Thanks for sending the deck. We\'d like to schedule a partner meeting.' }, - { tag: 'travel', type: 'topic', applicability: 'both', description: 'Flights, hotels, trips, and travel logistics', example: 'Your flight to Tokyo on March 15 is confirmed. Confirmation #ABC123.' }, - { tag: 'event', type: 'topic', applicability: 'both', description: 'Conferences, meetups, and gatherings', example: 'You\'re invited to speak at TechCrunch Disrupt. Can you confirm your availability?' }, - { tag: 'shopping', type: 'topic', applicability: 'both', description: 'Purchases, orders, and returns', example: 'Your order #12345 has shipped. Track it here.' }, - { tag: 'health', type: 'topic', applicability: 'both', description: 'Medical, wellness, and health-related matters', example: 'Your appointment with Dr. Smith is confirmed for Monday at 2pm.' }, - { tag: 'learning', type: 'topic', applicability: 'both', description: 'Courses, education, and skill-building', example: 'Welcome to the Advanced Python course. Here\'s your access link.' }, - { tag: 'research', type: 'topic', applicability: 'both', description: 'Research requests and information gathering', example: 'Here\'s the market analysis you requested on the AI agent space.' }, + { tag: 'sales', type: 'topic', applicability: 'both', noteEffect: 'create', description: 'Sales conversations, deals, and revenue', example: 'Here\'s the pricing proposal we discussed. Let me know if you have questions.' }, + { tag: 'support', type: 'topic', applicability: 'both', noteEffect: 'create', description: 'Help requests, issues, and customer support', example: 'We\'re seeing an error when trying to export. Can you help?' }, + { tag: 'legal', type: 'topic', applicability: 'both', noteEffect: 'create', description: 'Contracts, terms, compliance, and legal matters', example: 'Legal has reviewed the MSA. Attached are our requested changes.' }, + { tag: 'finance', type: 'topic', applicability: 'both', noteEffect: 'create', description: 'Money, invoices, payments, banking, and taxes', example: 'Your invoice #1234 for $5,000 is attached. Payment due in 30 days.' }, + { tag: 'hiring', type: 'topic', applicability: 'both', noteEffect: 'create', description: 'Recruiting, interviews, and employment', example: 'We\'d like to move forward with a final round interview. Are you available Thursday?' }, + { tag: 'fundraising', type: 'topic', applicability: 'both', noteEffect: 'create', description: 'Raising money and investor relations', example: 'Thanks for sending the deck. We\'d like to schedule a partner meeting.' }, + { tag: 'travel', type: 'topic', applicability: 'both', noteEffect: 'skip', description: 'Flights, hotels, trips, and travel logistics', example: 'Your flight to Tokyo on March 15 is confirmed. Confirmation #ABC123.' }, + { tag: 'event', type: 'topic', applicability: 'both', noteEffect: 'create', description: 'Conferences, meetups, and gatherings', example: 'You\'re invited to speak at TechCrunch Disrupt. Can you confirm your availability?' }, + { tag: 'shopping', type: 'topic', applicability: 'both', noteEffect: 'skip', description: 'Purchases, orders, and returns', example: 'Your order #12345 has shipped. Track it here.' }, + { tag: 'health', type: 'topic', applicability: 'both', noteEffect: 'skip', description: 'Medical, wellness, and health-related matters', example: 'Your appointment with Dr. Smith is confirmed for Monday at 2pm.' }, + { tag: 'learning', type: 'topic', applicability: 'both', noteEffect: 'skip', description: 'Courses, education, and skill-building', example: 'Welcome to the Advanced Python course. Here\'s your access link.' }, + { tag: 'research', type: 'topic', applicability: 'both', noteEffect: 'create', description: 'Research requests and information gathering', example: 'Here\'s the market analysis you requested on the AI agent space.' }, // ── Email Type ─────────────────────────────────────────────────────── - { tag: 'intro', type: 'email-type', applicability: 'both', description: 'Warm introduction from someone you know', example: 'I\'d like to introduce you to Sarah Chen, VP Engineering at Acme.' }, - { tag: 'followup', type: 'email-type', applicability: 'both', description: 'Following up on a previous conversation', example: 'Following up on our call last week. Have you had a chance to review the proposal?' }, - { tag: 'scheduling', type: 'email-type', applicability: 'email', description: 'Meeting and calendar scheduling', example: 'Are you available for a call next Tuesday at 2pm?' }, - { tag: 'cold-outreach', type: 'email-type', applicability: 'email', description: 'Unsolicited contact from someone you don\'t know', example: 'Hi, I noticed your company is growing fast. I\'d love to show you how we can help with...' }, - { tag: 'newsletter', type: 'email-type', applicability: 'email', description: 'Newsletters, marketing emails, and subscriptions', example: 'This week in AI: The latest developments in agent frameworks...' }, - { tag: 'notification', type: 'email-type', applicability: 'email', description: 'Automated alerts, receipts, and system notifications', example: 'Your password was changed successfully. If this wasn\'t you, contact support.' }, + { tag: 'intro', type: 'email-type', applicability: 'both', noteEffect: 'create', description: 'Warm introduction from someone you know', example: 'I\'d like to introduce you to Sarah Chen, VP Engineering at Acme.' }, + { tag: 'followup', type: 'email-type', applicability: 'both', noteEffect: 'create', description: 'Following up on a previous conversation', example: 'Following up on our call last week. Have you had a chance to review the proposal?' }, + { tag: 'scheduling', type: 'email-type', applicability: 'email', noteEffect: 'skip', description: 'Meeting and calendar scheduling', example: 'Are you available for a call next Tuesday at 2pm?' }, + { tag: 'cold-outreach', type: 'email-type', applicability: 'email', noteEffect: 'skip', description: 'Unsolicited contact from someone you don\'t know', example: 'Hi, I noticed your company is growing fast. I\'d love to show you how we can help with...' }, + { tag: 'newsletter', type: 'email-type', applicability: 'email', noteEffect: 'skip', description: 'Newsletters, marketing emails, and subscriptions', example: 'This week in AI: The latest developments in agent frameworks...' }, + { tag: 'notification', type: 'email-type', applicability: 'email', noteEffect: 'skip', description: 'Automated alerts, receipts, and system notifications', example: 'Your password was changed successfully. If this wasn\'t you, contact support.' }, // ── Filter (email only) ────────────────────────────────────────────── - { tag: 'spam', type: 'filter', applicability: 'email', description: 'Junk and unwanted email', example: 'Congratulations! You\'ve won $1,000,000...' }, - { tag: 'promotion', type: 'filter', applicability: 'email', description: 'Marketing offers and sales pitches', example: '50% off all items this weekend only!' }, - { tag: 'social', type: 'filter', applicability: 'email', description: 'Social media notifications', example: 'John Smith commented on your post.' }, - { tag: 'forums', type: 'filter', applicability: 'email', description: 'Mailing lists and group discussions', example: 'Re: [dev-list] Question about API design' }, + { tag: 'spam', type: 'filter', applicability: 'email', noteEffect: 'skip', description: 'Junk and unwanted email', example: 'Congratulations! You\'ve won $1,000,000...' }, + { tag: 'promotion', type: 'filter', applicability: 'email', noteEffect: 'skip', description: 'Marketing offers and sales pitches', example: '50% off all items this weekend only!' }, + { tag: 'social', type: 'filter', applicability: 'email', noteEffect: 'skip', description: 'Social media notifications', example: 'John Smith commented on your post.' }, + { tag: 'forums', type: 'filter', applicability: 'email', noteEffect: 'skip', description: 'Mailing lists and group discussions', example: 'Re: [dev-list] Question about API design' }, // ── Action ─────────────────────────────────────────────────────────── - { tag: 'action-required', type: 'action', applicability: 'both', description: 'Needs a response or action from you', example: 'Can you send me the pricing by Friday?' }, - { tag: 'fyi', type: 'action', applicability: 'email', description: 'Informational only, no action needed', example: 'Just wanted to let you know the deal closed. Thanks for your help!' }, - { tag: 'urgent', type: 'action', applicability: 'both', description: 'Time-sensitive, needs immediate attention', example: 'We need your signature on the contract by EOD today or we lose the deal.' }, - { tag: 'waiting', type: 'action', applicability: 'both', description: 'Waiting on a response from them' }, + { tag: 'action-required', type: 'action', applicability: 'both', noteEffect: 'create', description: 'Needs a response or action from you', example: 'Can you send me the pricing by Friday?' }, + { tag: 'fyi', type: 'action', applicability: 'email', noteEffect: 'skip', description: 'Informational only, no action needed', example: 'Just wanted to let you know the deal closed. Thanks for your help!' }, + { tag: 'urgent', type: 'action', applicability: 'both', noteEffect: 'create', description: 'Time-sensitive, needs immediate attention', example: 'We need your signature on the contract by EOD today or we lose the deal.' }, + { tag: 'waiting', type: 'action', applicability: 'both', noteEffect: 'create', description: 'Waiting on a response from them' }, // ── Status (email) ─────────────────────────────────────────────────── - { tag: 'unread', type: 'status', applicability: 'email', description: 'Not yet processed' }, - { tag: 'to-reply', type: 'status', applicability: 'email', description: 'Need to respond' }, - { tag: 'done', type: 'status', applicability: 'email', description: 'Handled, can be archived' }, + { tag: 'unread', type: 'status', applicability: 'email', noteEffect: 'none', description: 'Not yet processed' }, + { tag: 'to-reply', type: 'status', applicability: 'email', noteEffect: 'none', description: 'Need to respond' }, + { tag: 'done', type: 'status', applicability: 'email', noteEffect: 'none', description: 'Handled, can be archived' }, // ── Source (notes only) ────────────────────────────────────────────── - { tag: 'email', type: 'source', applicability: 'notes', description: 'Created or updated from email' }, - { tag: 'meeting', type: 'source', applicability: 'notes', description: 'Created or updated from meeting transcript' }, - { tag: 'browser', type: 'source', applicability: 'notes', description: 'Content captured from web browsing' }, - { tag: 'web-search', type: 'source', applicability: 'notes', description: 'Information from web search' }, - { tag: 'manual', type: 'source', applicability: 'notes', description: 'Manually entered by user' }, - { tag: 'import', type: 'source', applicability: 'notes', description: 'Imported from another system' }, + { tag: 'email', type: 'source', applicability: 'notes', noteEffect: 'none', description: 'Created or updated from email' }, + { tag: 'meeting', type: 'source', applicability: 'notes', noteEffect: 'none', description: 'Created or updated from meeting transcript' }, + { tag: 'browser', type: 'source', applicability: 'notes', noteEffect: 'none', description: 'Content captured from web browsing' }, + { tag: 'web-search', type: 'source', applicability: 'notes', noteEffect: 'none', description: 'Information from web search' }, + { tag: 'manual', type: 'source', applicability: 'notes', noteEffect: 'none', description: 'Manually entered by user' }, + { tag: 'import', type: 'source', applicability: 'notes', noteEffect: 'none', description: 'Imported from another system' }, // ── Status (notes) ────────────────────────────────────────────────── - { tag: 'active', type: 'status', applicability: 'notes', description: 'Currently relevant, recent activity' }, - { tag: 'archived', type: 'status', applicability: 'notes', description: 'No longer active, kept for reference' }, - { tag: 'stale', type: 'status', applicability: 'notes', description: 'No activity in 60+ days, needs attention or archive' }, + { tag: 'active', type: 'status', applicability: 'notes', noteEffect: 'none', description: 'Currently relevant, recent activity' }, + { tag: 'archived', type: 'status', applicability: 'notes', noteEffect: 'none', description: 'No longer active, kept for reference' }, + { tag: 'stale', type: 'status', applicability: 'notes', noteEffect: 'none', description: 'No activity in 60+ days, needs attention or archive' }, ]; // ── Disk-backed config with mtime caching ────────────────────────────────── @@ -186,6 +190,35 @@ function renderTagGroups(tags: TagDefinition[]): string { return `# Tag System Reference\n\n${sections.join('\n\n')}`; } +export function renderNoteEffectRules(): string { + const tags = getTagDefinitions(); + const skipByType = new Map(); + const createByType = new Map(); + + for (const t of tags) { + const effect = t.noteEffect ?? 'none'; + if (effect === 'none') continue; + const label = TYPE_LABELS[t.type] ?? t.type; + const map = effect === 'skip' ? skipByType : createByType; + const list = map.get(label) ?? []; + list.push(t.tag.split('-').map(w => w[0].toUpperCase() + w.slice(1)).join(' ')); + map.set(label, list); + } + + const formatList = (map: Map) => + Array.from(map.entries()).map(([type, tags]) => `- **${type}:** ${tags.join(', ')}`).join('\n'); + + return [ + `**SKIP if the email has ANY of these labels (skip labels override everything):**`, + formatList(skipByType), + ``, + `**CREATE/UPDATE notes if the email has ANY of these labels (and no skip labels present):**`, + formatList(createByType), + ``, + `**Logic:** If even one label falls in the "skip" list, skip the email — skip labels are hard filters that override create labels.`, + ].join('\n'); +} + export function renderTagSystemForNotes(): string { const tags = getTagDefinitions().filter(t => t.applicability !== 'email'); return renderTagGroups(tags);