Skip to content

[nightshift] idea-generator: 8 improvement ideas for Ancla iOS #21

@Microck

Description

@Microck

Ancla — Creative Improvement Ideas Report

Generated: 2026-04-19
Scope: iOS codebase at ios/ — architecture, features, UX, testing, CI/CD, ecosystem
Repository: Microck/ancla (single commit 5889ef5)


Executive Summary

Ancla is a well-structured iOS app that combines Screen Time API (FamilyControls/ManagedSettings) with physical NFC stickers to create a hardware-enforced digital wellness lock. The architecture uses clean protocol-based dependency injection and a shared core Swift package. However, the codebase has a single 1,430-line god-object ViewModel (AppViewModel), no CI/CD pipeline, limited App Intents, and several missed opportunities to leverage its unique physical-digital lock position. Below are 8 concrete improvement ideas ranked by impact-to-effort ratio.


Idea 1: Decompose the God ViewModel into Domain-Coordinated Services

Severity: 🔴 High (architectural debt)
Estimated Effort: Large
Potential Impact: High

Problem

ios/ancla-app/app-view-model.swift (1,430 lines) handles all business logic: authorization, NFC pairing, session lifecycle, mode CRUD, schedule CRUD, preset CRUD, temporary unlock timer management, paragraph challenges, schedule sync, diagnostics, and persistence orchestration. Every view binds to a single @Bindable var viewModel: AppViewModel.

This creates:

  • Merge conflict magnet — any two developers working on features touch the same file
  • Test explosion — every test file imports the full dependency graph
  • Cognitive overload — 80+ var/func members on one type

Recommendation

Extract domain-specific coordinators behind protocols, each owning a slice of the AppSnapshot:

New Type Responsibility File
SessionCoordinator arm, release, emergency unbrick, paragraph challenge ancla-app/coordinators/session-coordinator.swift
ModeCoordinator CRUD for BlockMode, default selection ancla-app/coordinators/mode-coordinator.swift
ScheduleCoordinator CRUD for ScheduledSessionPlan, sync logic ancla-app/coordinators/schedule-coordinator.swift
AnchorCoordinator pair, rename, unpair NFC stickers ancla-app/coordinators/anchor-coordinator.swift
UnlockCoordinator presets, temporary unlock timer, re-shield ancla-app/coordinators/unlock-coordinator.swift

AppViewModel becomes a thin façade that holds the AppSnapshot and delegates to coordinators. Views receive only the coordinator they need.

