diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 3ac0b24f..d1a6f1fe 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -282,6 +282,7 @@ interface ChatInputInnerProps { isProcessing: boolean presetMessage?: string onPresetMessageConsumed?: () => void + runId?: string | null } function ChatInputInner({ @@ -289,6 +290,7 @@ function ChatInputInner({ isProcessing, presetMessage, onPresetMessageConsumed, + runId, }: ChatInputInnerProps) { const controller = usePromptInputController() const message = controller.textInput.value @@ -320,8 +322,9 @@ function ChatInputInner({
@@ -1614,6 +1677,10 @@ function App() { placeholder="Start writing..." wikiLinks={wikiLinkConfig} onImageUpload={handleImageUpload} + onNavigateBack={navigateBack} + onNavigateForward={navigateForward} + canNavigateBack={canNavigateBack} + canNavigateForward={canNavigateForward} /> ) : ( @@ -1715,6 +1782,7 @@ function App() { isProcessing={isProcessing} presetMessage={presetMessage} onPresetMessageConsumed={() => setPresetMessage(undefined)} + runId={runId} /> diff --git a/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx b/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx index 37a4f4be..c27ab5c3 100644 --- a/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx +++ b/apps/x/apps/renderer/src/components/ai-elements/prompt-input.tsx @@ -904,13 +904,18 @@ export const PromptInputBody = ({ export type PromptInputTextareaProps = ComponentProps< typeof InputGroupTextarea ->; +> & { + autoFocus?: boolean; + focusTrigger?: unknown; // When this value changes, focus the textarea +}; export const PromptInputTextarea = ({ onChange, className, placeholder = "What would you like to know?", onKeyDown: externalOnKeyDown, + autoFocus = false, + focusTrigger, ...props }: PromptInputTextareaProps) => { const controller = useOptionalPromptInputController(); @@ -920,6 +925,17 @@ export const PromptInputTextarea = ({ const [isComposing, setIsComposing] = useState(false); const textareaRef = useRef(null); + + // Auto-focus the textarea when requested or when focusTrigger changes + useEffect(() => { + if (autoFocus || focusTrigger !== undefined) { + // Small delay to ensure the element is fully mounted and visible + const timer = setTimeout(() => { + textareaRef.current?.focus(); + }, 50); + return () => clearTimeout(timer); + } + }, [autoFocus, focusTrigger]); const containerRef = useRef(null); const highlightRef = useRef(null); diff --git a/apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx b/apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx index cd07ed7a..94eed585 100644 --- a/apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx +++ b/apps/x/apps/renderer/src/components/ai-elements/suggestions.tsx @@ -51,7 +51,7 @@ export function Suggestions({ return (
{suggestions.map((suggestion) => ( diff --git a/apps/x/apps/renderer/src/components/chat-sidebar.tsx b/apps/x/apps/renderer/src/components/chat-sidebar.tsx index 0d9a7d7e..4cddfdf0 100644 --- a/apps/x/apps/renderer/src/components/chat-sidebar.tsx +++ b/apps/x/apps/renderer/src/components/chat-sidebar.tsx @@ -260,10 +260,16 @@ export function ChatSidebar({ document.addEventListener('mouseup', handleMouseUp) }, [width]) - // Auto-focus textarea when sidebar opens + // Auto-focus textarea when sidebar opens or when conversation is cleared (new chat) useEffect(() => { - textareaRef.current?.focus() - }, []) + // Focus when conversation is empty (new chat started) + if (conversation.length === 0) { + const timer = setTimeout(() => { + textareaRef.current?.focus() + }, 50) + return () => clearTimeout(timer) + } + }, [conversation.length]) // Auto-populate with @currentfile when switching knowledge files useEffect(() => { @@ -584,9 +590,8 @@ export function ChatSidebar({ onKeyDown={handleKeyDown} onScroll={syncHighlightScroll} placeholder="Ask anything..." - disabled={isProcessing} rows={1} - className="relative z-10 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground disabled:opacity-50 resize-none max-h-32 min-h-6" + className="relative z-10 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground resize-none max-h-32 min-h-6" style={{ fieldSizing: 'content' } as React.CSSProperties} />
diff --git a/apps/x/apps/renderer/src/components/editor-toolbar.tsx b/apps/x/apps/renderer/src/components/editor-toolbar.tsx index 48b16156..95853e5c 100644 --- a/apps/x/apps/renderer/src/components/editor-toolbar.tsx +++ b/apps/x/apps/renderer/src/components/editor-toolbar.tsx @@ -22,8 +22,8 @@ import { MinusIcon, LinkIcon, CodeSquareIcon, - Undo2Icon, - Redo2Icon, + ChevronLeftIcon, + ChevronRightIcon, ExternalLinkIcon, Trash2Icon, ImageIcon, @@ -33,9 +33,21 @@ interface EditorToolbarProps { editor: Editor | null onSelectionHighlight?: (range: { from: number; to: number } | null) => void onImageUpload?: (file: File) => Promise | void + onNavigateBack?: () => void + onNavigateForward?: () => void + canNavigateBack?: boolean + canNavigateForward?: boolean } -export function EditorToolbar({ editor, onSelectionHighlight, onImageUpload }: EditorToolbarProps) { +export function EditorToolbar({ + editor, + onSelectionHighlight, + onImageUpload, + onNavigateBack, + onNavigateForward, + canNavigateBack, + canNavigateForward, +}: EditorToolbarProps) { const [linkUrl, setLinkUrl] = useState('') const [isLinkPopoverOpen, setIsLinkPopoverOpen] = useState(false) const fileInputRef = useRef(null) @@ -105,24 +117,24 @@ export function EditorToolbar({ editor, onSelectionHighlight, onImageUpload }: E return (
- {/* Undo / Redo */} + {/* Back / Forward Navigation */}
diff --git a/apps/x/apps/renderer/src/components/markdown-editor.tsx b/apps/x/apps/renderer/src/components/markdown-editor.tsx index 1bda92a9..a65f262a 100644 --- a/apps/x/apps/renderer/src/components/markdown-editor.tsx +++ b/apps/x/apps/renderer/src/components/markdown-editor.tsx @@ -30,6 +30,10 @@ interface MarkdownEditorProps { placeholder?: string wikiLinks?: WikiLinkConfig onImageUpload?: (file: File) => Promise + onNavigateBack?: () => void + onNavigateForward?: () => void + canNavigateBack?: boolean + canNavigateForward?: boolean } type WikiLinkMatch = { @@ -78,6 +82,10 @@ export function MarkdownEditor({ placeholder = 'Start writing...', wikiLinks, onImageUpload, + onNavigateBack, + onNavigateForward, + canNavigateBack, + canNavigateForward, }: MarkdownEditorProps) { const isInternalUpdate = useRef(false) const wrapperRef = useRef(null) @@ -318,7 +326,15 @@ export function MarkdownEditor({ return (
- +
{wikiLinks ? ( diff --git a/apps/x/apps/renderer/src/components/settings-dialog.tsx b/apps/x/apps/renderer/src/components/settings-dialog.tsx index ac5a24bf..b60ec736 100644 --- a/apps/x/apps/renderer/src/components/settings-dialog.tsx +++ b/apps/x/apps/renderer/src/components/settings-dialog.tsx @@ -137,7 +137,6 @@ export function SettingsDialog({ children }: SettingsDialogProps) { {children}
{/* Sidebar */} diff --git a/apps/x/packages/core/src/auth/providers.ts b/apps/x/packages/core/src/auth/providers.ts index 1cf703d6..b6afe2a2 100644 --- a/apps/x/packages/core/src/auth/providers.ts +++ b/apps/x/packages/core/src/auth/providers.ts @@ -62,7 +62,7 @@ const providerConfigs: ProviderConfig = { }, scopes: [ 'https://www.googleapis.com/auth/gmail.readonly', - 'https://www.googleapis.com/auth/calendar.readonly', + 'https://www.googleapis.com/auth/calendar.events.readonly', 'https://www.googleapis.com/auth/drive.readonly', ], }, diff --git a/apps/x/packages/core/src/config/security.ts b/apps/x/packages/core/src/config/security.ts index c2b4f9fb..9419e76e 100644 --- a/apps/x/packages/core/src/config/security.ts +++ b/apps/x/packages/core/src/config/security.ts @@ -6,7 +6,6 @@ export const SECURITY_CONFIG_PATH = path.join(WorkDir, "config", "security.json" const DEFAULT_ALLOW_LIST = [ "cat", - "curl", "date", "echo", "grep", diff --git a/apps/x/packages/core/src/knowledge/note_creation_high.ts b/apps/x/packages/core/src/knowledge/note_creation_high.ts index 9363206c..81d14dcf 100644 --- a/apps/x/packages/core/src/knowledge/note_creation_high.ts +++ b/apps/x/packages/core/src/knowledge/note_creation_high.ts @@ -997,6 +997,12 @@ If new info contradicts existing: ## 9a: Meetings — Create and Update Notes +**IMPORTANT: Write sequentially, one file at a time.** +- Generate content for exactly one note. +- Issue exactly one \`write\` command. +- Wait for the tool to return before generating the next note. +- Do NOT batch multiple \`write\` commands in a single response. + **For new entities (meetings only):** \`\`\`bash executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'") @@ -1103,6 +1109,7 @@ If you discovered new name variants during resolution, add them to Aliases field - Be concise: one line per activity entry - Note state changes with \`[Field → value]\` in activity - Escape quotes properly in shell commands +- Write only one file per response (no multi-file write batches) --- diff --git a/apps/x/packages/core/src/knowledge/note_creation_low.ts b/apps/x/packages/core/src/knowledge/note_creation_low.ts index 02eae480..ac8239f9 100644 --- a/apps/x/packages/core/src/knowledge/note_creation_low.ts +++ b/apps/x/packages/core/src/knowledge/note_creation_low.ts @@ -553,6 +553,12 @@ Before writing: ## 9a: Create and Update Notes +**IMPORTANT: Write sequentially, one file at a time.** +- Generate content for exactly one note. +- Issue exactly one \`write\` command. +- Wait for the tool to return before generating the next note. +- Do NOT batch multiple \`write\` commands in a single response. + **For new entities:** \`\`\`bash executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'") @@ -579,6 +585,7 @@ Add newly discovered name variants to Aliases field. - Use YYYY-MM-DD format for dates - Be concise: one line per activity entry - Escape quotes properly in shell commands +- Write only one file per response (no multi-file write batches) --- diff --git a/apps/x/packages/core/src/knowledge/note_creation_medium.ts b/apps/x/packages/core/src/knowledge/note_creation_medium.ts index 297e19fc..746d4fac 100644 --- a/apps/x/packages/core/src/knowledge/note_creation_medium.ts +++ b/apps/x/packages/core/src/knowledge/note_creation_medium.ts @@ -906,6 +906,12 @@ If new info contradicts existing: ## 9a: Create and Update Notes +**IMPORTANT: Write sequentially, one file at a time.** +- Generate content for exactly one note. +- Issue exactly one \`write\` command. +- Wait for the tool to return before generating the next note. +- Do NOT batch multiple \`write\` commands in a single response. + **For new entities (meetings and qualifying emails):** \`\`\`bash executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'") @@ -941,6 +947,7 @@ If you discovered new name variants during resolution, add them to Aliases field - Be concise: one line per activity entry - Note state changes with \`[Field → value]\` in activity - Escape quotes properly in shell commands +- Write only one file per response (no multi-file write batches) --- diff --git a/apps/x/packages/core/src/knowledge/sync_calendar.ts b/apps/x/packages/core/src/knowledge/sync_calendar.ts index f2719357..46ec2e1e 100644 --- a/apps/x/packages/core/src/knowledge/sync_calendar.ts +++ b/apps/x/packages/core/src/knowledge/sync_calendar.ts @@ -11,7 +11,7 @@ const SYNC_DIR = path.join(WorkDir, 'calendar_sync'); const SYNC_INTERVAL_MS = 5 * 60 * 1000; // Check every 5 minutes const LOOKBACK_DAYS = 14; const REQUIRED_SCOPES = [ - 'https://www.googleapis.com/auth/calendar.readonly', + 'https://www.googleapis.com/auth/calendar.events.readonly', 'https://www.googleapis.com/auth/drive.readonly' ];