Skip to content

feat: deep-link timeline export (dayflow://export-timeline)#283

Open
DucretJe wants to merge 2 commits into
JerryZLiu:mainfrom
DucretJe:feat/deeplink-timeline-export
Open

feat: deep-link timeline export (dayflow://export-timeline)#283
DucretJe wants to merge 2 commits into
JerryZLiu:mainfrom
DucretJe:feat/deeplink-timeline-export

Conversation

@DucretJe

@DucretJe DucretJe commented Jun 16, 2026

Copy link
Copy Markdown

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 .md artifact. basically we just allow it to drop it in allowed paths, maybe we could not give choice and send it to Downloads.

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).

# Export today to ~/Downloads
open "dayflow://export-timeline?date=today"

# Export a date range to a specific file
open "dayflow://export-timeline?start=2026-06-01&end=2026-06-07&path=~/Documents/last-week.md"

# Export yesterday into a folder and reveal it in Finder
open "dayflow://export-timeline?date=yesterday&path=~/Documents/reports/&reveal=true"

Parameters (query keys, case-insensitive):

Parameter Values Default
date today, yesterday, or YYYY-MM-DD (single day) today
start / end same grammar (inclusive range)
path (aliases to, destination) a file path or a directory to write into; ~ expanded ~/Downloads/Dayflow timeline <range>.md
reveal true / 1 / yes → reveal in Finder when done false

How

  • 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 shared defaultFileName).
  • TimelineExportRequest (new) — a pure, fully unit-tested URL→parameters parser (no file or DB I/O). Returns a Result with typed errors.
  • TimelineExportService (new) — the side effects: resolve the destination, write the file, emit analytics, optionally reveal in Finder.
  • AppDeepLinkRouter — adds the export-timeline action (alias export) next to the existing start-recording/stop-recording/referral actions.
  • OtherSettingsViewModel — refactored to call the shared builder (its inline day loop is gone).
  • Reuses the existing TimelineClipboardFormatter.makeMarkdown and timelineDisplayDate (4am→4am timeline day). No project.pbxproj edits 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:

  • Deep-link exports are constrained to the user's Downloads, Documents, and Desktop folders (and subfolders). .. is collapsed before the containment check; out-of-bounds paths are rejected and nothing is written.
  • The export range is capped at 366 days per call, so one link can't trigger thousands of background DB reads.
  • The export runs on a background GCD queue (off the Swift cooperative thread pool) and fails soft (logged + a timeline_export_failed analytics 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 :/

DucretJe and others added 2 commits June 16, 2026 16:41
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>
@DucretJe DucretJe marked this pull request as draft June 16, 2026 15:15
@DucretJe DucretJe marked this pull request as ready for review June 17, 2026 07:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Deep-link or CLI Export of Dayflow Timeline (for Raycast/automation)

1 participant