@mmerterden/multi-agent-pipeline 20.36.0 → 20.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/docs/facts.json +1 -1
- package/install/catalog-history.json +1 -1
- package/manifest.json +194 -122
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/SKILL.md +33 -154
- package/pipeline/commands/multi-agent/analysis/SKILL.md +14 -68
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +5 -8
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +38 -109
- package/pipeline/commands/multi-agent/channels/SKILL.md +38 -167
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +37 -144
- package/pipeline/commands/multi-agent/design-check/SKILL.md +27 -131
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +22 -48
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +37 -180
- package/pipeline/commands/multi-agent/jira/SKILL.md +16 -46
- package/pipeline/commands/multi-agent/refactor/SKILL.md +23 -184
- package/pipeline/commands/multi-agent/resume/SKILL.md +30 -136
- package/pipeline/commands/multi-agent/review/SKILL.md +24 -199
- package/pipeline/commands/multi-agent/setup/SKILL.md +25 -181
- package/pipeline/commands/multi-agent/stack/SKILL.md +9 -53
- package/pipeline/commands/multi-agent/status/SKILL.md +13 -2
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +27 -226
- package/pipeline/commands/multi-agent/sync/SKILL.md +35 -164
- package/pipeline/commands/multi-agent/test-gen/SKILL.md +2 -4
- package/pipeline/commands/multi-agent/update/SKILL.md +18 -60
- package/pipeline/contract/CHANGELOG.md +9 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/fixtures/runs-awaiting-question.json +1 -1
- package/pipeline/contract/fixtures/runs-empty.json +1 -1
- package/pipeline/contract/fixtures/runs-failed.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
- package/pipeline/contract/fixtures/runs-running.json +1 -1
- package/pipeline/contract/manifest.json +5 -5
- package/pipeline/contract/types/index.d.ts +3 -2
- package/pipeline/multi-agent-refs/autopilot-on/details.md +148 -0
- package/pipeline/multi-agent-refs/channels/generator.md +14 -0
- package/pipeline/multi-agent-refs/channels/preview.md +13 -0
- package/pipeline/multi-agent-refs/channels/reference.md +46 -0
- package/pipeline/multi-agent-refs/complaint-analysis/phases.md +159 -0
- package/pipeline/multi-agent-refs/design-check/export.md +1 -0
- package/pipeline/multi-agent-refs/design-check/init-inventory.md +1 -1
- package/pipeline/multi-agent-refs/design-check/scope-and-ledger.md +64 -0
- package/pipeline/multi-agent-refs/features/analysis-command-notes.md +53 -0
- package/pipeline/multi-agent-refs/garbage-collect/phases.md +60 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/plan.md +71 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/resolve-and-scan.md +135 -0
- package/pipeline/multi-agent-refs/jira/picker-detail.md +71 -0
- package/pipeline/multi-agent-refs/orchestrator/copilot-pipeline.md +88 -0
- package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +96 -1
- package/pipeline/multi-agent-refs/refactor/sweep.md +190 -0
- package/pipeline/multi-agent-refs/resume/details.md +115 -2
- package/pipeline/multi-agent-refs/review/inputs.md +33 -1
- package/pipeline/multi-agent-refs/review/module-guides.md +9 -1
- package/pipeline/multi-agent-refs/review/panel.md +75 -0
- package/pipeline/multi-agent-refs/review/valven.md +5 -1
- package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +4 -0
- package/pipeline/multi-agent-refs/setup/token-save-flow.md +13 -0
- package/pipeline/multi-agent-refs/stack/background.md +72 -0
- package/pipeline/multi-agent-refs/store-ready/gates.md +139 -0
- package/pipeline/multi-agent-refs/store-ready/report.md +40 -2
- package/pipeline/multi-agent-refs/sync/office.md +10 -0
- package/pipeline/multi-agent-refs/sync/overview.md +61 -0
- package/pipeline/multi-agent-refs/sync/repo.md +91 -0
- package/pipeline/multi-agent-refs/update/details.md +83 -0
- package/pipeline/schemas/runs-index.schema.json +2 -1
- package/pipeline/scripts/contract-server.mjs +1 -1
- package/pipeline/scripts/runs-index.mjs +34 -7
- package/pipeline/scripts/status-overview.mjs +401 -0
- package/pipeline/skills/.skill-manifest.json +63 -63
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +7 -70
- package/pipeline/skills/shared/core/apple-archive-compliance/references/examples.md +67 -0
- package/pipeline/skills/shared/core/apple-archive-compliance/references/version-history.md +8 -0
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +10 -72
- package/pipeline/skills/shared/core/google-play-compliance/references/examples.md +69 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/notes.md +7 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/version-history.md +5 -0
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +39 -262
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +35 -73
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +37 -337
- package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +27 -122
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +8 -340
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +37 -180
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +23 -184
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +14 -82
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +25 -277
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +13 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +73 -217
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +11 -162
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-code.md +257 -0
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +7 -58
- package/pipeline/skills/shared/external/android-architecture/references/code-examples.md +68 -0
- package/pipeline/skills/shared/external/app-intents/SKILL.md +3 -45
- package/pipeline/skills/shared/external/app-intents/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +5 -81
- package/pipeline/skills/shared/external/app-store-optimization/references/engagement-and-localization.md +96 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +6 -112
- package/pipeline/skills/shared/external/app-store-review/references/monetization-and-submission.md +138 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +10 -117
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md +186 -0
- package/pipeline/skills/shared/external/authentication/SKILL.md +3 -50
- package/pipeline/skills/shared/external/authentication/references/code-examples.md +59 -0
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +3 -84
- package/pipeline/skills/shared/external/callkit-voip/references/code-examples.md +93 -0
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +7 -263
- package/pipeline/skills/shared/external/ci-cd-pipelines/references/pipeline-examples.md +281 -0
- package/pipeline/skills/shared/external/clean-code/SKILL.md +8 -41
- package/pipeline/skills/shared/external/clean-code/references/code-examples.md +56 -0
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +8 -146
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-code.md +214 -0
- package/pipeline/skills/shared/external/compose-components/SKILL.md +6 -80
- package/pipeline/skills/shared/external/compose-components/references/code-examples.md +89 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +5 -64
- package/pipeline/skills/shared/external/compose-navigation/references/code-examples.md +70 -0
- package/pipeline/skills/shared/external/core-data/SKILL.md +9 -157
- package/pipeline/skills/shared/external/core-data/references/core-data-code.md +221 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +3 -89
- package/pipeline/skills/shared/external/core-nfc/references/code-examples.md +98 -0
- package/pipeline/skills/shared/external/coreml/SKILL.md +17 -133
- package/pipeline/skills/shared/external/coreml/references/coreml-code.md +208 -0
- package/pipeline/skills/shared/external/css-modern/SKILL.md +8 -131
- package/pipeline/skills/shared/external/css-modern/references/code-examples.md +142 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +6 -103
- package/pipeline/skills/shared/external/database-patterns/references/orm-examples.md +112 -0
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +7 -48
- package/pipeline/skills/shared/external/html-semantic/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +6 -19
- package/pipeline/skills/shared/external/humanizer/references/voice.md +24 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +13 -170
- package/pipeline/skills/shared/external/ios-accessibility/references/assistive-tech-review.md +224 -0
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +2 -24
- package/pipeline/skills/shared/external/ios-localization/references/code-examples.md +30 -0
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +3 -58
- package/pipeline/skills/shared/external/ios-networking/references/code-examples.md +67 -0
- package/pipeline/skills/shared/external/ios-security/SKILL.md +4 -64
- package/pipeline/skills/shared/external/ios-security/references/code-examples.md +74 -0
- package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -90
- package/pipeline/skills/shared/external/live-activities/references/code-examples.md +108 -0
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -23
- package/pipeline/skills/shared/external/localization-reuse-map/references/project-settings.md +22 -0
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +5 -95
- package/pipeline/skills/shared/external/mapkit-location/references/code-examples.md +117 -0
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +7 -106
- package/pipeline/skills/shared/external/nextjs-app-router/references/app-router-examples.md +124 -0
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -31
- package/pipeline/skills/shared/external/permissionkit/references/code-examples.md +37 -0
- package/pipeline/skills/shared/external/play-store-review/SKILL.md +6 -74
- package/pipeline/skills/shared/external/play-store-review/references/signing-and-policies.md +83 -0
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -125
- package/pipeline/skills/shared/external/push-notifications/references/notification-code.md +176 -0
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +7 -158
- package/pipeline/skills/shared/external/python-patterns/references/python-examples.md +176 -0
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +7 -157
- package/pipeline/skills/shared/external/rest-api-design/references/api-examples.md +175 -0
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +11 -230
- package/pipeline/skills/shared/external/swift-architecture/references/pattern-examples.md +295 -0
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +17 -149
- package/pipeline/skills/shared/external/swift-codable/references/codable-recipes.md +207 -0
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +12 -99
- package/pipeline/skills/shared/external/swift-concurrency/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-additions.md +18 -0
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +9 -78
- package/pipeline/skills/shared/external/swift-formatstyle/references/formatstyle-examples.md +113 -0
- package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -56
- package/pipeline/skills/shared/external/swift-language/references/code-examples.md +65 -0
- package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +1 -0
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +6 -122
- package/pipeline/skills/shared/external/swiftdata/references/code-examples.md +140 -0
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +12 -139
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/interop-code.md +198 -0
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +9 -81
- package/pipeline/skills/shared/external/tailwind-css/references/code-examples.md +91 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +7 -207
- package/pipeline/skills/shared/external/testing-backend/references/testing-examples.md +225 -0
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +8 -89
- package/pipeline/skills/shared/external/typescript-patterns/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -20
- package/pipeline/skills/shared/external/vision-framework/references/code-examples.md +26 -0
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +7 -169
- package/pipeline/skills/shared/external/vue-composition/references/vue-examples.md +187 -0
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +7 -174
- package/pipeline/skills/shared/external/web-accessibility/references/a11y-examples.md +192 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +8 -66
- package/pipeline/skills/shared/external/web-performance/references/code-examples.md +77 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +11 -186
- package/pipeline/skills/shared/external/web-testing/references/testing-examples.md +213 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/format-and-output.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/publish-and-snapshot.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/sources-and-recipes.md +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# MapKit and Core Location Code Examples
|
|
2
|
+
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [SwiftUI Map (iOS 17+)](#swiftui-map-ios-17)
|
|
5
|
+
- [Selection](#selection)
|
|
6
|
+
- [CLLocationUpdate.liveUpdates() (iOS 17+)](#cllocationupdateliveupdates-ios-17)
|
|
7
|
+
- [Authorization](#authorization)
|
|
8
|
+
<!-- /toc -->
|
|
9
|
+
|
|
10
|
+
Full code samples for the SKILL.md sections that link here. The rules and the decision guidance stay in SKILL.md; each heading below matches the SKILL.md section the code belongs to.
|
|
11
|
+
|
|
12
|
+
## SwiftUI Map (iOS 17+)
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
import SwiftUI
|
|
16
|
+
import MapKit
|
|
17
|
+
|
|
18
|
+
struct TrailheadMap: View {
|
|
19
|
+
@State private var camera: MapCameraPosition = .automatic
|
|
20
|
+
|
|
21
|
+
private let lot = CLLocationCoordinate2D(latitude: 46.852, longitude: -121.760)
|
|
22
|
+
private let summit = CLLocationCoordinate2D(latitude: 46.853, longitude: -121.727)
|
|
23
|
+
|
|
24
|
+
var body: some View {
|
|
25
|
+
Map(position: $camera) {
|
|
26
|
+
Marker("Parking", coordinate: lot)
|
|
27
|
+
Marker("Summit", systemImage: "mountain.2.fill", coordinate: summit)
|
|
28
|
+
}
|
|
29
|
+
.mapStyle(.standard(elevation: .realistic))
|
|
30
|
+
.mapControls {
|
|
31
|
+
MapUserLocationButton()
|
|
32
|
+
MapCompass()
|
|
33
|
+
MapScaleView()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Selection
|
|
40
|
+
|
|
41
|
+
```swift
|
|
42
|
+
struct CampsiteMap: View {
|
|
43
|
+
let sites: [Campsite]
|
|
44
|
+
@State private var chosen: MKMapItem?
|
|
45
|
+
@State private var headline = ""
|
|
46
|
+
|
|
47
|
+
var body: some View {
|
|
48
|
+
Map(selection: $chosen) {
|
|
49
|
+
ForEach(sites) { site in
|
|
50
|
+
Marker(site.name, coordinate: site.coordinate)
|
|
51
|
+
.tag(site.mapItem)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
.onChange(of: chosen) { _, item in
|
|
55
|
+
guard let item else { return }
|
|
56
|
+
headline = item.name ?? ""
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## CLLocationUpdate.liveUpdates() (iOS 17+)
|
|
63
|
+
|
|
64
|
+
```swift
|
|
65
|
+
import CoreLocation
|
|
66
|
+
import Observation
|
|
67
|
+
|
|
68
|
+
@MainActor
|
|
69
|
+
@Observable
|
|
70
|
+
final class RunTracker {
|
|
71
|
+
private(set) var lastFix: CLLocation?
|
|
72
|
+
private var stream: Task<Void, Never>?
|
|
73
|
+
|
|
74
|
+
func begin() {
|
|
75
|
+
stream?.cancel()
|
|
76
|
+
stream = Task {
|
|
77
|
+
do {
|
|
78
|
+
for try await reading in CLLocationUpdate.liveUpdates() {
|
|
79
|
+
guard let fix = reading.location,
|
|
80
|
+
fix.horizontalAccuracy >= 0,
|
|
81
|
+
fix.horizontalAccuracy < 50 else { continue }
|
|
82
|
+
lastFix = fix
|
|
83
|
+
}
|
|
84
|
+
} catch is CancellationError {
|
|
85
|
+
} catch {
|
|
86
|
+
lastFix = nil
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func end() {
|
|
92
|
+
stream?.cancel()
|
|
93
|
+
stream = nil
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Authorization
|
|
99
|
+
|
|
100
|
+
```swift
|
|
101
|
+
struct LocationOffView: View {
|
|
102
|
+
@Environment(\.openURL) private var openURL
|
|
103
|
+
|
|
104
|
+
var body: some View {
|
|
105
|
+
ContentUnavailableView {
|
|
106
|
+
Label("Location is off", systemImage: "location.slash")
|
|
107
|
+
} description: {
|
|
108
|
+
Text("Allow location access to see trails near you.")
|
|
109
|
+
} actions: {
|
|
110
|
+
Button("Open Settings") {
|
|
111
|
+
guard let settings = URL(string: UIApplication.openSettingsURLString) else { return }
|
|
112
|
+
openURL(settings)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
@@ -98,69 +98,7 @@ export function SearchBar() {
|
|
|
98
98
|
|
|
99
99
|
## Server Actions
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
// app/actions.ts
|
|
103
|
-
'use server';
|
|
104
|
-
|
|
105
|
-
import { revalidatePath } from 'next/cache';
|
|
106
|
-
import { redirect } from 'next/navigation';
|
|
107
|
-
import { z } from 'zod';
|
|
108
|
-
|
|
109
|
-
const CreatePostSchema = z.object({
|
|
110
|
-
title: z.string().min(1).max(200),
|
|
111
|
-
content: z.string().min(1),
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
export async function createPost(prevState: ActionState, formData: FormData) {
|
|
115
|
-
const validated = CreatePostSchema.safeParse({
|
|
116
|
-
title: formData.get('title'),
|
|
117
|
-
content: formData.get('content'),
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
if (!validated.success) {
|
|
121
|
-
return { errors: validated.error.flatten().fieldErrors };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
await db.post.create({ data: validated.data });
|
|
126
|
-
} catch (error) {
|
|
127
|
-
return { message: 'Database Error: Failed to create post.' };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
revalidatePath('/posts');
|
|
131
|
-
redirect('/posts');
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
```tsx
|
|
136
|
-
// app/posts/new/page.tsx
|
|
137
|
-
'use client';
|
|
138
|
-
|
|
139
|
-
import { useActionState } from 'react';
|
|
140
|
-
import { createPost } from '@/app/actions';
|
|
141
|
-
|
|
142
|
-
export default function NewPostPage() {
|
|
143
|
-
const [state, formAction, isPending] = useActionState(createPost, { errors: {} });
|
|
144
|
-
|
|
145
|
-
return (
|
|
146
|
-
<form action={formAction}>
|
|
147
|
-
<div>
|
|
148
|
-
<label htmlFor="title">Title</label>
|
|
149
|
-
<input id="title" name="title" />
|
|
150
|
-
{state.errors?.title && <p className="error">{state.errors.title}</p>}
|
|
151
|
-
</div>
|
|
152
|
-
<div>
|
|
153
|
-
<label htmlFor="content">Content</label>
|
|
154
|
-
<textarea id="content" name="content" />
|
|
155
|
-
{state.errors?.content && <p className="error">{state.errors.content}</p>}
|
|
156
|
-
</div>
|
|
157
|
-
<button type="submit" disabled={isPending}>
|
|
158
|
-
{isPending ? 'Creating...' : 'Create Post'}
|
|
159
|
-
</button>
|
|
160
|
-
</form>
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
```
|
|
101
|
+
Code for this section: [App Router examples](references/app-router-examples.md).
|
|
164
102
|
|
|
165
103
|
## Data Fetching Patterns
|
|
166
104
|
|
|
@@ -293,52 +231,11 @@ export default function Error({
|
|
|
293
231
|
|
|
294
232
|
## Middleware
|
|
295
233
|
|
|
296
|
-
|
|
297
|
-
// middleware.ts (project root)
|
|
298
|
-
import { NextResponse } from 'next/server';
|
|
299
|
-
import type { NextRequest } from 'next/server';
|
|
300
|
-
|
|
301
|
-
export function middleware(request: NextRequest) {
|
|
302
|
-
const token = request.cookies.get('session')?.value;
|
|
303
|
-
|
|
304
|
-
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
305
|
-
return NextResponse.redirect(new URL('/login', request.url));
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const response = NextResponse.next();
|
|
309
|
-
response.headers.set('x-request-id', crypto.randomUUID());
|
|
310
|
-
return response;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export const config = {
|
|
314
|
-
matcher: ['/dashboard/:path*', '/api/:path*'],
|
|
315
|
-
};
|
|
316
|
-
```
|
|
234
|
+
Code for this section: [App Router examples](references/app-router-examples.md).
|
|
317
235
|
|
|
318
236
|
## Route Handlers (API Routes)
|
|
319
237
|
|
|
320
|
-
|
|
321
|
-
// app/api/users/route.ts
|
|
322
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
323
|
-
|
|
324
|
-
export async function GET(request: NextRequest) {
|
|
325
|
-
const searchParams = request.nextUrl.searchParams;
|
|
326
|
-
const page = parseInt(searchParams.get('page') ?? '1');
|
|
327
|
-
|
|
328
|
-
const users = await db.user.findMany({
|
|
329
|
-
skip: (page - 1) * 10,
|
|
330
|
-
take: 10,
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
return NextResponse.json(users);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export async function POST(request: NextRequest) {
|
|
337
|
-
const body = await request.json();
|
|
338
|
-
const user = await db.user.create({ data: body });
|
|
339
|
-
return NextResponse.json(user, { status: 201 });
|
|
340
|
-
}
|
|
341
|
-
```
|
|
238
|
+
Code for this section: [App Router examples](references/app-router-examples.md).
|
|
342
239
|
|
|
343
240
|
## Rendering Strategies
|
|
344
241
|
|
|
@@ -419,3 +316,7 @@ export default async function ProfilePage() {
|
|
|
419
316
|
| Hydration errors | Server/client HTML mismatch | Check for browser-only APIs; use `useEffect` for client-only logic |
|
|
420
317
|
| Dynamic route 404 | Missing `generateStaticParams` | Add params generation or set `dynamicParams = true` |
|
|
421
318
|
| Server Action fails | Not marked with 'use server' | Add 'use server' at top of file or inline function |
|
|
319
|
+
|
|
320
|
+
## References
|
|
321
|
+
|
|
322
|
+
- [App Router examples](references/app-router-examples.md): open when writing a Server Action, middleware or a route handler.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# App Router examples
|
|
2
|
+
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [Server Actions](#server-actions)
|
|
5
|
+
- [Middleware](#middleware)
|
|
6
|
+
- [Route Handlers (API Routes)](#route-handlers-api-routes)
|
|
7
|
+
<!-- /toc -->
|
|
8
|
+
|
|
9
|
+
Code for Server Actions, middleware and route handlers. SKILL.md keeps the component model, data fetching, rendering strategies and rules.
|
|
10
|
+
|
|
11
|
+
## Server Actions
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
// app/actions.ts
|
|
15
|
+
'use server';
|
|
16
|
+
|
|
17
|
+
import { revalidatePath } from 'next/cache';
|
|
18
|
+
import { redirect } from 'next/navigation';
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
|
|
21
|
+
const CreatePostSchema = z.object({
|
|
22
|
+
title: z.string().min(1).max(200),
|
|
23
|
+
content: z.string().min(1),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export async function createPost(prevState: ActionState, formData: FormData) {
|
|
27
|
+
const validated = CreatePostSchema.safeParse({
|
|
28
|
+
title: formData.get('title'),
|
|
29
|
+
content: formData.get('content'),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!validated.success) {
|
|
33
|
+
return { errors: validated.error.flatten().fieldErrors };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await db.post.create({ data: validated.data });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return { message: 'Database Error: Failed to create post.' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
revalidatePath('/posts');
|
|
43
|
+
redirect('/posts');
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
// app/posts/new/page.tsx
|
|
49
|
+
'use client';
|
|
50
|
+
|
|
51
|
+
import { useActionState } from 'react';
|
|
52
|
+
import { createPost } from '@/app/actions';
|
|
53
|
+
|
|
54
|
+
export default function NewPostPage() {
|
|
55
|
+
const [state, formAction, isPending] = useActionState(createPost, { errors: {} });
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<form action={formAction}>
|
|
59
|
+
<div>
|
|
60
|
+
<label htmlFor="title">Title</label>
|
|
61
|
+
<input id="title" name="title" />
|
|
62
|
+
{state.errors?.title && <p className="error">{state.errors.title}</p>}
|
|
63
|
+
</div>
|
|
64
|
+
<div>
|
|
65
|
+
<label htmlFor="content">Content</label>
|
|
66
|
+
<textarea id="content" name="content" />
|
|
67
|
+
{state.errors?.content && <p className="error">{state.errors.content}</p>}
|
|
68
|
+
</div>
|
|
69
|
+
<button type="submit" disabled={isPending}>
|
|
70
|
+
{isPending ? 'Creating...' : 'Create Post'}
|
|
71
|
+
</button>
|
|
72
|
+
</form>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Middleware
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// middleware.ts (project root)
|
|
81
|
+
import { NextResponse } from 'next/server';
|
|
82
|
+
import type { NextRequest } from 'next/server';
|
|
83
|
+
|
|
84
|
+
export function middleware(request: NextRequest) {
|
|
85
|
+
const token = request.cookies.get('session')?.value;
|
|
86
|
+
|
|
87
|
+
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
88
|
+
return NextResponse.redirect(new URL('/login', request.url));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const response = NextResponse.next();
|
|
92
|
+
response.headers.set('x-request-id', crypto.randomUUID());
|
|
93
|
+
return response;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const config = {
|
|
97
|
+
matcher: ['/dashboard/:path*', '/api/:path*'],
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Route Handlers (API Routes)
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
// app/api/users/route.ts
|
|
105
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
106
|
+
|
|
107
|
+
export async function GET(request: NextRequest) {
|
|
108
|
+
const searchParams = request.nextUrl.searchParams;
|
|
109
|
+
const page = parseInt(searchParams.get('page') ?? '1');
|
|
110
|
+
|
|
111
|
+
const users = await db.user.findMany({
|
|
112
|
+
skip: (page - 1) * 10,
|
|
113
|
+
take: 10,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return NextResponse.json(users);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function POST(request: NextRequest) {
|
|
120
|
+
const body = await request.json();
|
|
121
|
+
const user = await db.user.create({ data: body });
|
|
122
|
+
return NextResponse.json(user, { status: 201 });
|
|
123
|
+
}
|
|
124
|
+
```
|
|
@@ -245,37 +245,7 @@ both. When the question carries an `expirationDate`, use that date for the
|
|
|
245
245
|
local expiry so the app and the parent's window close together. When a flow combines known-handle checks with responses, keep the
|
|
246
246
|
bundle-identifier guard from the section above.
|
|
247
247
|
|
|
248
|
-
|
|
249
|
-
enum RequestState { case pending, approved, denied, expired }
|
|
250
|
-
|
|
251
|
-
@MainActor @Observable
|
|
252
|
-
final class ContactRequests {
|
|
253
|
-
private(set) var states: [UUID: RequestState] = [:]
|
|
254
|
-
|
|
255
|
-
func markPending(_ id: UUID) { states[id] = .pending }
|
|
256
|
-
|
|
257
|
-
func expire(_ id: UUID) {
|
|
258
|
-
guard states[id] == .pending else { return }
|
|
259
|
-
states[id] = .expired // lets the child ask again
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
@available(iOS 26.2, *)
|
|
263
|
-
func listen() async {
|
|
264
|
-
for await response in AskCenter.shared.responses(for: CommunicationTopic.self) {
|
|
265
|
-
let id = response.question.id
|
|
266
|
-
switch response.choice.answer {
|
|
267
|
-
case .approval: states[id] = .approved
|
|
268
|
-
case .denial: states[id] = .denied
|
|
269
|
-
@unknown default: states[id] = .denied
|
|
270
|
-
}
|
|
271
|
-
let people = response.question.topic.personInformation
|
|
272
|
-
refreshContacts(people.map(\.handle))
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private func refreshContacts(_ handles: [CommunicationHandle]) {}
|
|
277
|
-
}
|
|
278
|
-
```
|
|
248
|
+
Code: [Handling responses](references/code-examples.md#handling-responses).
|
|
279
249
|
|
|
280
250
|
- `responses(for:)` is an async sequence of `PermissionResponse<Topic>`; each
|
|
281
251
|
element has `.question` and `.choice`.
|
|
@@ -386,3 +356,4 @@ produces no response.
|
|
|
386
356
|
- https://developer.apple.com/documentation/permissionkit/significantappupdatetopic
|
|
387
357
|
- https://developer.apple.com/documentation/permissionkit/askerror
|
|
388
358
|
- https://developer.apple.com/documentation/permissionkit/creating-a-communication-experience
|
|
359
|
+
- [Code examples](references/code-examples.md): open for the full code behind Handling responses.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# PermissionKit Code Examples
|
|
2
|
+
|
|
3
|
+
Full code samples for the SKILL.md sections that link here. The rules and the decision guidance stay in SKILL.md; each heading below matches the SKILL.md section the code belongs to.
|
|
4
|
+
|
|
5
|
+
## Handling responses
|
|
6
|
+
|
|
7
|
+
```swift
|
|
8
|
+
enum RequestState { case pending, approved, denied, expired }
|
|
9
|
+
|
|
10
|
+
@MainActor @Observable
|
|
11
|
+
final class ContactRequests {
|
|
12
|
+
private(set) var states: [UUID: RequestState] = [:]
|
|
13
|
+
|
|
14
|
+
func markPending(_ id: UUID) { states[id] = .pending }
|
|
15
|
+
|
|
16
|
+
func expire(_ id: UUID) {
|
|
17
|
+
guard states[id] == .pending else { return }
|
|
18
|
+
states[id] = .expired // lets the child ask again
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@available(iOS 26.2, *)
|
|
22
|
+
func listen() async {
|
|
23
|
+
for await response in AskCenter.shared.responses(for: CommunicationTopic.self) {
|
|
24
|
+
let id = response.question.id
|
|
25
|
+
switch response.choice.answer {
|
|
26
|
+
case .approval: states[id] = .approved
|
|
27
|
+
case .denial: states[id] = .denied
|
|
28
|
+
@unknown default: states[id] = .denied
|
|
29
|
+
}
|
|
30
|
+
let people = response.question.topic.personInformation
|
|
31
|
+
refreshContacts(people.map(\.handle))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private func refreshContacts(_ handles: [CommunicationHandle]) {}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
@@ -207,43 +207,7 @@ of receiving each item type before purchase.
|
|
|
207
207
|
|
|
208
208
|
### Play App Signing (Required for New Apps)
|
|
209
209
|
|
|
210
|
-
Google
|
|
211
|
-
|
|
212
|
-
```bash
|
|
213
|
-
# Generate upload key
|
|
214
|
-
keytool -genkeypair -v -keystore upload-keystore.jks \
|
|
215
|
-
-keyalg RSA -keysize 2048 -validity 10000 \
|
|
216
|
-
-alias upload-key
|
|
217
|
-
|
|
218
|
-
# Build AAB (not APK)
|
|
219
|
-
./gradlew bundleRelease
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### AAB Requirements
|
|
223
|
-
|
|
224
|
-
- New apps must upload AAB (Android App Bundle), not APK
|
|
225
|
-
- Maximum download size: 200 MB (on-demand delivery for larger)
|
|
226
|
-
- Use Play Feature Delivery for conditional/on-demand modules
|
|
227
|
-
|
|
228
|
-
### Signing Config
|
|
229
|
-
|
|
230
|
-
```kotlin
|
|
231
|
-
android {
|
|
232
|
-
signingConfigs {
|
|
233
|
-
create("release") {
|
|
234
|
-
storeFile = file(System.getenv("UPLOAD_KEYSTORE_PATH") ?: "upload.jks")
|
|
235
|
-
storePassword = System.getenv("UPLOAD_KEYSTORE_PASSWORD") ?: ""
|
|
236
|
-
keyAlias = System.getenv("UPLOAD_KEY_ALIAS") ?: ""
|
|
237
|
-
keyPassword = System.getenv("UPLOAD_KEY_PASSWORD") ?: ""
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
buildTypes {
|
|
241
|
-
release {
|
|
242
|
-
signingConfig = signingConfigs.getByName("release")
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
```
|
|
210
|
+
Google holds the app signing key; you upload with your own upload key and build an AAB with `bundleRelease`. New apps must upload an AAB, not an APK (200 MB download limit, Play Feature Delivery beyond that), and the release signing config reads keystore values from environment variables. Commands, AAB rules and the Gradle config: [Signing and policy details](references/signing-and-policies.md).
|
|
247
211
|
|
|
248
212
|
## Common Rejection Reasons
|
|
249
213
|
|
|
@@ -266,43 +230,7 @@ android {
|
|
|
266
230
|
|
|
267
231
|
## Policy Categories
|
|
268
232
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
- No hate speech, violence, or terrorism
|
|
272
|
-
- No sexually explicit content (except in apps with IARC rating)
|
|
273
|
-
- No gambling without proper licensing
|
|
274
|
-
- No marijuana or pharmaceutical sales
|
|
275
|
-
- No weapons or dangerous products
|
|
276
|
-
|
|
277
|
-
### Privacy and Security
|
|
278
|
-
|
|
279
|
-
- Data collected must match Data Safety declaration
|
|
280
|
-
- Sensitive permissions require justification
|
|
281
|
-
- Background location requires approval
|
|
282
|
-
- QUERY_ALL_PACKAGES requires approved use case
|
|
283
|
-
- Apps targeting children must comply with COPPA
|
|
284
|
-
|
|
285
|
-
### Monetization
|
|
286
|
-
|
|
287
|
-
- No deceptive subscription flows
|
|
288
|
-
- In-app purchase prices must be clear before purchase
|
|
289
|
-
- Free trials must state duration and post-trial price
|
|
290
|
-
- Refund policy must be accessible
|
|
291
|
-
|
|
292
|
-
### User-Generated Content
|
|
293
|
-
|
|
294
|
-
Apps with UGC must include:
|
|
295
|
-
- Content moderation
|
|
296
|
-
- Reporting mechanism
|
|
297
|
-
- User blocking
|
|
298
|
-
- Published content policy
|
|
299
|
-
|
|
300
|
-
### Account Deletion
|
|
301
|
-
|
|
302
|
-
If your app allows account creation, you must:
|
|
303
|
-
- Offer account deletion within the app
|
|
304
|
-
- Provide a web-based deletion option
|
|
305
|
-
- Delete data within 60 days of request (or explain legal retention)
|
|
233
|
+
Restricted content, privacy and security, monetization, user-generated content (moderation, reporting, blocking, published policy) and account deletion (in-app and web, data deleted within 60 days). Full lists: [Signing and policy details](references/signing-and-policies.md).
|
|
306
234
|
|
|
307
235
|
## Do's and Don'ts
|
|
308
236
|
|
|
@@ -350,3 +278,7 @@ If your app allows account creation, you must:
|
|
|
350
278
|
- [ ] Monitor ANR rate (target < 0.47%)
|
|
351
279
|
- [ ] Respond to user reviews promptly
|
|
352
280
|
- [ ] Set up staged rollout for production releases (10% -> 50% -> 100%)
|
|
281
|
+
|
|
282
|
+
## References
|
|
283
|
+
|
|
284
|
+
- [Signing and policy details](references/signing-and-policies.md): generating an upload key, configuring release signing and AAB delivery, or checking an app against the restricted content, privacy, monetization, UGC and account deletion policies.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Signing and policy details
|
|
2
|
+
|
|
3
|
+
Play App Signing setup, AAB requirements, the Gradle signing config, and the policy categories. SKILL.md keeps the checklists, data safety, privacy, rating, billing and rejection reasons.
|
|
4
|
+
|
|
5
|
+
## Play App Signing (Required for New Apps)
|
|
6
|
+
|
|
7
|
+
Google manages your app signing key. You upload with an upload key.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Generate upload key
|
|
11
|
+
keytool -genkeypair -v -keystore upload-keystore.jks \
|
|
12
|
+
-keyalg RSA -keysize 2048 -validity 10000 \
|
|
13
|
+
-alias upload-key
|
|
14
|
+
|
|
15
|
+
# Build AAB (not APK)
|
|
16
|
+
./gradlew bundleRelease
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## AAB Requirements
|
|
20
|
+
|
|
21
|
+
- New apps must upload AAB (Android App Bundle), not APK
|
|
22
|
+
- Maximum download size: 200 MB (on-demand delivery for larger)
|
|
23
|
+
- Use Play Feature Delivery for conditional/on-demand modules
|
|
24
|
+
|
|
25
|
+
## Signing Config
|
|
26
|
+
|
|
27
|
+
```kotlin
|
|
28
|
+
android {
|
|
29
|
+
signingConfigs {
|
|
30
|
+
create("release") {
|
|
31
|
+
storeFile = file(System.getenv("UPLOAD_KEYSTORE_PATH") ?: "upload.jks")
|
|
32
|
+
storePassword = System.getenv("UPLOAD_KEYSTORE_PASSWORD") ?: ""
|
|
33
|
+
keyAlias = System.getenv("UPLOAD_KEY_ALIAS") ?: ""
|
|
34
|
+
keyPassword = System.getenv("UPLOAD_KEY_PASSWORD") ?: ""
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
buildTypes {
|
|
38
|
+
release {
|
|
39
|
+
signingConfig = signingConfigs.getByName("release")
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Policy Categories
|
|
46
|
+
|
|
47
|
+
### Restricted Content
|
|
48
|
+
|
|
49
|
+
- No hate speech, violence, or terrorism
|
|
50
|
+
- No sexually explicit content (except in apps with IARC rating)
|
|
51
|
+
- No gambling without proper licensing
|
|
52
|
+
- No marijuana or pharmaceutical sales
|
|
53
|
+
- No weapons or dangerous products
|
|
54
|
+
|
|
55
|
+
### Privacy and Security
|
|
56
|
+
|
|
57
|
+
- Data collected must match Data Safety declaration
|
|
58
|
+
- Sensitive permissions require justification
|
|
59
|
+
- Background location requires approval
|
|
60
|
+
- QUERY_ALL_PACKAGES requires approved use case
|
|
61
|
+
- Apps targeting children must comply with COPPA
|
|
62
|
+
|
|
63
|
+
### Monetization
|
|
64
|
+
|
|
65
|
+
- No deceptive subscription flows
|
|
66
|
+
- In-app purchase prices must be clear before purchase
|
|
67
|
+
- Free trials must state duration and post-trial price
|
|
68
|
+
- Refund policy must be accessible
|
|
69
|
+
|
|
70
|
+
### User-Generated Content
|
|
71
|
+
|
|
72
|
+
Apps with UGC must include:
|
|
73
|
+
- Content moderation
|
|
74
|
+
- Reporting mechanism
|
|
75
|
+
- User blocking
|
|
76
|
+
- Published content policy
|
|
77
|
+
|
|
78
|
+
### Account Deletion
|
|
79
|
+
|
|
80
|
+
If your app allows account creation, you must:
|
|
81
|
+
- Offer account deletion within the app
|
|
82
|
+
- Provide a web-based deletion option
|
|
83
|
+
- Delete data within 60 days of request (or explain legal retention)
|