Launch

Waku: the visual PKM I wish I'd found on the App Store

Alexandre
Alexandre
··
Reading time: 7 min
Waku Logo — Download on the App Store
Two months. One Apple rejection for a Safari logo in a screenshot. And this morning, the notification: "WAKU (iOS) — Version 1.0.0 status changed to Ready for Distribution." My second digital baby is alive.
Apple notification — WAKU v1.0.0 ready for distribution

Click to enlarge

Why I built Waku

Pocket shut down. Read Later stopped. Raindrop became bloated. GoodLinks hasn't been updated since 2019.
Millions of people save links every week — articles, threads, videos, recipes — and never find them again. Not because they're disorganized, but because the existing tools are either dead, ugly, or too complex.
I had the same problem. Dozens of links per week, scattered between Notes, Safari Reading List, and tabs open since August 2025. Twitter threads about SwiftUI, recipes, articles I wanted to re-read. I never did.
One evening, I opened Xcode. If I have to build my own tool, I might as well do it right.
Waku (枠) means "frame" in Japanese. It's what gives shape to your ideas.

What Waku does

Waku is a personal knowledge manager (PKM) for iOS. Not a complex tool with graphs and backlinks — an elegant space to capture a link, annotate an article, find an idea.

v1.0.0 features

  • Instant capture — paste a link, Waku automatically extracts the title, description, image and favicon
  • Rich notes — text + images + PencilKit drawings, a real visual notebook
  • Collections & tags — organize your bookmarks your way
  • On-device AI summary — Apple Intelligence summarizes your bookmarks and notes, everything runs on-device, no data sent anywhere
  • Siri & App Intents — "Hey Siri, save this link in Waku"
  • iOS Widgets — 3 sizes (Small, Medium, Large), your bookmarks on the home screen
  • Spotlight — your bookmarks indexed, searchable from iOS search
  • 100% offline-first — SwiftData, no account, no server, your data stays on your device
  • Bento design — organic grid, rounded corners, warm orange accent, inspired by Bento.me and Arc

Waku on the App Store

Waku — Collections and favoritesWaku — Instant captureWaku — Rich notesWaku — Tags and organizationWaku — iOS WidgetsWaku — Bento design

The tech stack

Everything is native Swift. No cross-platform, no compromise.
TechRole
SwiftUIInterface
SwiftDataOffline persistence
Swift 6Strict concurrency
MVVMArchitecture
LinkPresentationMetadata extraction
FoundationModelsOn-device AI (Apple Intelligence)
AppIntentsSiri & Shortcuts
WidgetKitiOS Widgets
The architecture is the same as Livate: strict MVVM, offline-first, cache-first. The difference: Waku has no user backend. Everything is local. I chose to stay 100% local, with no data collection, to respect users' privacy.

Under the hood: metadata extraction

Illustration — metadata extraction from a link

Click to enlarge

When you paste a link, Waku launches LinkPresentation and HTML parsing in parallel, then merges results with a fallback strategy:
func extract(from url: URL) async throws -> ItemMetadata {
    async let lpResult = extractWithLP(from: url)
    async let htmlResult = fetchHTML(from: url)

    let lp = try? await lpResult
    let htmlString = try? await htmlResult
    let html = htmlString.map { parseHTML($0, base: url) }
    let oembed = await extractWithOEmbed(from: url, html: htmlString)

    guard lp != nil || html != nil || oembed != nil else {
        throw ExtractionError.extractionFailed
    }

    let thumbnail = oembed?.thumbnailURL
        ?? html?.ogImageURL
        ?? youtubeDirectThumbnail(for: url)

    return ItemMetadata(
        title: oembed?.title ?? lp?.title ?? html?.title ?? url.host() ?? url.absoluteString,
        description: oembed?.description ?? html?.description ?? "",
        ogImageURL: thumbnail,
        faviconURL: html?.faviconURL ?? faviconFallback(for: url),
        siteName: oembed?.providerName ?? html?.siteName ?? lp?.siteName ?? url.host() ?? ""
    )
}
LinkPresentation returns nil on many sites — hence the HTML fallback that parses Open Graph tags directly. The oEmbed → HTML → LP chain guarantees we always get something.

The SwiftData model

Each bookmark is a WakuItem persisted with SwiftData. Heavy data (PencilKit drawings, formatted content) uses externalStorage to keep the database lightweight:
@Model
final class WakuItem {
    var id: UUID
    var url: String
    var title: String
    var descriptionText: String
    var ogImageURL: String?
    var siteName: String
    var isPinned: Bool
    var isFavorite: Bool
    @Attribute(.externalStorage) var customImageData: Data?
    @Attribute(.externalStorage) var drawingData: Data?
    @Attribute(.externalStorage) var noteBlocksData: Data?
    var summary: String?

    var collection: ItemCollection?
    @Relationship(deleteRule: .nullify) var tags: [Tag] = []
}

Siri & App Intents

"Hey Siri, save this link in Waku" — powered by an AppIntent using the .browser.bookmarkURL schema:
@AppIntent(schema: .browser.bookmarkURL)
struct SaveBookmarkIntent: AppIntent {
    static let title: LocalizedStringResource = "intent.save.title"

    @Parameter(title: "URL")
    var url: URL

    @MainActor
    func perform() async throws -> some IntentResult & ReturnsValue<BrowserBookmarkEntity> & ProvidesDialog {
        let displayName = url.host() ?? url.absoluteString

        switch BookmarkPendingStore.save(urlInput: url.absoluteString) {
        case .saved(let host):
            return .result(value: entity, dialog: "\(host) saved in Waku!")
        case .alreadyPending:
            return .result(value: entity, dialog: "This link is already pending")
        case .invalidURL:
            return .result(value: entity, dialog: "Invalid URL")
        case .storeError:
            return .result(value: entity, dialog: "Save error")
        }
    }
}

