From 672d4b89d342438982ece75c47eef60654640db6 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 08:14:34 +0000 Subject: [PATCH] Docs & polish: DocC hosting, examples, SECURITY.md, localization - DocC: enrich the catalog landing page, fix symbol links for the new onError signatures, add a PrototypeKitError topic, and add a Docs workflow that builds the DocC archive and publishes it to GitHub Pages (one-time: set Pages source to "GitHub Actions"). - Examples: add an Examples/ gallery (reference SwiftUI, not built by the package) demonstrating every feature, including the onError handler, plus a README. - SECURITY.md: vulnerability-reporting policy. - Localization: route the two user-facing camera placeholder messages through NSLocalizedString with an en string table, and set defaultLocalization: "en". - README: link the hosted docs and the Examples gallery. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_014wwQvH5kuGmjQ9nW3gJ6zD --- .github/workflows/docs.yml | 61 ++++++ CHANGELOG.md | 5 + Examples/PrototypeKitExamples.swift | 181 ++++++++++++++++++ Examples/README.md | 32 ++++ Package.swift | 1 + README.md | 8 + SECURITY.md | 37 ++++ .../Camera/CameraViewController-iOS.swift | 12 +- .../PrototypeKit.docc/PrototypeKit.md | 33 +++- .../PrototypeKit/en.lproj/Localizable.strings | 5 + 10 files changed, 363 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 Examples/PrototypeKitExamples.swift create mode 100644 Examples/README.md create mode 100644 SECURITY.md create mode 100644 Sources/PrototypeKit/en.lproj/Localizable.strings diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..e7e98ec --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,61 @@ +# Builds the DocC documentation and publishes it to GitHub Pages. +# +# One-time setup: in the repository settings, set Pages → Build and deployment → Source to +# "GitHub Actions". The site is then served at https://fridaytechnologies.github.io/PrototypeKit/. + +name: Docs + +on: + push: + branches: [ "master" ] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment; don't cancel an in-progress one. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + + - name: Build DocC archive + run: > + xcodebuild docbuild + -scheme PrototypeKit + -destination 'generic/platform=iOS' + -derivedDataPath .derivedData + + - name: Transform for static hosting + run: | + ARCHIVE=$(find .derivedData -type d -name 'PrototypeKit.doccarchive' | head -n 1) + if [ -z "$ARCHIVE" ]; then + echo "::error::PrototypeKit.doccarchive not found under .derivedData" + exit 1 + fi + "$(xcrun --find docc)" process-archive transform-for-static-hosting "$ARCHIVE" \ + --output-path _site \ + --hosting-base-path PrototypeKit + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: _site + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f009a..a91dd18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,11 @@ once tagged releases begin. these failures — show a message, offer a retry — instead of only reading the log. The parameter defaults to `nil`, so existing call sites are unaffected, and errors are still logged as before. Transient per-frame Vision errors remain log-only by design. +- **Documentation & examples.** Enriched the DocC catalog and added a `Docs` workflow that builds + and publishes it to GitHub Pages; a runnable `Examples/` gallery covering every feature; and a + `SECURITY.md` vulnerability-reporting policy. +- **Localization.** The user-facing camera placeholder messages now go through `NSLocalizedString` + with an `en` string table (`defaultLocalization: "en"`), so they can be translated. ## [0.1.0] - 2026-07-02 diff --git a/Examples/PrototypeKitExamples.swift b/Examples/PrototypeKitExamples.swift new file mode 100644 index 0000000..f8bb3a4 --- /dev/null +++ b/Examples/PrototypeKitExamples.swift @@ -0,0 +1,181 @@ +// +// PrototypeKitExamples.swift +// +// Reference examples for PrototypeKit. Copy this file into your own SwiftUI app target. +// This file is intentionally NOT part of the Swift package (it lives under Examples/), so it is +// never compiled by the library — it's a copy-paste starting point. +// +// Set `ExampleGallery()` as your app's root view. See Examples/README.md for the Info.plist keys +// each feature needs (camera / microphone). +// + +#if os(iOS) +import SwiftUI +import PrototypeKit + +/// A menu linking to a demo for each PrototypeKit feature. +struct ExampleGallery: View { + var body: some View { + NavigationStack { + List { + Section("Camera + Vision") { + NavigationLink("Live text", destination: LiveTextExample()) + NavigationLink("Live barcodes", destination: LiveBarcodeExample()) + NavigationLink("Face detection", destination: FaceCountExample()) + NavigationLink("Image classification", destination: ImageClassifierExample()) + } + Section("Sound") { + NavigationLink("Sound recognition", destination: SoundExample()) + } + Section("Motion") { + NavigationLink("Activity classification", destination: ActivityExample()) + } + Section("Natural Language") { + NavigationLink("Sentiment", destination: SentimentExample()) + } + } + .navigationTitle("PrototypeKit") + } + } +} + +// MARK: - Camera + Vision (no model required) + +/// Recognizes text in the live camera feed and lists it. +struct LiveTextExample: View { + @State private var detectedText: [String] = [] + + var body: some View { + ZStack(alignment: .bottom) { + LiveTextRecognizerView(detectedText: $detectedText) + Text(detectedText.joined(separator: "\n")) + .padding() + .frame(maxWidth: .infinity) + .background(.thinMaterial) + } + .ignoresSafeArea() + } +} + +/// Decodes barcodes/QR codes in the live camera feed. +struct LiveBarcodeExample: View { + @State private var barcodes: [String] = [] + + var body: some View { + ZStack(alignment: .bottom) { + LiveBarcodeRecognizerView(detectedBarcodes: $barcodes) + Text(barcodes.first ?? "Point the camera at a barcode") + .padding() + .frame(maxWidth: .infinity) + .background(.thinMaterial) + } + .ignoresSafeArea() + } +} + +/// Counts faces in the live camera feed. +struct FaceCountExample: View { + @State private var faceCount = 0 + + var body: some View { + ZStack(alignment: .top) { + LiveFaceDetectorView(faceCount: $faceCount) + Text("Faces: \(faceCount)") + .font(.title2.bold()) + .padding() + .background(.thinMaterial, in: Capsule()) + .padding(.top, 60) + } + .ignoresSafeArea() + } +} + +// MARK: - Camera + Core ML (bring your own Create ML model) + +/// Classifies each camera frame with a Create ML image classifier. +/// +/// Replace `modelURL` with your own model, e.g. `FruitClassifier.urlOfModelInThisBundle`. +struct ImageClassifierExample: View { + @State private var prediction = "" + @State private var errorMessage: String? + + // Point this at your Create ML model's `urlOfModelInThisBundle`. + private let modelURL = URL(fileURLWithPath: "/path/to/YourModel.mlmodelc") + + var body: some View { + ZStack(alignment: .bottom) { + ImageClassifierView(modelURL: modelURL, + latestPrediction: $prediction) { error in + // React to a model-load failure instead of showing a silent, prediction-less feed. + errorMessage = error.localizedDescription + } + Text(errorMessage ?? (prediction.isEmpty ? "Classifying…" : prediction)) + .padding() + .frame(maxWidth: .infinity) + .background(.thinMaterial) + } + .ignoresSafeArea() + } +} + +// MARK: - Sound + +/// Recognizes sounds from the microphone using the built-in classifier. +struct SoundExample: View { + @State private var recognizedSound: String? + @State private var errorMessage: String? + + var body: some View { + VStack(spacing: 16) { + Text(recognizedSound ?? "Listening…") + .font(.title) + if let errorMessage = errorMessage { + Text(errorMessage).foregroundStyle(.red).font(.footnote) + } + } + .recognizeSounds(recognizedSound: $recognizedSound) { error in + errorMessage = error.localizedDescription + } + } +} + +// MARK: - Motion + +/// Classifies device activity with a Create ML activity classifier. +/// +/// Replace `modelURL` with your own model, e.g. `ActivityClassifier.urlOfModelInThisBundle`. +struct ActivityExample: View { + @State private var activity: String? + @State private var errorMessage: String? + + private let modelURL = URL(fileURLWithPath: "/path/to/YourActivityModel.mlmodelc") + + var body: some View { + VStack(spacing: 16) { + Text(activity ?? "Detecting…").font(.title) + if let errorMessage = errorMessage { + Text(errorMessage).foregroundStyle(.red).font(.footnote) + } + } + .classifyActivity(modelURL: modelURL, latestActivity: $activity) { error in + errorMessage = error.localizedDescription + } + } +} + +// MARK: - Natural Language (ships with the OS, no camera/mic/model) + +/// Scores the sentiment of some text as you type. +struct SentimentExample: View { + @State private var text = "I love prototyping with this!" + @State private var score: Double = 0 + + var body: some View { + Form { + TextField("Type something", text: $text, axis: .vertical) + Text("Sentiment: \(score, specifier: "%.2f")") + .analyzeSentiment(text: text, score: $score) + } + } +} +#endif diff --git a/Examples/README.md b/Examples/README.md new file mode 100644 index 0000000..f618ad0 --- /dev/null +++ b/Examples/README.md @@ -0,0 +1,32 @@ +# PrototypeKit Examples + +Reference SwiftUI code showing how to use each PrototypeKit feature. These files live outside the +Swift package's `Sources/` directory, so they are **not** compiled as part of the library — they're +meant to be copied into your own app. + +## Running the examples + +1. Create a new iOS app in Xcode (**File → New → Project → App**, SwiftUI lifecycle). +2. Add PrototypeKit as a package dependency (see the root [README](../README.md#installation-)). +3. Copy [`PrototypeKitExamples.swift`](PrototypeKitExamples.swift) into your app target. +4. Set `ExampleGallery()` as your root view (or present it). +5. Add the Info keys the features you try need: + - **Camera** views: `Privacy - Camera Usage Description` (`NSCameraUsageDescription`). + - **Sound** recognition: `Privacy - Microphone Usage Description` (`NSMicrophoneUsageDescription`). + - **Activity** classification reads Core Motion, which needs no usage string. + +## What's covered + +| Feature | Needs a model? | Info key | +| --- | --- | --- | +| Live text / barcode / face / body / rectangle / animal | No | Camera | +| Image classification, hand-pose classification | Yes (Create ML) | Camera | +| Sound recognition | Optional custom model | Microphone | +| Activity classification | Yes (Create ML) | — | +| Sentiment / language / entities | No (ships with the OS) | — | + +The model-backed demos show the call site with a placeholder — drop in your own Create ML model and +point `modelURL` at `YourModel.urlOfModelInThisBundle`. Each model-backed example also shows the +`onError` handler so you can surface a load failure in the UI. + +> **Note:** The live camera and microphone don't run in the iOS Simulator — try these on a device. diff --git a/Package.swift b/Package.swift index 69d0856..4a24a20 100644 --- a/Package.swift +++ b/Package.swift @@ -5,6 +5,7 @@ import PackageDescription let package = Package( name: "PrototypeKit", + defaultLocalization: "en", platforms: [ .iOS(.v14), .macOS(.v13) diff --git a/README.md b/README.md index 3b9276e..c69a93a 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,14 @@ dependencies: [ > `.upToNextMinor(from: "0.1.0")` if you need stricter guarantees. See the > [CHANGELOG](CHANGELOG.md) for what's in each release. +## Documentation & examples 📚 + +- **API reference** — DocC documentation is published to GitHub Pages: + +- **Sample code** — the [`Examples/`](Examples) directory contains a runnable SwiftUI gallery + covering every feature; copy [`PrototypeKitExamples.swift`](Examples/PrototypeKitExamples.swift) + into an app to try them. + # Examples Here are a few basic examples you can use today. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..a4eb731 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,37 @@ +# Security Policy + +## Scope + +PrototypeKit is a SwiftUI toolkit for prototyping on-device machine-learning features. All inference +runs on-device using Apple's frameworks (Vision, Core ML, Sound Analysis, Natural Language, Core +Motion); PrototypeKit does not collect, store, or transmit user data, and it ships a +`PrivacyInfo.xcprivacy` manifest declaring no tracking and no data collection. + +Because PrototypeKit is intended for prototyping and idea validation, please review it before relying +on it in a production, safety-critical, or sensitive context. + +## Supported versions + +PrototypeKit is pre-1.0. Security fixes are made against the latest `master`; there is no long-term +support branch yet. Pin a released `0.x` version and update promptly when a fix ships. + +## Reporting a vulnerability + +Please **do not** open a public issue for security-sensitive reports. + +Instead, use GitHub's private vulnerability reporting: + +1. Go to the repository's **Security** tab → **Report a vulnerability** + (). +2. Describe the issue, the affected version/commit, and reproduction steps. + +If private reporting is unavailable, contact the maintainers privately rather than filing a public +issue. + +### What to expect + +- We aim to acknowledge a report within a few business days. +- We'll work with you on a fix and coordinate a disclosure timeline. +- With your permission, we're happy to credit you in the release notes. + +Thank you for helping keep PrototypeKit and the apps built with it safe. diff --git a/Sources/PrototypeKit/Camera/CameraViewController-iOS.swift b/Sources/PrototypeKit/Camera/CameraViewController-iOS.swift index 121468f..2f822f2 100644 --- a/Sources/PrototypeKit/Camera/CameraViewController-iOS.swift +++ b/Sources/PrototypeKit/Camera/CameraViewController-iOS.swift @@ -39,9 +39,9 @@ class CameraViewController: UIViewController { guard checkDeveloperHasConfiguredInfoPlist() else { presentCenteredMessage( icon: "⚠️", - text: "Camera unavailable: add the \"Privacy - Camera Usage Description\" " - + "(NSCameraUsageDescription) key to your app's Info settings. " - + "See the PrototypeKit setup guide.") + text: NSLocalizedString("camera.missingUsageDescription", + bundle: .module, + comment: "Shown on the camera preview when NSCameraUsageDescription is missing.")) return } #if targetEnvironment(simulator) @@ -70,7 +70,11 @@ class CameraViewController: UIViewController { func checkAndHandleSimulator() { #if targetEnvironment(simulator) - presentCenteredMessage(icon: "📸", text: "Live camera is not supported in previews.") + presentCenteredMessage( + icon: "📸", + text: NSLocalizedString("camera.unavailableInSimulator", + bundle: .module, + comment: "Shown in place of the camera preview in the Simulator and previews.")) #endif } diff --git a/Sources/PrototypeKit/PrototypeKit.docc/PrototypeKit.md b/Sources/PrototypeKit/PrototypeKit.docc/PrototypeKit.md index 87917cc..a4360a2 100644 --- a/Sources/PrototypeKit/PrototypeKit.docc/PrototypeKit.md +++ b/Sources/PrototypeKit/PrototypeKit.docc/PrototypeKit.md @@ -5,17 +5,24 @@ Prototype on-device machine learning apps with SwiftUI in just a few lines of co ## Overview PrototypeKit is a lightweight, zero-dependency wrapper around Apple's camera, Vision, -Core ML, and Sound Analysis frameworks. It exposes ready-made SwiftUI views and modifiers -so you can validate ideas quickly without writing AVFoundation and Vision boilerplate. +Core ML, Sound Analysis, Natural Language, and Core Motion frameworks. It exposes ready-made +SwiftUI views and modifiers so you can validate ideas quickly without writing AVFoundation and +Vision boilerplate. -> Important: PrototypeKit is a work in progress and is **not** intended for production use. +All inference runs on-device, and PrototypeKit ships a privacy manifest declaring no tracking and no +data collection. It is designed to fail gracefully — a missing model, denied permission, or +interrupted audio session degrades cleanly and is reported through the log and an optional +``PrototypeKitError`` handler, rather than crashing your app. + +> Important: PrototypeKit is a work in progress aimed at prototyping and idea validation. It does not +> yet publish stable, versioned APIs — pin a `0.x` release and expect changes before 1.0. ### Getting started 1. Add the package to your project (see the README for Swift Package Manager instructions). 2. Add the required Info properties for the features you use: - Camera-based views require `NSCameraUsageDescription` (Privacy - Camera Usage Description). - - ``SwiftUI/View/recognizeSounds(recognizedSound:configuration:)`` requires + - ``SwiftUI/View/recognizeSounds(recognizedSound:configuration:onError:)`` requires `NSMicrophoneUsageDescription` (Privacy - Microphone Usage Description). 3. Drop a view into your SwiftUI hierarchy: @@ -25,17 +32,23 @@ import PrototypeKit struct ContentView: View { @State private var latestPrediction = "" + @State private var errorMessage: String? var body: some View { VStack { ImageClassifierView(modelURL: FruitClassifier.urlOfModelInThisBundle, - latestPrediction: $latestPrediction) - Text(latestPrediction) + latestPrediction: $latestPrediction) { error in + errorMessage = error.localizedDescription + } + Text(errorMessage ?? latestPrediction) } } } ``` +See the [`Examples/`](https://github.com/FridayTechnologies/PrototypeKit/tree/master/Examples) +directory for a runnable gallery covering every feature. + ## Topics ### Camera @@ -56,11 +69,12 @@ struct ContentView: View { ### Sound +- ``SwiftUI/View/recognizeSounds(recognizedSound:configuration:onError:)`` - ``SoundAnalysisConfiguration`` ### Motion -- ``SwiftUI/View/classifyActivity(modelURL:configuration:latestActivity:)`` +- ``SwiftUI/View/classifyActivity(modelURL:configuration:latestActivity:onError:)`` - ``ActivityClassifierConfiguration`` ### Natural Language @@ -68,4 +82,7 @@ struct ContentView: View { - ``SwiftUI/View/analyzeSentiment(text:score:)`` - ``SwiftUI/View/identifyLanguage(text:language:)`` - ``SwiftUI/View/tagEntities(text:entities:)`` - + +### Error handling + +- ``PrototypeKitError`` diff --git a/Sources/PrototypeKit/en.lproj/Localizable.strings b/Sources/PrototypeKit/en.lproj/Localizable.strings new file mode 100644 index 0000000..61636de --- /dev/null +++ b/Sources/PrototypeKit/en.lproj/Localizable.strings @@ -0,0 +1,5 @@ +/* Shown on the camera preview when the app is missing the camera usage description. */ +"camera.missingUsageDescription" = "Camera unavailable: add the \"Privacy - Camera Usage Description\" (NSCameraUsageDescription) key to your app's Info settings. See the PrototypeKit setup guide."; + +/* Shown in place of the camera preview in the iOS Simulator and SwiftUI previews. */ +"camera.unavailableInSimulator" = "Live camera is not supported in previews.";