Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions