Skip to content

Add export connectors to onboarding and desktop apps#6373

Merged
kodjima33 merged 4 commits into
mainfrom
codex/exports-after-import
Apr 7, 2026
Merged

Add export connectors to onboarding and desktop apps#6373
kodjima33 merged 4 commits into
mainfrom
codex/exports-after-import

Conversation

@kodjima33
Copy link
Copy Markdown
Collaborator

Summary

  • add an Exports step after imports in desktop onboarding
  • add export connector cards to the desktop Apps page
  • support lower-friction export flows for Notion and Obsidian plus copy-and-open flows for ChatGPT, Claude, and Gemini
  • add real Obsidian and Gemini logos to the shared connector icon set

Testing

  • cd desktop/Desktop && swift test --filter OnboardingFlowTests

@kodjima33 kodjima33 merged commit 39b180f into main Apr 7, 2026
3 checks passed
@kodjima33 kodjima33 deleted the codex/exports-after-import branch April 7, 2026 07:37
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 7, 2026

Greptile Summary

This PR adds an Exports onboarding step (after DataSources) and export-destination cards on the Apps page, supporting Obsidian (automated file write), Notion (clipboard copy-and-open), and ChatGPT/Claude/Gemini (prompt + memory-pack copy-and-open).

  • HANDOFF-exports-after-import.md is a WIP internal note explicitly marked not ready to merge; the file itself states the current UX is "not acceptable yet" and requests author sign-off before landing.
  • MemoryExportService retains a full Notion API integration (exportToNotion) that is never called from any UI path and uses a non-existent future-dated API version (2026-03-11); the Notion UX has been simplified to clipboard copy-paste only, leaving this code as a latent breakage point.

Confidence Score: 4/5

Not safe to merge — HANDOFF file explicitly flags UX as unacceptable and requests author confirmation before landing.

Two P1 findings: the committed WIP handoff document signals the PR is intentionally incomplete, and the non-existent Notion API version string is a latent runtime failure if the dead code path is ever reactivated.

HANDOFF-exports-after-import.md and desktop/Desktop/Sources/MemoryExportService.swift

Important Files Changed