The dev log

February 1, 2026 — First item captured end-to-end. I thought I'd be done in two weeks. I wasn't.
Mid-February — Third design system iteration. Neumorphism felt like 2019, glassmorphism felt too iOS 15. I spent three evenings drawing rounded corners in Figma before finding the right balance: flat, very rounded corners, soft shadows, warm orange, SF Pro Rounded.
March 5 — Metadata extraction breaks on 30% of links. LinkPresentation returns nil for no reason on some sites. I coded an HTML fallback that parses the <head> directly.
March 18 — Siri & App Intents are working. "Hey Siri, save this link in Waku." Works on the first try. First real moment of satisfaction in three weeks.
March 14 — First submission to Apple. From that point on, you can't do anything. You refresh App Store Connect. You check your emails. You sleep badly. And you wait. Nearly a week before the review even starts. A week staring at "Waiting for Review" with nothing you can do.
March 22, 2:12 PM — Apple rejection. Safari logo in a screenshot. Fixed in five minutes, resubmitted.
March 23, 9:03 AM — "Ready for Distribution."

Publishing an app in 2026 is a contact sport

Illustration — the Apple review process

Click to enlarge

What nobody tells you when you start building an app is that the code is the easy part. The real final boss is the Apple review.

1,600 apps per day: the bottleneck

Submitting an app and getting a response within 24-48 hours used to be the norm. In 2026, that's a distant memory.
Vibe coding changed everything. With tools like Cursor, Replit, or Claude generating code, anyone can produce an app in a weekend and submit it. In 2025, the App Store received about 1,340 new apps per day. By March 2026, that number climbed to 1,600 daily submissions — over 45,000 apps per month (Apptunix). Apple's review pipeline hasn't kept up.
The result: review times getting longer, overloaded reviewer teams, and you, indie dev, just waiting. Refreshing App Store Connect every hour. Checking your emails at 2 AM. Wondering if your app will be rejected because you used the word "best" in your description.

Apple's March 2026 crackdown

On March 18, 2026 — while my app had been waiting for review for four days — Apple quietly blocked updates for several vibe coding apps, including Replit and Vibecode. No new rule: just strict enforcement of clause 3.3.1(B), which prohibits apps from changing their functionality after approval.
The core issue: these apps let you generate an iOS app from an iPhone, submit it, and modify it after the fact. Apple sees that as bypassing their review. Replit hasn't been able to publish an update since January 2026 and dropped from 1st to 3rd place in the Developer Tools chart (MacRumors).
The irony is that Apple simultaneously integrated support for Cursor and Claude into Xcode. Vibe coding on desktop? Fine. On mobile? Blocked. The difference? On desktop, the code goes through review before publication. On mobile, it bypasses everything.

The real problem: quality

Beyond review delays, there's the question of what's landing on the App Store. Vibe coding accelerates prototyping, not quality. Technical analyses (Glide, Magnise) flag the same recurring issues in AI-generated apps: undetected security vulnerabilities, inconsistent architecture, edge-case bugs, and technical debt that explodes as soon as the app leaves prototype mode.
As Addy Osmani puts it: vibe coding isn't an excuse for shipping sloppy work. It's a tool. But a stable app for real users requires rigor, testing, and architecture. AI doesn't replace that.

What makes the process stressful

The hardest part is the opacity. You submit your app, and then it's silence. No progress bar, no ETA. Just "Waiting for Review." You don't know if the reviewer will take 2 days or 2 weeks.
And when the rejection comes — because it often does — it's sometimes for reasons you never would have guessed. A wording in your description. A button that doesn't lead exactly where Apple expects. Or, in my case, a Safari logo in a screenshot.
For Livate, I got rejections. For Waku too. Apple protects its visual trademarks, and even an icon that seems harmless can trigger a rejection. Fixed within hours, but it's a reminder that every detail matters. I know the App Store Review Guidelines by heart now.

What I learned

A few tips for those about to submit:
  • Read the guidelines before writing a single line of code, not the night before submission
  • Test on a real device, not just the simulator — Apple tests on real hardware
  • Prepare your metadata in advance — descriptions, screenshots, category, keywords — it takes as long as the dev
  • Plan buffer time — never promise a launch date without Apple's validation in hand
  • Submit early, submit often — a pre-release with core features, then updates. It's easier to get updates through than a first submission

Waku is free

The app is free with 20 bookmarks, 3 collections, and theme/language/layout options. For those who want more, a single in-app purchase at $4.99 (lifetime) unlocks everything:
  • Unlimited bookmarks and collections
  • On-device AI summary
  • iOS Widgets
  • Custom accent color
  • Import/export
  • Siri guide
No subscription. A one-time purchase to support development and unlock the full app.
If you're looking for a simple place to save what matters — no account, no mandatory cloud — download Waku on the App Store and let me know what you think. I read every piece of feedback.

What's next?

v1.1 is already in the works:
  • CloudKit — iCloud sync across your devices
  • Share Extension — capture from any app in one tap
  • Pocket/Raindrop import — migrate your bookmarks in one click
The full dev log (with code) is on getwaku.app.

Comments

Comments

Got a take on this article?

Create a free account in 10 seconds to comment, like, and get the next articles straight to your inbox.

Don't have an account yet?

This site uses cookies for analytics and advertising. No personal data is sold. Learn more