TUI for Todoist written with React+Ink.
0

Configure Feed

Select the types of activity you want to include in your feed.

add in-line refreshing

Aly Raffauf (Feb 15, 2026, 12:50 AM EST) 8e629d37 02449249

+21 -27
+11 -12
source/app.tsx
··· 131 131 const handleSubmit = async (value: string) => { 132 132 const trimmed = value.trim(); 133 133 setInput(''); 134 + setMessage(''); 134 135 135 136 if (!trimmed || trimmed === '?') { 136 137 return; ··· 142 143 projects, 143 144 homeFilter, 144 145 setMessage, 146 + setTasks, 145 147 refresh, 146 148 setView, 147 149 exit, ··· 157 159 } 158 160 }; 159 161 160 - if (loading) { 161 - return ( 162 - <Text> 163 - <Text color="cyan"> 164 - <Spinner type="dots" /> 165 - </Text>{' '} 166 - Loading 167 - </Text> 168 - ); 169 - } 162 + const refreshIndicator = loading && ( 163 + <Text color="cyan"> 164 + {' '} 165 + <Spinner type="dots" /> 166 + </Text> 167 + ); 170 168 171 169 const viewLabel = 172 170 view.type === 'edit' 173 171 ? `dewy ∙ edit: ${view.task.content}` 174 172 : `dewy ∙ ${view.query === homeFilter ? 'home' : view.query} ∙ ${ 175 173 tasks.length 176 - } ${tasks.length === 1 ? 'item' : 'items'}`; 174 + } ${tasks.length === 1 ? 'item' : 'items'}`; 177 175 178 176 const isEditing = view.type === 'edit'; 179 177 ··· 182 180 <Box flexDirection="column" paddingLeft={2}> 183 181 <Text bold color="cyan"> 184 182 {viewLabel} 183 + {refreshIndicator} 185 184 </Text> 186 185 {isEditing ? ( 187 186 <> ··· 219 218 > 220 219 <TaskListView tasks={tasks} projects={projects} /> 221 220 </Box> 222 - {message && <Text color="yellow">{message}</Text>} 221 + <Text color="yellow">{message || ' '}</Text> 223 222 </> 224 223 )} 225 224 </Box>
+10 -15
source/commands.ts
··· 8 8 projects: Map<string, string>; 9 9 homeFilter: string; 10 10 setMessage: (msg: string) => void; 11 + setTasks: (tasks: Task[]) => void; 11 12 refresh: () => Promise<void>; 12 13 setView: (view: View) => void; 13 14 exit: () => void; ··· 22 23 { 23 24 prefix: 'done ', 24 25 hint: 'done <number>', 25 - run: async (args, {api, tasks, setMessage, refresh}) => { 26 + run: async (args, {api, tasks, setTasks, setMessage}) => { 26 27 const num = Number.parseInt(args, 10); 27 28 const task = tasks[num - 1]; 28 29 if (task) { 29 30 await api.closeTask(task.id); 30 - setMessage(`✓ ${task.content}`); 31 - await refresh(); 31 + setTasks(tasks.filter(t => t.id !== task.id)); 32 32 } else { 33 33 setMessage(`No task #${num}`); 34 34 } ··· 52 52 { 53 53 prefix: 'add ', 54 54 hint: 'add <task>', 55 - run: async (args, {api, setMessage, refresh}) => { 56 - await api.quickAddTask({text: args}); 57 - setMessage(`+ ${args}`); 58 - await refresh(); 55 + run: async (args, {api, tasks, setTasks}) => { 56 + const task = await api.quickAddTask({text: args}); 57 + setTasks([...tasks, task]); 59 58 }, 60 59 }, 61 60 { 62 61 prefix: 'filter ', 63 62 hint: 'filter <query>', 64 - run: async (args, {setMessage, setView}) => { 63 + run: async (args, {setView}) => { 65 64 setView({type: 'filter', query: args}); 66 - setMessage('⊳ filtered'); 67 65 }, 68 66 }, 69 67 { 70 68 prefix: 'refresh', 71 69 hint: 'refresh', 72 - run: async (_args, {refresh, setMessage}) => { 70 + run: async (_args, {refresh}) => { 73 71 await refresh(); 74 - setMessage('↻ refreshed'); 75 72 }, 76 73 }, 77 74 78 75 { 79 76 prefix: 'today', 80 77 hint: 'today', 81 - run: async (_args, {setView, setMessage}) => { 78 + run: async (_args, {setView}) => { 82 79 setView({type: 'filter', query: 'today'}); 83 - setMessage('→ today'); 84 80 }, 85 81 }, 86 82 87 83 { 88 84 prefix: 'home', 89 85 hint: 'home', 90 - run: async (_args, {setView, setMessage, homeFilter}) => { 86 + run: async (_args, {setView, homeFilter}) => { 91 87 setView({type: 'filter', query: homeFilter}); 92 - setMessage('→ home'); 93 88 }, 94 89 }, 95 90