Filename Overview
HANDOFF-exports-after-import.md WIP handoff note explicitly flagged as not-ready-to-merge; should be removed before landing
desktop/Desktop/Sources/MemoryExportService.swift Core export logic; contains a future-dated Notion API version string and an entirely unreachable exportToNotion API flow
desktop/Desktop/Sources/OnboardingExportsStepView.swift New onboarding exports step view; contains an unused inlineTextField helper left over from removed Notion token flow
desktop/Desktop/Sources/MainWindow/Pages/MemoryExportDestinationSheet.swift Export destination sheet and card grid; RelativeDateTimeFormatter allocated on each render
desktop/Desktop/Sources/MainWindow/Pages/AppsPage.swift Adds ExportsSection grid and sheet presentation to the Apps page; logic looks correct
desktop/Desktop/Sources/OnboardingFlow.swift Inserts Exports step at index 16 with migration guard; migration logic and tests look correct
desktop/Desktop/Sources/OnboardingView.swift Wires hasInsertedExportsStep migration flag and renders new Exports step correctly
desktop/Desktop/Sources/ConnectorBrandIcon.swift Adds Notion, Obsidian, Gemini brand entries with app-path detection and fallback SF Symbols
desktop/Desktop/Tests/OnboardingFlowTests.swift Adds tests covering the new Exports step migration; all assertions appear correct
desktop/Desktop/Sources/ViewExporter.swift Registers the new Exports onboarding step (index 16) in the export preview registry

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User clicks Export destination] --> B{destination}
    B -->|Obsidian| C{vault path set?}
    C -->|No| D[NSOpenPanel: pick vault]
    D --> E[exportToObsidian: write Omi/Memories.md]
    C -->|Yes| E
    E --> F[Open obsidian://open URL]
    B -->|Notion| G[prepareManualExport]
    G --> H[Fetch memories up to 250]
    H --> I[Build markdown pack]
    I --> J[Copy markdown to clipboard]
    J --> K[Reveal file in Finder]
    K --> L[Open notion.so in browser/app]
    B -->|ChatGPT / Claude / Gemini| M[prepareManualExport]
    M --> N[Fetch memories up to 400]
    N --> O[Build markdown with prompt prefix]
    O --> P[Copy prompt + markdown to clipboard]
    P --> Q[Reveal file in Finder]
    Q --> R[Open target URL in browser/app]
Loading

Reviews (1): Last reviewed commit: "Polish export connectors and add brand l..." | Re-trigger Greptile

Comment on lines +1 to +31
Branch: `codex/exports-after-import`
Worktree: `/Users/nik/projects/omi-exports-after-import`

Current state:
- Export onboarding step exists after imports.
- Apps page has export cards.
- Local test bundle: `com.omi.exports-after-import-local`
- The current UX is not acceptable yet:
- ChatGPT/Claude/Gemini manual export does not auto-paste into destination apps.
- Notion and Obsidian flows feel too heavy and should be simplified.

What likely needs work next:
1. Make manual exports copy the generated memory pack/prompt automatically and open the destination reliably.
2. Simplify Notion export UX:
- Prefer one-click connect or reuse existing Notion integration if possible.
- Avoid token + parent page friction if there is a simpler in-repo path.
3. Simplify Obsidian export UX:
- Prefer folder/vault selection once, then one-click export.
4. Test on Mac mini instead of local machine when possible.

Key files:
- `desktop/Desktop/Sources/MemoryExportService.swift`
- `desktop/Desktop/Sources/OnboardingExportsStepView.swift`
- `desktop/Desktop/Sources/MainWindow/Pages/MemoryExportDestinationSheet.swift`
- `desktop/Desktop/Sources/MainWindow/Pages/AppsPage.swift`
- `desktop/Desktop/Sources/OnboardingFlow.swift`
- `desktop/Desktop/Sources/OnboardingView.swift`

Notes:
- User wants this on a separate worktree and does not want anything merged until they confirm.
- Main branch had newer onboarding/import UI changes; this branch was merged on top of updated main but still needs product polish and verification.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 WIP handoff artifact committed to repo

This file is an internal note that explicitly states "User wants this on a separate worktree and does not want anything merged until they confirm" and that the current UX is "not acceptable yet." Committing it to main signals the PR is intentionally in a draft state. Remove this file before landing.

static let shared = MemoryExportService()

private let defaults = UserDefaults.standard
private let notionVersion = "2026-03-11"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Non-existent Notion API version

"2026-03-11" is a future-dated version string the Notion API does not recognise. The current stable version header is "2022-06-28". While exportToNotion is currently unreachable from the UI, it remains live code and would return HTTP 400 on every call if ever invoked.

Suggested change
private let notionVersion = "2026-03-11"
private let notionVersion = "2022-06-28"

Comment on lines +215 to +255
func exportToNotion(token: String, parentPageID: String) async throws -> MemoryExportResult {
let sanitizedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
let sanitizedParentPageID = parentPageID.trimmingCharacters(in: .whitespacesAndNewlines)
guard !sanitizedToken.isEmpty, !sanitizedParentPageID.isEmpty else {
throw MemoryExportError.invalidNotionConfiguration
}

let memories = try await fetchMemories(limit: 250)
guard !memories.isEmpty else { throw MemoryExportError.noMemories }

let pageTitle = "Omi Memory Export \(Self.exportTitleFormatter.string(from: Date()))"
let pageID = try await createNotionPage(
token: sanitizedToken,
parentPageID: sanitizedParentPageID,
title: pageTitle
)
try await appendNotionBlocks(
token: sanitizedToken,
pageID: pageID,
memories: memories
)

defaults.set(sanitizedToken, forKey: MemoryExportDestination.notion.notionTokenKey)
defaults.set(sanitizedParentPageID, forKey: MemoryExportDestination.notion.notionParentPageKey)

let detail = "Exported to Notion"
persistStatus(
destination: .notion,
exportedCount: memories.count,
detailText: detail,
filePath: nil
)

return MemoryExportResult(
memoryCount: memories.count,
detailText: detail,
destinationURL: URL(string: "https://www.notion.so/"),
fileURL: nil,
clipboardText: nil
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 exportToNotion is unreachable dead code

The full Notion API integration (exportToNotion, createNotionPage, appendNotionBlocks) is never called from any UI path — MemoryExportDestinationSheetModel.run() routes .notion to prepareManualExport (clipboard copy-open) instead. The notionToken and notionParentPageID published properties on the sheet model are similarly unused. This code should either be wired to a UI path or removed to avoid confusion.

Comment on lines +54 to +55
return
"Updated \(RelativeDateTimeFormatter().localizedString(for: lastExportedAt, relativeTo: Date()))"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 RelativeDateTimeFormatter allocated on every render

A new formatter is constructed inside a computed property evaluated on every MemoryExportCard re-render. Formatters are expensive to create; promote it to a static constant.

Suggested change
return
"Updated \(RelativeDateTimeFormatter().localizedString(for: lastExportedAt, relativeTo: Date()))"
private static let relativeDateFormatter = RelativeDateTimeFormatter()
private var secondaryText: String? {
if let lastExportedAt = status.lastExportedAt {
return
"Updated \(Self.relativeDateFormatter.localizedString(for: lastExportedAt, relativeTo: Date()))"

Comment on lines +237 to +262
private func inlineTextField(
_ placeholder: String,
text: Binding<String>,
secure: Bool = false
) -> some View {
Group {
if secure {
SecureField(placeholder, text: text)
} else {
TextField(placeholder, text: text)
}
}
.textFieldStyle(.plain)
.font(.system(size: 13))
.foregroundColor(OmiColors.textPrimary)
.padding(.horizontal, 14)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(OmiColors.backgroundSecondary)
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.stroke(Color.white.opacity(0.08), lineWidth: 1)
)
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unused inlineTextField helper

inlineTextField(_:text:secure:) is defined inside OnboardingInlineExportPanel but never called — the Notion token/page-ID fields it served were removed when the flow was simplified to clipboard copy-paste. Remove the dead helper.

Glucksberg pushed a commit to Glucksberg/omi-local that referenced this pull request Apr 28, 2026
)

## Summary
- add an Exports step after imports in desktop onboarding
- add export connector cards to the desktop Apps page
- support lower-friction export flows for Notion and Obsidian plus
copy-and-open flows for ChatGPT, Claude, and Gemini
- add real Obsidian and Gemini logos to the shared connector icon set

## Testing
- cd desktop/Desktop && swift test --filter OnboardingFlowTests
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.

1 participant