Add Daily Note feature with Obsidian template support#1
Add Daily Note feature with Obsidian template support#1kyu91 wants to merge 1 commit intotriptu:mainfrom
Conversation
- Today tab in sidebar footer opens today's daily note
- Daily note filename format configurable in Settings (Obsidian-style tokens: YYYY, MM, DD, ddd, dddd)
- Sidebar shows full note at top + headings (H1–H6) as navigation items
- Clicking a heading shows only that section's content
- If today's note doesn't exist, shows a "Create Daily Note" prompt
- Template file path configurable in Settings; supports Obsidian tokens: {{title}}, {{date}}, {{date:FORMAT}}, {{time}}
- Edit/preview toggle (⌘E) and double-click to edit
- Live file watcher reloads content when edited externally (e.g. in Obsidian)
- Keyboard shortcut ⌥⌘D to open Today tab
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@kyu91 is attempting to deploy a commit to the triptu's projects Team on Vercel. A member of the Team first needs to authorize it. |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a comprehensive "Daily Note" feature to Flowbar, mirroring the popular Obsidian daily note workflow. It allows users to quickly access, create, and manage daily markdown notes with configurable filenames and template support. The feature includes a new live markdown editor with dynamic formatting, heading-based navigation within the note, and seamless integration with external editors like Obsidian through file watching and URL schemes. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a well-designed Daily Note feature, complete with Obsidian-style template support and heading navigation. The implementation is solid, with good use of SwiftUI and AppKit integration. My review focuses on a few critical areas to improve robustness and prevent potential data corruption. Specifically, I've pointed out issues with how file content is updated, which could lead to incorrect modifications if the note contains duplicate content. I've also highlighted several places where file I/O errors are silently ignored, which could result in data loss. Finally, there are a couple of minor suggestions for performance improvements in the new live markdown editor. Overall, this is a great addition to the app.
| guard let url = appState.dailyNoteURL else { return } | ||
| if let heading = appState.dailyNoteSelectedHeading { | ||
| let old = MarkdownParser.sectionContent(for: heading, in: appState.dailyNoteContent) | ||
| appState.dailyNoteContent = appState.dailyNoteContent.replacingOccurrences(of: old, with: newContent) |
There was a problem hiding this comment.
Using replacingOccurrences(of: with:) to save the edited section is risky and can lead to data corruption. If the original content of the section (old) appears elsewhere in the note, this method will replace all instances, not just the one being edited. A more robust approach is to identify the exact range (e.g., line numbers) of the section within the full document and replace only that specific range.
| try? content.write(to: fileURL, atomically: true, encoding: .utf8) | ||
| dailyNoteExists = true | ||
| loadDailyNoteContent(from: fileURL) | ||
| watchDailyNote(at: fileURL) |
There was a problem hiding this comment.
Using try? to write the daily note file can lead to an inconsistent UI state. If the write operation fails, the error is ignored, but the code proceeds to set dailyNoteExists = true and attempts to load the (non-existent or outdated) file. This can cause unexpected behavior. It's safer to handle the error from content.write explicitly, for example with a do-catch block, and only update the app state on a successful write.
do {
try content.write(to: fileURL, atomically: true, encoding: .utf8)
dailyNoteExists = true
loadDailyNoteContent(from: fileURL)
watchDailyNote(at: fileURL)
} catch {
print("Failed to create daily note: \(error.localizedDescription)")
dailyNoteExists = false
}| let displayLines = appState.dailyNoteDisplayContent.components(separatedBy: "\n") | ||
| guard lineIndex < displayLines.count else { return } | ||
| let targetLine = displayLines[lineIndex] | ||
| guard let fullIndex = lines.firstIndex(where: { $0 == targetLine }), |
There was a problem hiding this comment.
Finding the line to toggle by searching for its content with firstIndex(where: { $0 == targetLine }) is not reliable. If the note contains duplicate lines (e.g., two identical todo items), this could find and toggle the wrong line, especially if one of them is outside the currently displayed section. A safer method would be to calculate the line's absolute index within the full document instead of searching by its string value.
| lines[lineIndex] = toggled | ||
| } | ||
| appState.dailyNoteContent = lines.joined(separator: "\n") | ||
| try? appState.dailyNoteContent.write(to: url, atomically: true, encoding: .utf8) |
There was a problem hiding this comment.
Using try? to save the file after toggling a todo silently ignores potential write errors. This could lead to the UI showing a toggled state while the change was not actually saved to disk, causing data inconsistency. It's better to use a do-catch block to handle errors, at least by logging them, to ensure data integrity.
| let trimmed = line.trimmingCharacters(in: .whitespaces) | ||
| let level = trimmed.prefix(while: { $0 == "#" }).count | ||
| if level >= 1, level <= 6, trimmed.dropFirst(level).hasPrefix(" ") { | ||
| let sizes: [CGFloat] = [baseSize * 2.0, baseSize * 1.6, baseSize * 1.3, baseSize * 1.15, baseSize * 1.05, baseSize] |
There was a problem hiding this comment.
| let indent = String(line.prefix(while: { $0 == " " || $0 == "\t" })) | ||
| let trimmed = line.trimmingCharacters(in: .whitespaces) | ||
|
|
||
| let emptyPrefixes = ["-", "- [ ]", "- [x]", "- [X]", "*"] |
There was a problem hiding this comment.
Summary
This PR adds a Daily Note feature to Flowbar, inspired by Obsidian's daily note workflow.
⌥⌘Dshortcut) opens today's daily noteYYYY,MM,DD,ddd,dddd), e.g.YYYY-MM-DD(ddd)→2026-03-24(Mon).mdtemplate file in Settings; tokens substituted on creation:{{title}},{{date}},{{date:FORMAT}},{{time}}New files
DailyNoteContentView.swift— content view with edit/preview toggle and heading-scoped displayLiveMarkdownEditorView.swift— NSTextView-based editor with live heading + strikethrough formattingModified files
AppState.swift— daily note state,showDailyNote(),createDailyNote(), template resolutionSettingsState.swift—dailyNoteFormat+dailyNoteTemplatePathpersisted settingsMarkdownParser.swift—extractHeadings()andsectionContent()helpersSidebarView.swift— heading navigation sidebar when in daily note modeSidebarFooter.swift— Today buttonMainView.swift—.dailyNotepanel routing +⌥⌘DshortcutSettingsView.swift— Daily Note settings sectionTest plan
YYYY-MM-DD)YYYY-MM-DD(ddd)) produces correct filename🤖 Generated with Claude Code