[aidan] ux/workflows: animate sidebar title on auto-rename

Wrap the calendar hub's sidebar row title in the same Typewriter the
workflow card uses, so a title that auto-renames retypes letter-by-letter
in the sidebar too. Extract the placeholder/isRealTitle guard into the
shared workflowVisuals so the card and sidebar stay in sync.
This commit is contained in:
abccodes
2026-06-17 20:04:00 -07:00
parent 13b2722efc
commit ec352cbbce
3 changed files with 21 additions and 10 deletions
@@ -43,7 +43,7 @@ import InlineEditableTitle from '@/app/components/InlineEditableTitle';
import { Typewriter } from '@/app/components/feedback/Animated';
import StopRounded from '@mui/icons-material/StopRounded';
import PauseRounded from '@mui/icons-material/PauseRounded';
import { StatusDot, RunSparkline, LastFiredHint, isStaleSinceLastRun } from './workflowVisuals';
import { StatusDot, RunSparkline, LastFiredHint, isStaleSinceLastRun, isRealTitle } from './workflowVisuals';
import { store } from '@/shared/state/store';
import { getAgentWorkTime, fmtSeconds } from '@/shared/agentWorkTime';
@@ -75,14 +75,6 @@ const HANDLE_DEFS: { dir: ResizeDir; sx: Record<string, any> }[] = [
{ dir: 'se', sx: { bottom: -EDGE_THICKNESS / 2, right: -EDGE_THICKNESS / 2, width: CORNER_SIZE, height: CORNER_SIZE } },
];
// Placeholder titles the backend uses before auto-naming kicks in. The title
// Typewriter only animates once the title is a real (generated/user) name, so
// the card doesn't animate on mount or while still showing a placeholder.
const PLACEHOLDER_TITLES = new Set(['', 'New workflow', 'Untitled workflow', 'Scheduled workflow']);
function isRealTitle(title?: string | null): boolean {
return !!title && !PLACEHOLDER_TITLES.has(title.trim());
}
interface Props {
workflowId: string;
cardX: number;
@@ -29,6 +29,8 @@ import { useEffect } from 'react';
import ScheduleCalendar from './ScheduleCalendar';
import AddToSchedulePopover from './AddToSchedulePopover';
import { WEEKDAY_LABEL, addDays, sameDay, startOfMonthGrid, isWorkflowSchedulable } from './scheduleUtils';
import { isRealTitle } from './workflowVisuals';
import { Typewriter } from '@/app/components/feedback/Animated';
type ResizeDir = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
@@ -680,7 +682,14 @@ function SidebarSection({ title, items, onPick, scheduled, onContext, onSchedule
</Box>
</Tooltip>
)}
<Typography sx={{ flex: 1, fontSize: '0.82rem', color: c.text.primary, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', opacity: scheduled && !w.schedule.enabled ? 0.7 : 1 }}>{w.title}</Typography>
{/* Retype the title letter-by-letter when it auto-renames, matching
the workflow card. Gated on a real (non-placeholder) title so it
never animates on first appearance or for already-named rows. */}
<Typewriter value={w.title} enabled={isRealTitle(w.title)}>
{(t) => (
<Typography sx={{ flex: 1, fontSize: '0.82rem', color: c.text.primary, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', opacity: scheduled && !w.schedule.enabled ? 0.7 : 1 }}>{t}</Typography>
)}
</Typewriter>
{scheduled && !w.schedule.enabled && (
<Box sx={{ flexShrink: 0, px: 0.6, py: 0.1, borderRadius: '3px', bgcolor: c.bg.elevated, color: c.text.muted, fontSize: '0.62rem', fontWeight: 600, lineHeight: 1.5, letterSpacing: '0.02em' }}>Paused</Box>
)}
@@ -31,6 +31,16 @@ import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import type { Workflow, WorkflowRun, ScheduleConfig, PermissionTier } from '@/shared/state/workflowsSlice';
import { formatTime, WEEKDAY_LABEL, isScheduleConfigured } from './scheduleUtils';
// ---------- Title placeholders ----------
// Placeholder titles the backend uses before auto-naming kicks in. The title
// Typewriter only animates once the title is a real (generated/user) name, so
// the UI doesn't animate on mount or while still showing a placeholder.
const PLACEHOLDER_TITLES = new Set(['', 'New workflow', 'Untitled workflow', 'Scheduled workflow']);
export function isRealTitle(title?: string | null): boolean {
return !!title && !PLACEHOLDER_TITLES.has(title.trim());
}
// ---------- Status colors ----------
export type LastRunStatus = NonNullable<Workflow['last_run_status']>;