feat: make track block icons configurable via yaml

This commit is contained in:
Gagancreates 2026-05-04 22:12:05 +05:30
parent ce128629a4
commit d857cf318d
3 changed files with 25 additions and 9 deletions

View file

@ -1,18 +1,28 @@
import { z } from 'zod'
import { useMemo } from 'react'
import { useMemo, type ComponentType } from 'react'
import { mergeAttributes, Node } from '@tiptap/react'
import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react'
import { Radio, Loader2, CalendarDays, Mail, Clock, ListTodo, History } from 'lucide-react'
import { Radio, Loader2, type LucideProps } from 'lucide-react'
import * as LucideIcons from 'lucide-react'
import { parse as parseYaml } from 'yaml'
import { TrackBlockSchema } from '@x/shared/dist/track-block.js'
import { useTrackStatus } from '@/hooks/use-track-status'
function TrackIcon({ trackId, size }: { trackId: string; size: number }) {
if (trackId === 'up-next') return <Clock size={size} />
if (trackId === 'calendar') return <CalendarDays size={size} />
if (trackId === 'emails') return <Mail size={size} />
if (trackId === 'what-you-missed') return <History size={size} />
if (trackId === 'priorities') return <ListTodo size={size} />
function resolveIcon(iconName: string): ComponentType<LucideProps> | null {
const key = iconName
.split('-')
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
.join('')
const component = (LucideIcons as Record<string, unknown>)[key]
if (component != null) return component as ComponentType<LucideProps>
return null
}
function TrackIcon({ icon, size }: { icon?: string; size: number }) {
if (icon) {
const Icon = resolveIcon(icon)
if (Icon) return <Icon size={size} />
}
return <Radio size={size} />
}
@ -109,7 +119,7 @@ function TrackBlockView({ node, deleteNode, extension }: {
<span className="track-block-chip-icon">
{isRunning
? <Loader2 size={24} className="animate-spin" />
: <TrackIcon trackId={trackId} size={24} />}
: <TrackIcon icon={track?.icon} size={24} />}
</span>
<span className="track-block-chip-id">{trackId || 'track'}</span>
{instruction && <span className="track-block-chip-sep">·</span>}

View file

@ -18,6 +18,7 @@ const SECTIONS: Section[] = [
heading: '## Up Next',
track: {
trackId: 'up-next',
icon: 'clock',
instruction:
`Write 1-3 sentences of plain markdown giving the user a shoulder-tap about what's next on their calendar today.
@ -44,6 +45,7 @@ Plain markdown prose only — no calendar block, no email block, no headings.`,
heading: '## Calendar',
track: {
trackId: 'calendar',
icon: 'calendar-days',
instruction:
`Emit today's meetings as a calendar block titled "Today's Meetings".
@ -63,6 +65,7 @@ After the block, you MAY add one short markdown line per event giving useful pre
heading: '## Emails',
track: {
trackId: 'emails',
icon: 'mail',
instruction:
`Maintain a digest of email threads worth the user's attention today, rendered as zero or more email blocks (one per thread).
@ -84,6 +87,7 @@ Do NOT re-list threads the user has already seen unless their state changed (new
heading: '## What You Missed',
track: {
trackId: 'what-you-missed',
icon: 'history',
instruction:
`Short markdown summary of what happened yesterday that matters this morning.
@ -109,6 +113,7 @@ Do NOT manufacture content to fill the section.`,
heading: '## Today\'s Priorities',
track: {
trackId: 'priorities',
icon: 'list-todo',
instruction:
`Ranked markdown list of the real, actionable items the user should focus on today.

View file

@ -27,6 +27,7 @@ export const TrackBlockSchema = z.object({
schedule: TrackScheduleSchema.optional(),
model: z.string().optional().describe('ADVANCED — leave unset. Per-track LLM model override (e.g. "anthropic/claude-sonnet-4.6"). Only set when the user explicitly asked for a specific model for THIS track. The global default already picks a tuned model for tracks; overriding usually makes things worse, not better.'),
provider: z.string().optional().describe('ADVANCED — leave unset. Per-track provider name override (e.g. "openai", "anthropic"). Only set when the user explicitly asked for a specific provider for THIS track. Almost always omitted; the global default flows through correctly.'),
icon: z.string().optional().describe('Lucide icon name for the chip (e.g. "clock", "calendar-days", "mail", "history", "list-todo"). Omit to use the default icon for this track.'),
lastRunAt: z.string().optional().describe('Runtime-managed — never write this yourself'),
lastRunId: z.string().optional().describe('Runtime-managed — never write this yourself'),
lastRunSummary: z.string().optional().describe('Runtime-managed — never write this yourself'),