design(app-creator): S6 — forfatt ios-app referanse-domain-pack + start claude-code-plugin-pakke
- domain-packs/ios-app/: komplett referanse-pakke (8 komponenter) — pack.json med components-map, conventions.md, 4 patterns/, gotchas.md, checklist.md splittet i 3 (App Store-submission + security-privacy/MASVS 2.1 + accessibility/WCAG 2.2 AA), 3 scaffold/-maler (PrivacyInfo.xcprivacy-plist, NSUsageDescription-inventar, ASC-metadata), eksempel-feature-brief i Voyage strict-mode-format, glossary.md. Hver teknisk påstand verifisert mot Apple Developer / W3C WAI / OWASP MAS. - domain-packs/claude-code-plugin/: stub — pack.json + conventions.md + gotchas.md + checklist.md + glossary.md ferdige (ekstrahert fra ktg-privat-konvensjoner); patterns/scaffold/examples er stubs. - docs/domain-pack-spec.md: § D2/D3/D4/D7 + restrisiko oppdatert — pack.json components-felt, core/supplementary låst til manifestet, checklist-splitt-konvensjon, snapshot-materialiserings-mekanikk presisert, iOS-versjons-korreksjon (iOS 26, ikke "iOS 18/19"), D7 → "forfattet i S6". - prototype-run/friksjon.md: #9 (Akashic-briefen refererer "iOS 19" som ikke finnes — rettes i S7) + S6-prosessnotater (checklist-splitt bekreftet nødvendig, components-gap fylt, verifiserings-asymmetri ios-app vs claude-code-plugin notert). - CLAUDE.md: peker til domain-packs/ under § Status. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e4ae4ac5b4
commit
2011507ea1
27 changed files with 1059 additions and 17 deletions
|
|
@ -0,0 +1,36 @@
|
|||
<!-- domain-pack: ios-app · component: patterns · supplementary -->
|
||||
<!-- verified 2026-05-12 against Core Location / CLLocationManager docs. -->
|
||||
|
||||
# Pattern: current-location-basert beregning med regenerering & fallback
|
||||
|
||||
**Når:** appen beregner noe som avhenger av hvor brukeren er *nå* (soltider, tidssoner, lokale data) og må holde det oppdatert når brukeren flytter seg — uten å lagre lokasjonshistorikk.
|
||||
|
||||
## Form
|
||||
|
||||
- **Be om "When In Use"**, ikke "Always", med mindre bakgrunns-oppdatering er strengt nødvendig. Nøkkel: `NSLocationWhenInUseUsageDescription` (Always: `NSLocationAlwaysAndWhenInUseUsageDescription`). Forklar hvorfor på onboarding *før* prompten.
|
||||
- **Hent posisjon ved app-foreground** (`scenePhase == .active`): én `requestLocation()` (engangs) eller kort `startUpdatingLocation()` til en god fix, så stopp. Ikke kontinuerlig sporing.
|
||||
- **Regenerer avledede verdier** ved hver fix der posisjonen har endret seg vesentlig (terskel, f.eks. >tens of km, eller tidssone-bytte): re-kjør beregningen, re-planlegg avhengige lokale notifikasjoner (se `patterns/local-notifications.md`), oppdater widget-timeline.
|
||||
- **Cache siste kjente posisjon** (kun den ene verdien, ikke en logg) for bruk når en fersk fix ikke er tilgjengelig.
|
||||
|
||||
## Fallback-stige (når GPS mangler / permission nektet)
|
||||
|
||||
1. **Siste kjente posisjon** (cachet) — bruk den, vis at den er stale.
|
||||
2. **Manuell input** — la brukeren sette/velge sted (by, eller kart-pin) hvis appen er meningsløs uten posisjon.
|
||||
3. **Degradert modus** — vis hva som er mulig uten posisjon (generisk innhold), med tydelig CTA for å gi tilgang / sette sted.
|
||||
4. **Aldri krasj, aldri tom skjerm** uten forklaring. En app som bare viser "Location required" og ikke noe annet risikerer Guideline 4.2 / dårlig review.
|
||||
|
||||
## Personvern
|
||||
|
||||
- Posisjon brukes til beregning og forkastes — ikke lagret som historikk, ikke sendt noe sted. Reflekter dette i App Privacy Details (Location → App Functionality, *ikke* linket til identitet, ingen tracking).
|
||||
- Ingen `PrivacyInfo.xcprivacy`-required-reason for Core Location i seg selv, men *vær konsekvent* med nutrition-label-erklæringen.
|
||||
|
||||
## Fallgruver
|
||||
|
||||
- **Ikke** be om "Always" for noe "When In Use" dekker — review-friksjon + brukermistillit.
|
||||
- **Ikke** poll lokasjon i bakgrunnen "for sikkerhets skyld" — batteridrenering, og synlig i Settings → Privacy.
|
||||
- **Ikke** anta at en fix kommer raskt — ha en timeout og fallback-sti.
|
||||
- **Husk** tidssone: en lokasjonsendring kan også endre tidssonen; beregninger som bruker lokal tid må re-deriveres.
|
||||
|
||||
## App-brief-signal
|
||||
|
||||
"Lokasjons-bevisst", "current location", "regenererer ved app-foreground", "fallback ved nektet permission", "soltider / lokale tider".
|
||||
34
domain-packs/ios-app/patterns/local-notifications.md
Normal file
34
domain-packs/ios-app/patterns/local-notifications.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!-- domain-pack: ios-app · component: patterns · supplementary -->
|
||||
<!-- verified 2026-05-12 against UNUserNotificationCenter docs. -->
|
||||
|
||||
# Pattern: lokale notifikasjoner (UNUserNotificationCenter)
|
||||
|
||||
**Når:** appen må varsle brukeren om hendelser den selv vet om (tidsvinduer, påminnelser, milepæler) — uten server, uten push.
|
||||
|
||||
## Form
|
||||
|
||||
- **Autorisasjon ved kjøretid:** `UNUserNotificationCenter.shared().requestAuthorization(options: [.alert, .sound, .badge])`. **Ingen Info.plist-nøkkel kreves** for standard lokale notifikasjoner. ([Apple — UNUserNotificationCenter](https://developer.apple.com/documentation/usernotifications/unusernotificationcenter))
|
||||
- **Be om autorisasjon i kontekst** — ikke ved første launch. Forklar hvorfor på en onboarding-skjerm *før* prompten (App Store Review forventer dette; GDPR-vennlig).
|
||||
- **Planlegging:** `UNNotificationRequest` med `UNCalendarNotificationTrigger` (klokkeslett) eller `UNTimeIntervalNotificationTrigger`. iOS-grense: ~64 ventende lokale notifikasjoner per app — planlegg rullerende, ikke alt på en gang.
|
||||
- **Re-planlegg ved app-foreground** hvis tidene avhenger av tilstand som endrer seg (f.eks. lokasjons-avledede tider — se `patterns/current-location-regeneration.md`): fjern utdaterte (`removePendingNotificationRequests`) og planlegg nye.
|
||||
|
||||
## Interruption levels & Focus
|
||||
|
||||
- `UNNotificationInterruptionLevel`: `.passive`, `.active` (default), `.timeSensitive`, `.critical`.
|
||||
- **Focus mode respekterer nivåene:** `.timeSensitive` kan bryte gjennom de fleste Focus-filtre — krever entitlement (`com.apple.developer.usernotifications.time-sensitive`). `.critical` omgår mute/DND — krever Apple-godkjent entitlement, gis sjelden. For en vanlig app: `.active` eller `.timeSensitive` (med entitlement) hvis varselet er reelt tidskritisk.
|
||||
- Ingen App Store-entitlement kreves for å *planlegge* standard lokale notifikasjoner — kun for time-sensitive/critical-nivåene.
|
||||
|
||||
## Handling av tap
|
||||
|
||||
- `UNUserNotificationCenterDelegate` — `didReceive response` for å reagere på trykk (deep-link til riktig skjerm), `willPresent` for visning mens appen er i forgrunnen.
|
||||
- Hvis et trykk skal endre tilstand ("marker gjort"): bruk `UNNotificationAction` for in-notification-handling der mulig, ellers åpne appen til riktig kontekst.
|
||||
|
||||
## Fallgruver
|
||||
|
||||
- **Ikke** anta autorisasjon — sjekk `getNotificationSettings` og degrader nådig (in-app-klokke/varsler) hvis nektet.
|
||||
- **Ikke** spam — én varsel per hendelse, av/på per kategori.
|
||||
- **Ikke** glem å re-planlegge når underliggende data endres; gamle notifikasjoner som fyrer på feil tid er en synlig bug.
|
||||
|
||||
## App-brief-signal
|
||||
|
||||
"Påminn brukeren", "varsel når X", "respekterer Focus mode", "av/på per varseltype".
|
||||
33
domain-packs/ios-app/patterns/offline-first-swiftdata.md
Normal file
33
domain-packs/ios-app/patterns/offline-first-swiftdata.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<!-- domain-pack: ios-app · component: patterns · supplementary -->
|
||||
<!-- verified 2026-05-12 against SwiftData docs (iOS 17+). -->
|
||||
|
||||
# Pattern: offline-first med SwiftData
|
||||
|
||||
**Når:** appen eier sine data lokalt, ingen backend (eller backend kun som sync-tillegg). Den enkleste personvern-posisjonen og det riktige valget for små single-purpose-apper.
|
||||
|
||||
## Form
|
||||
|
||||
- **`@Model`-klasser** definerer skjemaet. Ingen `NSManagedObject`, ingen `.xcdatamodeld`.
|
||||
- **`ModelContainer`** opprettes ved app-start; `ModelContext` injiseres i view-treet via `.modelContainer(...)`.
|
||||
- **`@Query`** i views for reaktiv henting; muter via `modelContext.insert/delete` + (auto)`save`.
|
||||
- **Migrasjoner:** `SchemaMigrationPlan` med versjonerte `VersionedSchema`-er. Planlegg fra dag 1 — skjemaet *vil* endre seg.
|
||||
|
||||
## Offline-first-disiplin
|
||||
|
||||
- All lesning/skriving går mot lokal store; ingen view venter på nett.
|
||||
- Hvis sync legges til senere: lokal store er sannheten, sync er en bakgrunnsoppgave som reconciler — ikke omvendt. Konflikt-strategi (last-write-wins / per-felt-merge) er en arkitektur-beslutning (fase 3-ADR).
|
||||
- Backup: SwiftData-store ligger i app-containeren og dekkes av iCloud-enhets-backup automatisk. Eksplisitt iCloud-sync krever CloudKit-integrasjon (`ModelConfiguration(... , cloudKitDatabase: .automatic)`) — en bevisst tilleggsbeslutning, ikke default.
|
||||
|
||||
## Deling med widget / Live Activity
|
||||
|
||||
Store må ligge i en **App Group**-container for å være synlig for extensions. Se `patterns/widget-live-activities-shared-model.md` — dette pattern-et og det henger sammen for enhver app med widgets.
|
||||
|
||||
## Fallgruver
|
||||
|
||||
- **Ikke** anta at endringer i app-prosessen er øyeblikkelig synlige i widget-prosessen — refresh-timing er mer pålitelig på iOS 18+ enn iOS 17. ([Hacking with Swift — SwiftData i widgets](https://www.hackingwithswift.com/quick-start/swiftdata/how-to-access-a-swiftdata-container-from-widgets))
|
||||
- **Ikke** behold en stor `ModelContainer` i minne i en widget-extension uten grunn — extension-minnebudsjettet er stramt.
|
||||
- **Ikke** hopp over migrasjonsplanen "fordi appen er liten" — en uplanlagt skjemaendring etter lansering kan kreve destruktiv migrasjon.
|
||||
|
||||
## App-brief-signal som trigger dette pattern-et
|
||||
|
||||
"Ingen backend / all data lokal", "fungerer offline", "personvern-bevarende", "én bruker, egen enhet".
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<!-- domain-pack: ios-app · component: patterns · supplementary -->
|
||||
<!-- verified 2026-05-12 against WidgetKit / ActivityKit docs (Live Activities iOS 16.1+). -->
|
||||
|
||||
# Pattern: app + widget + Live Activity deler én datamodell
|
||||
|
||||
**Når:** funksjonalitet vises på hjemskjerm-widget, låseskjerm og/eller Dynamic Island — som *presentasjonslag på samme kjerne-data*, ikke fire uavhengige delsystemer.
|
||||
|
||||
## Arkitektur-prinsipp
|
||||
|
||||
Modeller én gang. App, widget-extension og Live Activity leser **samme delte tilstand** via en **App Group**-container. Widgeten og Live Activity-en eier *ikke* egen kopi av forretningslogikken — de rendrer et snapshot.
|
||||
|
||||
## Mekanikk
|
||||
|
||||
- **App Group:** legg til App Group-capability (`group.<bundle-id>`) på *både* app-target og widget-extension-target.
|
||||
- **Delt SwiftData-store:** `ModelConfiguration(... , groupContainer: .identifier("group.<bundle-id>"))` → samme `ModelContainer` i begge targets. Widgeten legger `.modelContainer(...)` på sin `WidgetConfiguration`. ([Hacking with Swift — SwiftData i widgets](https://www.hackingwithswift.com/quick-start/swiftdata/how-to-access-a-swiftdata-container-from-widgets)) Alternativ for enkle verdier: delt `UserDefaults(suiteName:)`.
|
||||
- **Widget-refresh:** `WidgetCenter.shared.reloadTimelines(ofKind:)` fra appen når data endres; ellers timeline-baserte oppdateringer. Vær konservativ — WidgetKit-refresh-budsjettet er begrenset.
|
||||
- **Live Activities (iOS 16.1+):** ActivityKit + en WidgetKit-extension. Krever `NSSupportsLiveActivities = YES` i **hoved-app-ens** Info.plist (ikke extension-ens). Vises på låseskjerm + Dynamic Island (Dynamic Island: iPhone 14 Pro+ / standard på iPhone 15+; eldre enheter får bare låseskjerm-presentasjonen). `ActivityAttributes` definerer statisk + dynamisk innhold; oppdater via `activity.update(...)`, avslutt via `activity.end(...)`. ([Live Activities — GitHub iOS16-Live-Activities](https://github.com/1998code/iOS16-Live-Activities))
|
||||
- **Opt-in:** Live Activities / Lock Screen-features bør være noe brukeren slår på, ikke noe som dukker opp uoppfordret.
|
||||
|
||||
## Fallgruver
|
||||
|
||||
- **Ikke** dupliser forretningslogikk inn i extension — beregn i appen / et delt framework, del bare resultatet.
|
||||
- **Ikke** anta sanntids-synk app↔extension; oppdateringstiming er strammere på iOS 17 enn iOS 18+.
|
||||
- **Ikke** overskrid extension-minnebudsjettet (≈30 MB-klassen) — hold widget/Live-Activity-koden lett.
|
||||
- **Glem ikke** `NSSupportsLiveActivities` — uten den fungerer ikke Live Activities, og det feiler stille.
|
||||
|
||||
## App-brief-signal
|
||||
|
||||
"Widget på hjemskjerm", "Live Activities / låseskjerm-countdown", "samme kjerne-datamodell", "Dynamic Island".
|
||||
Loading…
Add table
Add a link
Reference in a new issue