Files touched:

  • ios/ancla-app/app-view-model.swift (refactor from 1,430 → ~200 lines)
  • New: ios/ancla-app/coordinators/*.swift (5 new files)
  • ios/ancla-app/content-view.swift (update bindings)
  • ios/ancla-app/lock-screen-view.swift (update bindings)
  • ios/ancla-tests/app-view-model-tests.swift (update imports/fakes)

Idea 2: Session Analytics Dashboard — "Your Anchor Report"

Severity: 🟡 Medium (feature gap)
Estimated Effort: Medium
Potential Impact: High

Problem

The app records rich SessionHistoryEntry data (mode name, anchor name, armed/released timestamps, release method) but only displays it as a flat "Recent" list (ios/ancla-app/content-view.swift, lines 499+). There's no aggregation: no weekly streak, no total hours anchored, no breakdown by mode, no visual chart. Users have no feedback loop to reinforce behavior change.

Recommendation

Add a read-only analytics view accessible from the home screen:

  1. Weekly/Monthly streak — consecutive days with at least one completed session
  2. Total anchored hours — sum of SessionHistoryEntry.duration over time windows
  3. Mode breakdown — pie chart or bar chart showing time per mode
  4. Release method histogram — anchor vs. emergency vs. paragraph challenge vs. schedule
  5. Prune old history — cap sessionHistory at 365 entries, archive older ones to a separate cold-store

Implementation approach:

  • New file: ios/ancla-app/analytics-view.swift (SwiftUI Charts)
  • New file: ios/ancla-shared/ancla-analytics.swift (pure functions for aggregation — testable in AnclaCore SPM target)
  • Extend AnclaCore with static func aggregatedStats(in snapshot:AppSnapshot, from:Date, to:Date) -> SessionStats
  • Add a new tab or section in ContentView

Files touched:

  • ios/ancla-shared/ancla-core.swift (add analytics functions)
  • New: ios/ancla-app/analytics-view.swift
  • New: ios/ancla-shared/ancla-analytics.swift
  • ios/ancla-app/content-view.swift (add navigation to analytics)
  • ios/ancla-core-tests/ancla-core-tests.swift (add analytics tests)
  • ios/ancla-shared/ancla-models.swift (add SessionStats model)

Idea 3: Live Activity & Lock Screen Widget for Active Sessions

Severity: 🟡 Medium (UX gap)
Estimated Effort: Medium
Potential Impact: High

Problem

When a session is armed, the user must open the Ancla app to see status or tap to unlock. There's no presence on the Lock Screen or Dynamic Island. This is a missed opportunity for the app's core value proposition — the lock screen is exactly where an "anchored" indicator belongs.

Recommendation

Add a ActivityKit Live Activity that displays:

  • Armed duration (elapsed timer since armedAt)
  • Active mode name
  • Quick-access "Scan Anchor" button (deep link into the app's NFC scan flow)
  • Ending state when released (shows total duration for 30 seconds)

Implementation:

  1. New target: AnclaLiveActivity (Widget Extension)
  2. Define AnchorActivityAttributes with AnchorActivityContentState (mode name, armed date)
  3. Start the Live Activity in AppViewModel.arm(mode:)
  4. End it in completeRelease()
  5. Read shared state from AppGroupStore (already shared via app group group.dev.micr.ancla)

Files touched:

  • New target: ios/ancla-live-activity/ (WidgetKit extension)
  • ios/project.yml (add target)
  • ios/ancla-app/app-view-model.swift (start/end Live Activity calls)
  • ios/ancla-shared/ancla-models.swift (add AnchorActivityAttributes)
  • ios/ancla-shared/ancla-store.swift (share activity state via app group)

Idea 4: Expand App Intents — "Arm Mode" & "Release Session" Shortcuts

Severity: 🟡 Medium (ecosystem gap)
Estimated Effort: Small
Potential Impact: Medium

Problem

ios/ancla-app/shortcuts-intents.swift currently exposes only one App Intent: CheckBlockStatusIntent (read-only boolean). The app's most valuable actions — arming a mode and releasing a session — are not available to Shortcuts automation. This means users cannot create automations like "When I arrive at work, arm Work mode" or "At bedtime, arm Sleep mode."

Recommendation

Add two new intents:

  1. ArmModeIntent — Takes a mode name as parameter, triggers NFC scan, arms the session. Returns success/failure.
  2. ReleaseActiveSessionIntent — Triggers NFC scan, releases the active session. Returns success/failure.
  3. ListModesIntent — Returns the list of saved mode names (for Shortcuts UI pickers).

Also expose these as Focus Filters so a "Work" Focus can auto-arm the Work mode.

Files touched:

  • ios/ancla-app/shortcuts-intents.swift (add 3 new intents)
  • ios/ancla-shared/ancla-dependencies.swift (ensure store access for intents)
  • ios/project.yml (update if needed for intent definitions)

Idea 5: NFC Tag Write-Once Challenge — Anti-Cheating Anchor Binding

Severity: 🟢 Low (security hardening)
Estimated Effort: Medium
Potential Impact: Medium

Problem

Currently, StickerPairingService (in ios/ancla-shared/ancla-services.swift) only reads the NFC tag UID and hashes it. Any NFC tag with the same UID can be substituted. A determined user could clone a tag UID or use a programmable NFC tag to bypass the physical lock. The app hashes the UID with SHA-256 (TagFingerprint.hash), but this only prevents casual duplication — not active bypass.

Recommendation

Implement a challenge-response protocol using NDEF or tag-specific commands:

  1. During pairing: Write a random 32-byte secret to the NFC tag's user memory (NDEF record or raw block). Store SHA256(uid + secret) as the uidHash.
  2. During arm/release: Read the secret back from the tag. Verify SHA256(scannedUID + scannedSecret) == storedUidHash.
  3. This makes tag cloning insufficient — the attacker needs both the UID and the stored secret.

Requires:

  • NFCTagReaderSession → connect to tag → read/write specific NDEF or data blocks
  • Only works with writable NFC tags (NTAG213/215/216 or MiFare DESFire), which is realistic for dedicated "anchor" stickers

Files touched:

  • ios/ancla-shared/ancla-services.swift (add write/read secret logic to StickerPairingService)
  • ios/ancla-shared/ancla-models.swift (extend PairedTag if needed)
  • ios/ancla-core-tests/ancla-core-tests.swift (verify hash logic)
  • ios/ancla-tests/app-view-model-tests.swift (update pairing test expectations)

Idea 6: CI/CD Pipeline with Xcode Cloud or GitHub Actions

Severity: 🔴 High (infrastructure gap)
Estimated Effort: Small
Potential Impact: Medium

Problem

There is no CI/CD configuration in the repository — no .github/workflows/, no xcode-cloud config, no Fastfile, no Makefile. The project uses XcodeGen (project.yml) but has no automated build/test pipeline. Core tests (AnclaCoreTests via Package.swift) could run on any Linux/macOS runner, but aren't wired up.

Recommendation

Add a two-tier CI pipeline:

Tier 1 — Core Tests (GitHub Actions, runs on every PR):

# .github/workflows/core-tests.yml
name: Core Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - run: cd ios && swift test

Tier 2 — Full Build + UI Tests (Xcode Cloud):

  • Add ciserver/xcode-cloud.yml or configure via Xcode Cloud dashboard
  • Build the full Ancla scheme
  • Run AnclaTests target
  • Archive and distribute to TestFlight on main branch merges

Also add a Makefile for common developer commands:

generate:  cd ios && xcodegen generate
test-core: cd ios && swift test
build:     xcodebuild -scheme Ancla -destination 'platform=iOS Simulator,name=iPhone 16'

Files touched:

  • New: .github/workflows/core-tests.yml
  • New: Makefile
  • Optional: ios/ciserver/xcode-cloud.yml

Idea 7: Context-Aware Shield Extension — Dynamic Messaging

Severity: 🟢 Low (UX polish)
Estimated Effort: Small
Potential Impact: Medium

Problem

ios/ancla-shield-extension/shield-configuration-extension.swift shows a static shield UI with just the mode name and anchor name. It reads the AppSnapshot every time, but doesn't use contextual information like:

  • How long the session has been active (motivational: "You've been focused for 2h 14m")
  • Scheduled end time (if from a schedule: "This session ends at 5:00 PM")
  • Temporary unlock cooldown (if one was just used: "Last unlock ended 3 min ago")
  • Custom per-mode messages (user could set a personal reminder like "You chose this")

Recommendation

Enhance ShieldConfigurationExtension.makeConfiguration(title:) to:

  1. Calculate elapsed time since armedAt and display in subtitle: "45 minutes anchored. Scan Desk anchor to release."
  2. If scheduledPlanID is set, look up the plan's endMinuteOfDay and show: "Scheduled until 5:00 PM."
  3. Add an optional motivationalMessage: String? field to BlockMode that users can customize per mode
  4. Show remaining emergency unbricks count when relevant

Files touched:

  • ios/ancla-shield-extension/shield-configuration-extension.swift (enhance makeConfiguration)
  • ios/ancla-shared/ancla-models.swift (add optional motivationalMessage to BlockMode)
  • ios/ancla-app/mode-editor-view.swift (add motivational message text field)

Idea 8: Anchor Proximity Awareness via Background NFC Tag Reading

Severity: 🟢 Low (advanced feature)
Estimated Effort: Large
Potential Impact: High

Problem

Currently, the user must open the Ancla app and explicitly scan NFC to arm or release. This creates friction. iOS supports background NFC tag reading — when the iPhone screen is on and the device detects an NFC tag, it can present a notification to open the associated app automatically via NDEF URI records.

Recommendation

Implement a two-phase NFC experience:

  1. NDEF URI on anchor tags: During pairing, write an NDEF record to the tag with a custom URL scheme like ancla://anchor/{tagId}. iOS will show a notification when the phone is near the anchor.
  2. Background tag reading: Register the URL scheme in Info.plist. When the user taps the iOS system NFC notification, Ancla opens directly to the NFC scan/release flow — reducing the steps from "unlock phone → find Ancla → tap scan" to just "tap the system notification."
  3. Future: CoreNFC background tag reading session — on supported hardware, detect the anchor tag without even opening the app, and surface a Lock Screen action button.

This is the app's killer differentiator: NFC stickers that the phone recognizes passively, not just active scan-to-unlock. It transforms Ancla from "app you open" to "physical environment that enforces digital boundaries."

Files touched:

  • ios/ancla-shared/ancla-services.swift (add NDEF write during pairing)
  • ios/ancla-app/Info.plist (register URL scheme)
  • New: ios/ancla-app/ancla-deep-link-handler.swift
  • ios/ancla-app/ancla-app.swift (handle incoming URL)
  • ios/ancla-lite/ancla-lite-support.swift (update LiteStickerPairingService for testing)

Priority Matrix

# Idea Effort Impact Priority
6 CI/CD Pipeline Small Medium P0 — Ship immediately
4 Expand App Intents Small Medium P0 — Quick win
7 Context-Aware Shield Extension Small Medium P1 — Next sprint
2 Session Analytics Dashboard Medium High P1 — High value feature
3 Live Activity & Lock Screen Widget Medium High P1 — Key UX improvement
1 Decompose God ViewModel Large High P2 — Technical debt
5 NFC Challenge-Response Protocol Medium Medium P2 — Security hardening
8 Anchor Proximity Awareness Large High P3 — Strategic bet

Quick Wins (< 1 day each)

These can be implemented immediately with minimal risk:

  1. Cap sessionHistory — Add pruning in AnclaCore.recordHistory() to keep only the last 365 entries. Prevents unbounded JSON growth in ancla-snapshot.json.
  2. Add swift-format configuration — The codebase is consistent but has no automated formatter. Add a .swift-format config and a git pre-commit hook.
  3. Error logginglastError is a plain String?. Add structured logging via os.Logger so crashes and errors are visible in Xcode Organizer.
  4. Localization preparation — All UI strings are hardcoded. Wrap in String(localized:) or NSLocalizedString for future i18n.
  5. Screenshot tests — The SwiftUI views (LockScreenView, ParagraphChallengeSheet) are perfect candidates for Xcode's screenshot testing to prevent visual regressions.

Architecture Strengths to Preserve

  • Protocol-based DI (AppSnapshotStore, Shielding, StickerPairing, AuthorizationClienting, ScheduleNotifying) — excellent testability
  • AnclaCore SPM package — pure Swift, no UIKit/SwiftUI dependency, runs on Linux
  • Dual build variants (full vs sideloadLite) via compilation conditions
  • Backward-compatible decoding (decodeIfPresent everywhere in AppSnapshot.init(from:))
  • InMemorySnapshotStore + FakeShieldingService + FakeStickerPairingService in tests — comprehensive fakes

End of report. Each idea includes specific file paths and is scoped to be actionable in a 1-4 week development cycle.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions