feat: deep-link timeline export (dayflow://export-timeline)#283
Open
DucretJe wants to merge 2 commits into
Open
feat: deep-link timeline export (dayflow://export-timeline)#283DucretJe wants to merge 2 commits into
DucretJe wants to merge 2 commits into
Conversation
Implements JerryZLiu#178. Adds a non-interactive deep-link action that exports the timeline to Markdown for a single day or an inclusive date range, written to a caller-supplied path or ~/Downloads by default — making timeline export scriptable from Raycast, Alfred, Shortcuts, and cron, alongside the existing recording start/stop deep links. - Extract TimelineRangeExport.build, used by both the Settings UI export and the new deep link, so both surfaces produce identical output - Add a pure, unit-tested TimelineExportRequest URL parser (date / range / destination / reveal), with no file or database I/O - Add TimelineExportService for destination resolution and the file write - Wire the export-timeline action into AppDeepLinkRouter - Document the dayflow:// automation surface in the README Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code review of the export-timeline deep link surfaced a security issue and several quality findings; this commit resolves them. - Security (P1): constrain deep-link export writes to the user's Downloads, Documents, and Desktop folders (or subfolders) and collapse `..` before the containment check. Previously a hostile `dayflow://export-timeline?path=...` (the URL scheme is invokable by other apps/webpages, and the app is not sandboxed) could overwrite arbitrary user-writable files. Rejected paths fail soft (logged + timeline_export_failed analytics, nothing written). - Cap export range at 366 days (ParseError.rangeTooLarge) so a single link can't trigger thousands of background DB reads. - Run the export on a GCD global queue instead of Task.detached so the synchronous DB reads and file write don't occupy a Swift cooperative thread. - Unify the export filename: extract TimelineRangeExport.defaultFileName and use it from both the deep link and the Settings save panel (removes the divergent local DateFormatter; single-day exports now share one name). - Resolve destination param aliases in preference order (path > to > destination) instead of by query-string position. - Register timeline_exported / timeline_export_failed in the analytics dictionary; document the allowed folders and range cap in the README. - Strengthen tests: destination allowlist/traversal rejection, range cap, reveal=yes, range with only `end`, alias precedence, router action wiring, and the moved defaultFileName cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: Deep-link timeline export (
dayflow://export-timeline)Note
I'm not used to swift apps, this code change is mainly done using AI. I tested it, it seems to be good, my main concern is about the path you pass to the
.mdartifact. basically we just allow it to drop it in allowed paths, maybe we could not give choice and send it toDownloads.Closes #178.
What
Adds a non-interactive deep-link action that exports the Dayflow timeline to a Markdown file, so it can be triggered from Raycast, Alfred, Apple Shortcuts, or cron — closing the gap in #178 (today the Markdown export is only reachable through Settings, which opens an interactive
NSSavePanel, while recording start/stop is already automatable via deep links).Parameters (query keys, case-insensitive):
datetoday,yesterday, orYYYY-MM-DD(single day)todaystart/endpath(aliasesto,destination)~expanded~/Downloads/Dayflow timeline <range>.mdrevealtrue/1/yes→ reveal in Finder when donefalseHow
TimelineRangeExport(new) — the shared builder that turns a day range into Markdown + counts. Both the Settings UI export and the new deep link call it, so they produce identical output from one source of truth (it also now owns the shareddefaultFileName).TimelineExportRequest(new) — a pure, fully unit-tested URL→parameters parser (no file or DB I/O). Returns aResultwith typed errors.TimelineExportService(new) — the side effects: resolve the destination, write the file, emit analytics, optionally reveal in Finder.AppDeepLinkRouter— adds theexport-timelineaction (aliasexport) next to the existingstart-recording/stop-recording/referralactions.OtherSettingsViewModel— refactored to call the shared builder (its inline day loop is gone).TimelineClipboardFormatter.makeMarkdownandtimelineDisplayDate(4am→4am timeline day). Noproject.pbxprojedits needed — the project uses file-system-synchronized groups.Security
The
dayflow://scheme is invokable by any local app or webpage, and the app is not sandboxed. To prevent a hostile link from overwriting arbitrary files (~/.zshrc,~/.ssh/config, LaunchAgents) or exfiltrating the timeline:..is collapsed before the containment check; out-of-bounds paths are rejected and nothing is written.timeline_export_failedanalytics event), never crashing.The interactive Settings export is unchanged and still uses
NSSavePanel.Tests
Build and Unit tests are passing.
No idea how to do the integration tests :/