Appendix J: Claude's Web Wrapper (Shop Manual)

Status: Live on the App Store. App Apple ID 6762163478. App Store name "Web Wrapper by Claude." GPL v3. Public repo at github.com/fluhartyml/Wraply (repo name "Wraply" is historical; the project name "Wraply" was retired 2026-04-17 in favor of "Claude's Web Wrapper" everywhere user-facing). Build-Along chapter: Part VII Build-Along 01. Claude's Web Wrapper is the first Build-Along app in this book. It wraps the book's GitHub Wiki in a native SwiftUI + WebKit shell so readers can browse all 22 Books, the Lexicon, the Index, and the Bibliography inside an app instead of a browser tab. The app is deliberately narrow in scope — one destination, one WKWebView, plus an About view, an Under the Hood view, and a Bookmarks store — so the reader can copy the pattern into a first app of their own.

What it is

A SwiftUI + WebKit + SwiftData app with a single web destination (the book's GitHub Wiki). Toolbar across the top: back / forward, star to save a bookmark, book icon to open the bookmarks list, menu for Home / Reload / About / Under the Hood. The Under the Hood feature lets the user browse the app's own source inside the app, with Lexicon Quick-Define popups on tappable Swift identifiers.

The app exists for three reasons stacked on top of each other:

  1. Reader convenience. Reading the book's wiki in a browser is workable; reading it in a native app with bookmarks and an offline-friendly shell is better.
  2. Teaching surface. First Build-Along chapter in the book. Every design choice is deliberately small so a reader can recreate the pattern in their own first app.
  3. Pattern reference. Subsequent Build-Along chapters (Bible reader, LockBox) reuse the WKWebView wrapper pattern this app established.

Architecture overview

SwiftUI + WebKit + SwiftData. No CloudKit, no custom networking, no per-platform shells beyond the one WebKit bridge.

The bookmark store

The Bookmark @Model is the book's primary SwiftData reference for Book 15:

@Model
final class Bookmark {
    var title: String
    var urlString: String
    var dateAdded: Date

    init(title: String, urlString: String, dateAdded: Date = .now) {
        self.title = title
        self.urlString = urlString
        self.dateAdded = dateAdded
    }
}

The whole feature — star-to-save in the toolbar, swipe-to-delete in the list, tap to navigate the existing web view to that URL — is the minimum viable use of @Model + @Query + modelContainer + modelContext. Book 15 walks through this exact code.

Cross-platform WebKit bridge

The wrapper has to work on iOS, iPadOS, visionOS (all UIKit-derived) and macOS (AppKit-derived). WebViewRepresentable handles this with a compile-time platform check:

#if os(macOS)
typealias PlatformWebViewBase = NSViewRepresentable
#else
typealias PlatformWebViewBase = UIViewRepresentable
#endif

struct WebViewRepresentable: PlatformWebViewBase {
    let url: URL
    // ... makeUIView / makeNSView returns a WKWebView either way
}

Same SwiftUI call site, same WKWebView underneath, two-line difference in the bridge. This is the cleanest pattern for any SwiftUI-wraps-UIKit-or-AppKit problem.

Lexicon Quick-Define — how the tap routes work

The Lexicon Quick-Define feature is the most non-obvious piece of this app, and it's the one Book 10 (TextEditor + AttributedString) and Book 19 (Custom Views) both reference. The mechanism:

  1. The Under the Hood callout sheet renders source via IdentifierTaggedSourceView.
  2. The tagger scans the source string for any Swift identifier present in LexiconContent's headword list.
  3. For each match, it wraps that range in an AttributedString with a link attribute pointing at lexicon://Headword.
  4. The parent view installs an OpenURLAction environment value that intercepts the lexicon scheme.
  5. On tap, the action handler reads the headword from the URL and presents LexiconSheet with that entry.
  6. Inside the sheet, related-headword links are real NavigationLinks on the same NavigationStack, so the user can drill from one entry to another.

Three SwiftUI features doing the work together: AttributedString, OpenURLAction, and NavigationStack with self-pushed navigation.

Build + deployment

The deployment-floor teaching moment

The original v1.0 floor was iOS 15.6 / macOS 12.0 — the lowest the build tools allowed at the time. The current build raised the floor to iOS 17 / macOS 14 because NavigationStack (iOS 16+) and AttributedString with link tap-routing (in the form this app uses) are both load-bearing for the Under the Hood retool and the Lexicon Quick-Define feature.

The teaching: pick the lowest floor that builds, ship at that floor, then raise it only when a modern API you actually need demands it. Users on iOS 15 / iOS 16 devices keep receiving the final v1.0 build via the App Store's last-compatible-version fallback. Raising the floor mid-product does not orphan users — it gates them to the version they can run.

Known gotchas

Sources