gsd-pi 2.35.0-dev.cd3b7ea → 2.36.0-dev.d612764
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/README.md +3 -1
- package/dist/cli.js +7 -2
- package/dist/resource-loader.d.ts +1 -1
- package/dist/resource-loader.js +13 -1
- package/dist/resources/extensions/async-jobs/await-tool.js +0 -2
- package/dist/resources/extensions/async-jobs/job-manager.js +0 -6
- package/dist/resources/extensions/bg-shell/output-formatter.js +1 -19
- package/dist/resources/extensions/bg-shell/process-manager.js +0 -4
- package/dist/resources/extensions/bg-shell/types.js +0 -2
- package/dist/resources/extensions/cmux/index.js +321 -0
- package/dist/resources/extensions/context7/index.js +5 -0
- package/dist/resources/extensions/get-secrets-from-user.js +2 -30
- package/dist/resources/extensions/google-search/index.js +5 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +334 -104
- package/dist/resources/extensions/gsd/auto-dispatch.js +43 -1
- package/dist/resources/extensions/gsd/auto-loop.js +28 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +15 -3
- package/dist/resources/extensions/gsd/auto-recovery.js +35 -0
- package/dist/resources/extensions/gsd/auto-start.js +35 -2
- package/dist/resources/extensions/gsd/auto.js +75 -4
- package/dist/resources/extensions/gsd/commands-cmux.js +120 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +2 -2
- package/dist/resources/extensions/gsd/commands-inspect.js +10 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands-rate.js +31 -0
- package/dist/resources/extensions/gsd/commands.js +94 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +26 -17
- package/dist/resources/extensions/gsd/files.js +11 -2
- package/dist/resources/extensions/gsd/gitignore.js +54 -7
- package/dist/resources/extensions/gsd/guided-flow.js +8 -2
- package/dist/resources/extensions/gsd/health-widget-core.js +96 -0
- package/dist/resources/extensions/gsd/health-widget.js +97 -46
- package/dist/resources/extensions/gsd/index.js +31 -33
- package/dist/resources/extensions/gsd/migrate-external.js +55 -2
- package/dist/resources/extensions/gsd/milestone-ids.js +3 -2
- package/dist/resources/extensions/gsd/notifications.js +10 -1
- package/dist/resources/extensions/gsd/paths.js +74 -7
- package/dist/resources/extensions/gsd/post-unit-hooks.js +4 -1
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
- package/dist/resources/extensions/gsd/preferences.js +15 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +55 -0
- package/dist/resources/extensions/gsd/session-lock.js +53 -2
- package/dist/resources/extensions/gsd/state.js +2 -1
- package/dist/resources/extensions/gsd/templates/plan.md +8 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +6 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +12 -0
- package/dist/resources/extensions/remote-questions/remote-command.js +2 -22
- package/dist/resources/extensions/search-the-web/native-search.js +45 -4
- package/dist/resources/extensions/shared/mod.js +1 -1
- package/dist/resources/extensions/shared/sanitize.js +30 -0
- package/dist/resources/extensions/shared/terminal.js +5 -0
- package/dist/resources/extensions/subagent/index.js +186 -74
- package/dist/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/dist/resources/skills/github-workflows/SKILL.md +0 -2
- package/dist/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/package.json +2 -1
- package/packages/pi-agent-core/dist/agent.d.ts +10 -2
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +19 -8
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.ts +31 -10
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-responses.ts +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +20 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +13 -2
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -12
- package/packages/pi-coding-agent/src/core/resource-loader.ts +13 -2
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +4 -0
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/src/terminal-image.ts +5 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/await-tool.ts +0 -2
- package/src/resources/extensions/async-jobs/job-manager.ts +0 -7
- package/src/resources/extensions/bg-shell/output-formatter.ts +0 -17
- package/src/resources/extensions/bg-shell/process-manager.ts +0 -4
- package/src/resources/extensions/bg-shell/types.ts +0 -12
- package/src/resources/extensions/cmux/index.ts +384 -0
- package/src/resources/extensions/context7/index.ts +7 -0
- package/src/resources/extensions/get-secrets-from-user.ts +2 -35
- package/src/resources/extensions/google-search/index.ts +7 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +363 -116
- package/src/resources/extensions/gsd/auto-dispatch.ts +49 -1
- package/src/resources/extensions/gsd/auto-loop.ts +64 -2
- package/src/resources/extensions/gsd/auto-model-selection.ts +23 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +39 -0
- package/src/resources/extensions/gsd/auto-start.ts +42 -2
- package/src/resources/extensions/gsd/auto.ts +82 -3
- package/src/resources/extensions/gsd/commands-cmux.ts +143 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -2
- package/src/resources/extensions/gsd/commands-inspect.ts +10 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands-rate.ts +55 -0
- package/src/resources/extensions/gsd/commands.ts +97 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +26 -16
- package/src/resources/extensions/gsd/files.ts +12 -2
- package/src/resources/extensions/gsd/gitignore.ts +54 -7
- package/src/resources/extensions/gsd/guided-flow.ts +8 -2
- package/src/resources/extensions/gsd/health-widget-core.ts +129 -0
- package/src/resources/extensions/gsd/health-widget.ts +103 -59
- package/src/resources/extensions/gsd/index.ts +37 -32
- package/src/resources/extensions/gsd/migrate-external.ts +47 -2
- package/src/resources/extensions/gsd/milestone-ids.ts +3 -2
- package/src/resources/extensions/gsd/notifications.ts +10 -1
- package/src/resources/extensions/gsd/paths.ts +73 -7
- package/src/resources/extensions/gsd/post-unit-hooks.ts +5 -1
- package/src/resources/extensions/gsd/preferences-types.ts +13 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +42 -1
- package/src/resources/extensions/gsd/preferences.ts +18 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/research-milestone.md +4 -3
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -2
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +66 -0
- package/src/resources/extensions/gsd/session-lock.ts +59 -2
- package/src/resources/extensions/gsd/state.ts +2 -1
- package/src/resources/extensions/gsd/templates/plan.md +8 -0
- package/src/resources/extensions/gsd/templates/preferences.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/cmux.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +214 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/paths.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +35 -2
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/test-utils.ts +165 -0
- package/src/resources/extensions/gsd/tests/validate-directory.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +32 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +11 -0
- package/src/resources/extensions/remote-questions/remote-command.ts +2 -23
- package/src/resources/extensions/search-the-web/native-search.ts +50 -4
- package/src/resources/extensions/shared/mod.ts +1 -1
- package/src/resources/extensions/shared/sanitize.ts +36 -0
- package/src/resources/extensions/shared/terminal.ts +5 -0
- package/src/resources/extensions/subagent/index.ts +242 -91
- package/src/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/src/resources/skills/github-workflows/SKILL.md +0 -2
- package/src/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/dist/resources/extensions/shared/wizard-ui.js +0 -478
- package/dist/resources/skills/swiftui/SKILL.md +0 -208
- package/dist/resources/skills/swiftui/references/animations.md +0 -921
- package/dist/resources/skills/swiftui/references/architecture.md +0 -1561
- package/dist/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/dist/resources/skills/swiftui/references/navigation.md +0 -1492
- package/dist/resources/skills/swiftui/references/networking-async.md +0 -214
- package/dist/resources/skills/swiftui/references/performance.md +0 -1706
- package/dist/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/dist/resources/skills/swiftui/references/state-management.md +0 -1443
- package/dist/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/dist/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/dist/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/dist/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/dist/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/dist/resources/skills/swiftui/workflows/ship-app.md +0 -203
- package/dist/resources/skills/swiftui/workflows/write-tests.md +0 -235
- package/src/resources/extensions/shared/wizard-ui.ts +0 -551
- package/src/resources/skills/swiftui/SKILL.md +0 -208
- package/src/resources/skills/swiftui/references/animations.md +0 -921
- package/src/resources/skills/swiftui/references/architecture.md +0 -1561
- package/src/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/src/resources/skills/swiftui/references/navigation.md +0 -1492
- package/src/resources/skills/swiftui/references/networking-async.md +0 -214
- package/src/resources/skills/swiftui/references/performance.md +0 -1706
- package/src/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/src/resources/skills/swiftui/references/state-management.md +0 -1443
- package/src/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/src/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/src/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/src/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/src/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/src/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/src/resources/skills/swiftui/workflows/ship-app.md +0 -203
- package/src/resources/skills/swiftui/workflows/write-tests.md +0 -235
|
@@ -1,478 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* General-purpose multi-page wizard UI.
|
|
3
|
-
*
|
|
4
|
-
* Supports declarative page definitions with select and text fields.
|
|
5
|
-
* Pages can conditionally route to different next pages based on answers.
|
|
6
|
-
*
|
|
7
|
-
* Navigation:
|
|
8
|
-
* ← go back one page (on page 1: triggers exit confirmation)
|
|
9
|
-
* → / Enter advance to next page (or submit on last page)
|
|
10
|
-
* Escape triggers exit confirmation overlay
|
|
11
|
-
*
|
|
12
|
-
* Exit confirmation (shown on Escape or ← from page 1):
|
|
13
|
-
* 1. Go back — dismiss and return to current page
|
|
14
|
-
* 2. Exit — cancel the wizard, returns null to caller
|
|
15
|
-
*
|
|
16
|
-
* Returns:
|
|
17
|
-
* Record<pageId, Record<fieldId, string | string[]>> on completion
|
|
18
|
-
* null on exit/cancel
|
|
19
|
-
*
|
|
20
|
-
* Example:
|
|
21
|
-
*
|
|
22
|
-
* const result = await showWizard(ctx, {
|
|
23
|
-
* title: "New Project",
|
|
24
|
-
* pages: [
|
|
25
|
-
* {
|
|
26
|
-
* id: "mode",
|
|
27
|
-
* fields: [
|
|
28
|
-
* {
|
|
29
|
-
* type: "select",
|
|
30
|
-
* id: "start_type",
|
|
31
|
-
* question: "How do you want to start?",
|
|
32
|
-
* options: [
|
|
33
|
-
* { label: "Describe it", description: "Type what you want to build." },
|
|
34
|
-
* { label: "Provide a file", description: "Point to an existing doc." },
|
|
35
|
-
* ],
|
|
36
|
-
* },
|
|
37
|
-
* ],
|
|
38
|
-
* next: (answers) =>
|
|
39
|
-
* answers["mode"]?.["start_type"] === "Provide a file" ? "file_path" : null,
|
|
40
|
-
* },
|
|
41
|
-
* {
|
|
42
|
-
* id: "file_path",
|
|
43
|
-
* fields: [
|
|
44
|
-
* { type: "text", id: "path", label: "File path", placeholder: "/path/to/doc.md" },
|
|
45
|
-
* ],
|
|
46
|
-
* next: () => null,
|
|
47
|
-
* },
|
|
48
|
-
* ],
|
|
49
|
-
* });
|
|
50
|
-
*
|
|
51
|
-
* if (!result) return; // user exited
|
|
52
|
-
* const startType = result["mode"]["start_type"]; // "Describe it" | "Provide a file"
|
|
53
|
-
* const filePath = result["file_path"]?.["path"];
|
|
54
|
-
*/
|
|
55
|
-
import { Editor, Key, matchesKey, truncateToWidth, } from "@gsd/pi-tui";
|
|
56
|
-
import { makeUI } from "./ui.js";
|
|
57
|
-
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
58
|
-
/**
|
|
59
|
-
* Show a multi-page wizard and return collected answers, or null if the user exits.
|
|
60
|
-
*/
|
|
61
|
-
export async function showWizard(ctx, opts) {
|
|
62
|
-
const pageMap = new Map(opts.pages.map((p) => [p.id, p]));
|
|
63
|
-
return ctx.ui.custom((tui, theme, _kb, done) => {
|
|
64
|
-
// ── State ──────────────────────────────────────────────────────────────
|
|
65
|
-
/** Stack of page ids visited — drives back navigation */
|
|
66
|
-
const pageStack = [opts.pages[0].id];
|
|
67
|
-
const pageStates = new Map();
|
|
68
|
-
/** Collected answers across all pages */
|
|
69
|
-
const answers = {};
|
|
70
|
-
/** Whether the exit-confirmation overlay is showing */
|
|
71
|
-
let showingExitConfirm = false;
|
|
72
|
-
/** Cursor in the exit-confirm overlay: 0 = go back, 1 = exit */
|
|
73
|
-
let exitCursor = 0;
|
|
74
|
-
let cachedLines;
|
|
75
|
-
// Editors keyed by fieldId — one per text field
|
|
76
|
-
// editorTheme is derived from the design system at first render
|
|
77
|
-
const editors = new Map();
|
|
78
|
-
let resolvedEditorTheme = null;
|
|
79
|
-
function getEditor(fieldId) {
|
|
80
|
-
if (!resolvedEditorTheme)
|
|
81
|
-
resolvedEditorTheme = makeUI(theme, 80).editorTheme;
|
|
82
|
-
if (!editors.has(fieldId))
|
|
83
|
-
editors.set(fieldId, new Editor(tui, resolvedEditorTheme));
|
|
84
|
-
return editors.get(fieldId);
|
|
85
|
-
}
|
|
86
|
-
// ── Page state helpers ─────────────────────────────────────────────────
|
|
87
|
-
function getPageState(pageId) {
|
|
88
|
-
if (!pageStates.has(pageId)) {
|
|
89
|
-
pageStates.set(pageId, {
|
|
90
|
-
selectStates: new Map(),
|
|
91
|
-
textValues: new Map(),
|
|
92
|
-
focusedFieldId: null,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
return pageStates.get(pageId);
|
|
96
|
-
}
|
|
97
|
-
function getSelectState(pageId, fieldId, _optCount) {
|
|
98
|
-
const ps = getPageState(pageId);
|
|
99
|
-
if (!ps.selectStates.has(fieldId)) {
|
|
100
|
-
ps.selectStates.set(fieldId, {
|
|
101
|
-
cursorIndex: 0,
|
|
102
|
-
committedIndex: null, // nothing pre-committed — user must explicitly confirm
|
|
103
|
-
checkedIndices: new Set(),
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
return ps.selectStates.get(fieldId);
|
|
107
|
-
}
|
|
108
|
-
// ── Current page ───────────────────────────────────────────────────────
|
|
109
|
-
function currentPageId() {
|
|
110
|
-
return pageStack[pageStack.length - 1];
|
|
111
|
-
}
|
|
112
|
-
function currentPage() {
|
|
113
|
-
return pageMap.get(currentPageId());
|
|
114
|
-
}
|
|
115
|
-
function currentPageState() {
|
|
116
|
-
return getPageState(currentPageId());
|
|
117
|
-
}
|
|
118
|
-
// ── Validation ─────────────────────────────────────────────────────────
|
|
119
|
-
function isPageComplete(page, ps) {
|
|
120
|
-
for (const field of page.fields) {
|
|
121
|
-
if (field.type === "select") {
|
|
122
|
-
const ss = ps.selectStates.get(field.id);
|
|
123
|
-
if (!ss)
|
|
124
|
-
return false;
|
|
125
|
-
if (field.allowMultiple) {
|
|
126
|
-
if (ss.checkedIndices.size === 0)
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
if (ss.committedIndex === null)
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
const val = ps.textValues.get(field.id) ?? "";
|
|
136
|
-
if (!val.trim())
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
// ── Collect answers for a page ─────────────────────────────────────────
|
|
143
|
-
function collectPageAnswers(page, ps) {
|
|
144
|
-
const result = {};
|
|
145
|
-
for (const field of page.fields) {
|
|
146
|
-
if (field.type === "select") {
|
|
147
|
-
const ss = ps.selectStates.get(field.id);
|
|
148
|
-
if (!ss)
|
|
149
|
-
continue;
|
|
150
|
-
if (field.allowMultiple) {
|
|
151
|
-
result[field.id] = Array.from(ss.checkedIndices)
|
|
152
|
-
.sort((a, b) => a - b)
|
|
153
|
-
.map((i) => field.options[i].label);
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
if (ss.committedIndex !== null && ss.committedIndex < field.options.length) {
|
|
157
|
-
result[field.id] = field.options[ss.committedIndex].label;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
result[field.id] = ps.textValues.get(field.id) ?? "";
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return result;
|
|
166
|
-
}
|
|
167
|
-
// ── Auto-focus helper ──────────────────────────────────────────────────
|
|
168
|
-
/** If a page's first field is a text field, focus it immediately on arrival. */
|
|
169
|
-
function autoFocusPageIfText(pageId) {
|
|
170
|
-
const page = pageMap.get(pageId);
|
|
171
|
-
if (!page)
|
|
172
|
-
return;
|
|
173
|
-
const firstField = page.fields[0];
|
|
174
|
-
if (firstField?.type === "text") {
|
|
175
|
-
const ps = getPageState(pageId);
|
|
176
|
-
ps.focusedFieldId = firstField.id;
|
|
177
|
-
const editor = getEditor(firstField.id);
|
|
178
|
-
editor.setText(ps.textValues.get(firstField.id) ?? "");
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
// Auto-focus the first page if it starts with a text field
|
|
182
|
-
autoFocusPageIfText(opts.pages[0].id);
|
|
183
|
-
// ── Navigation ─────────────────────────────────────────────────────────
|
|
184
|
-
function advance() {
|
|
185
|
-
const page = currentPage();
|
|
186
|
-
const ps = currentPageState();
|
|
187
|
-
if (!isPageComplete(page, ps)) {
|
|
188
|
-
refresh();
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
// Save text field values from editors
|
|
192
|
-
for (const field of page.fields) {
|
|
193
|
-
if (field.type === "text") {
|
|
194
|
-
ps.textValues.set(field.id, getEditor(field.id).getText().trim());
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
// Collect answers for this page
|
|
198
|
-
answers[page.id] = collectPageAnswers(page, ps);
|
|
199
|
-
// Route to next page
|
|
200
|
-
const nextId = page.next ? page.next(answers) : null;
|
|
201
|
-
if (!nextId) {
|
|
202
|
-
// End of wizard
|
|
203
|
-
done(answers);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const nextPage = pageMap.get(nextId);
|
|
207
|
-
if (!nextPage) {
|
|
208
|
-
done(answers);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
pageStack.push(nextId);
|
|
212
|
-
autoFocusPageIfText(nextId);
|
|
213
|
-
refresh();
|
|
214
|
-
}
|
|
215
|
-
function goBack() {
|
|
216
|
-
if (pageStack.length <= 1) {
|
|
217
|
-
// Already at first page — Esc here means exit
|
|
218
|
-
showingExitConfirm = true;
|
|
219
|
-
exitCursor = 0;
|
|
220
|
-
refresh();
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
pageStack.pop();
|
|
224
|
-
autoFocusPageIfText(currentPageId());
|
|
225
|
-
refresh();
|
|
226
|
-
}
|
|
227
|
-
function refresh() {
|
|
228
|
-
cachedLines = undefined;
|
|
229
|
-
tui.requestRender();
|
|
230
|
-
}
|
|
231
|
-
// ── Input handler ──────────────────────────────────────────────────────
|
|
232
|
-
function handleInput(data) {
|
|
233
|
-
// ── Exit confirm overlay ─────────────────────────────────────────
|
|
234
|
-
if (showingExitConfirm) {
|
|
235
|
-
if (matchesKey(data, Key.up)) {
|
|
236
|
-
exitCursor = 0;
|
|
237
|
-
refresh();
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (matchesKey(data, Key.down)) {
|
|
241
|
-
exitCursor = 1;
|
|
242
|
-
refresh();
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
if (data === "1") {
|
|
246
|
-
showingExitConfirm = false;
|
|
247
|
-
refresh();
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
if (data === "2") {
|
|
251
|
-
done(null);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
if (matchesKey(data, Key.enter) || matchesKey(data, Key.space)) {
|
|
255
|
-
if (exitCursor === 0) {
|
|
256
|
-
showingExitConfirm = false;
|
|
257
|
-
refresh();
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
done(null);
|
|
261
|
-
}
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
// Esc on the confirm screen = go back (dismiss confirm)
|
|
265
|
-
if (matchesKey(data, Key.escape)) {
|
|
266
|
-
showingExitConfirm = false;
|
|
267
|
-
refresh();
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
// ── Text field focus ─────────────────────────────────────────────
|
|
273
|
-
const ps = currentPageState();
|
|
274
|
-
if (ps.focusedFieldId) {
|
|
275
|
-
const editor = getEditor(ps.focusedFieldId);
|
|
276
|
-
if (matchesKey(data, Key.escape)) {
|
|
277
|
-
// First Esc: unfocus the text field
|
|
278
|
-
ps.textValues.set(ps.focusedFieldId, editor.getText().trim());
|
|
279
|
-
ps.focusedFieldId = null;
|
|
280
|
-
refresh();
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
if (matchesKey(data, Key.enter)) {
|
|
284
|
-
ps.textValues.set(ps.focusedFieldId, editor.getText().trim());
|
|
285
|
-
ps.focusedFieldId = null;
|
|
286
|
-
advance();
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
editor.handleInput(data);
|
|
290
|
-
refresh();
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
// ── Esc with no text field focused: go back (or exit if on page 1) ──
|
|
294
|
-
if (matchesKey(data, Key.escape)) {
|
|
295
|
-
goBack();
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
// ── Enter / → to advance ─────────────────────────────────────────
|
|
299
|
-
if (matchesKey(data, Key.enter) || matchesKey(data, Key.right)) {
|
|
300
|
-
// For single-select fields, commit cursor before advancing
|
|
301
|
-
const page = currentPage();
|
|
302
|
-
for (const field of page.fields) {
|
|
303
|
-
if (field.type === "select" && !field.allowMultiple) {
|
|
304
|
-
const ss = getSelectState(currentPageId(), field.id, field.options.length);
|
|
305
|
-
if (ss.committedIndex === null)
|
|
306
|
-
ss.committedIndex = ss.cursorIndex;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
advance();
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
// ── Select field interactions ────────────────────────────────────
|
|
313
|
-
const page = currentPage();
|
|
314
|
-
for (const field of page.fields) {
|
|
315
|
-
if (field.type !== "select")
|
|
316
|
-
continue;
|
|
317
|
-
const ss = getSelectState(currentPageId(), field.id, field.options.length);
|
|
318
|
-
const totalOpts = field.options.length;
|
|
319
|
-
if (matchesKey(data, Key.up)) {
|
|
320
|
-
ss.cursorIndex = (ss.cursorIndex - 1 + totalOpts) % totalOpts;
|
|
321
|
-
refresh();
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (matchesKey(data, Key.down)) {
|
|
325
|
-
ss.cursorIndex = (ss.cursorIndex + 1) % totalOpts;
|
|
326
|
-
refresh();
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
if (field.allowMultiple) {
|
|
330
|
-
if (matchesKey(data, Key.space)) {
|
|
331
|
-
if (ss.checkedIndices.has(ss.cursorIndex))
|
|
332
|
-
ss.checkedIndices.delete(ss.cursorIndex);
|
|
333
|
-
else
|
|
334
|
-
ss.checkedIndices.add(ss.cursorIndex);
|
|
335
|
-
refresh();
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
// Numeric shortcut: press the number to select and immediately advance
|
|
341
|
-
if (data.length === 1 && data >= "1" && data <= "9") {
|
|
342
|
-
const idx = parseInt(data, 10) - 1;
|
|
343
|
-
if (idx < totalOpts) {
|
|
344
|
-
ss.cursorIndex = idx;
|
|
345
|
-
ss.committedIndex = idx;
|
|
346
|
-
advance();
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
// Enter/Space commit cursor and advance (Enter handled above, Space here)
|
|
351
|
-
if (matchesKey(data, Key.space)) {
|
|
352
|
-
ss.committedIndex = ss.cursorIndex;
|
|
353
|
-
advance();
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
// Only handle the first select field for nav
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
// ── Render ─────────────────────────────────────────────────────────────
|
|
362
|
-
function renderExitConfirm(width) {
|
|
363
|
-
const ui = makeUI(theme, width);
|
|
364
|
-
const lines = [];
|
|
365
|
-
const push = (...rows) => { for (const r of rows)
|
|
366
|
-
lines.push(...r); };
|
|
367
|
-
push(ui.bar(), ui.blank(), ui.header(" Exit wizard?"), ui.blank(), ui.subtitle(" Your progress will be lost."), ui.blank());
|
|
368
|
-
if (exitCursor === 0)
|
|
369
|
-
push(ui.actionSelected(1, "Go back", "Return to where you were."));
|
|
370
|
-
else
|
|
371
|
-
push(ui.actionUnselected(1, "Go back", "Return to where you were."));
|
|
372
|
-
push(ui.blank());
|
|
373
|
-
if (exitCursor === 1)
|
|
374
|
-
push(ui.actionSelected(2, "Exit", "Cancel and discard all answers."));
|
|
375
|
-
else
|
|
376
|
-
push(ui.actionUnselected(2, "Exit", "Cancel and discard all answers."));
|
|
377
|
-
push(ui.blank(), ui.hints(["↑/↓ to choose", "1/2 to quick-select", "enter to confirm"]), ui.bar());
|
|
378
|
-
return lines;
|
|
379
|
-
}
|
|
380
|
-
function renderSelectField(ui, field, lines) {
|
|
381
|
-
const push = (...rows) => { for (const r of rows)
|
|
382
|
-
lines.push(...r); };
|
|
383
|
-
const ss = getSelectState(currentPageId(), field.id, field.options.length);
|
|
384
|
-
const multi = !!field.allowMultiple;
|
|
385
|
-
push(ui.question(` ${field.question}`));
|
|
386
|
-
if (multi)
|
|
387
|
-
push(ui.meta(" (select all that apply — space to toggle, enter to confirm)"));
|
|
388
|
-
push(ui.blank());
|
|
389
|
-
for (let i = 0; i < field.options.length; i++) {
|
|
390
|
-
const opt = field.options[i];
|
|
391
|
-
const isCursor = i === ss.cursorIndex;
|
|
392
|
-
const isCommitted = i === ss.committedIndex;
|
|
393
|
-
if (multi) {
|
|
394
|
-
const isChecked = ss.checkedIndices.has(i);
|
|
395
|
-
if (isCursor)
|
|
396
|
-
push(ui.checkboxSelected(opt.label, opt.description, isChecked));
|
|
397
|
-
else
|
|
398
|
-
push(ui.checkboxUnselected(opt.label, opt.description, isChecked));
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
if (isCursor)
|
|
402
|
-
push(ui.optionSelected(i + 1, opt.label, opt.description, isCommitted));
|
|
403
|
-
else
|
|
404
|
-
push(ui.optionUnselected(i + 1, opt.label, opt.description, { isCommitted }));
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
function renderTextField(ui, field, ps, lines, width) {
|
|
409
|
-
const push = (...rows) => { for (const r of rows)
|
|
410
|
-
lines.push(...r); };
|
|
411
|
-
const isFocused = ps.focusedFieldId === field.id;
|
|
412
|
-
const value = isFocused ? getEditor(field.id).getText() : (ps.textValues.get(field.id) ?? "");
|
|
413
|
-
push(ui.question(` ${field.label}`), ui.blank());
|
|
414
|
-
if (isFocused) {
|
|
415
|
-
for (const line of getEditor(field.id).render(width - 2))
|
|
416
|
-
lines.push(truncateToWidth(` ${line}`, width));
|
|
417
|
-
}
|
|
418
|
-
else if (value) {
|
|
419
|
-
push(ui.answer(` ${value}`));
|
|
420
|
-
}
|
|
421
|
-
else if (field.placeholder) {
|
|
422
|
-
push(ui.meta(` ${field.placeholder}`));
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
function render(width) {
|
|
426
|
-
if (cachedLines)
|
|
427
|
-
return cachedLines;
|
|
428
|
-
if (showingExitConfirm) {
|
|
429
|
-
cachedLines = renderExitConfirm(width);
|
|
430
|
-
return cachedLines;
|
|
431
|
-
}
|
|
432
|
-
const ui = makeUI(theme, width);
|
|
433
|
-
const lines = [];
|
|
434
|
-
const push = (...rows) => { for (const r of rows)
|
|
435
|
-
lines.push(...r); };
|
|
436
|
-
push(ui.bar(), ui.header(` ${opts.title}`));
|
|
437
|
-
// ── Page indicator ────────────────────────────────────────────────
|
|
438
|
-
if (opts.pages.length > 1) {
|
|
439
|
-
push(ui.pageDots(opts.pages.length, pageStack.length - 1));
|
|
440
|
-
}
|
|
441
|
-
// ── Page content ──────────────────────────────────────────────────
|
|
442
|
-
const page = currentPage();
|
|
443
|
-
const ps = currentPageState();
|
|
444
|
-
if (page.subtitle) {
|
|
445
|
-
push(ui.blank(), ui.subtitle(` ${page.subtitle}`));
|
|
446
|
-
}
|
|
447
|
-
push(ui.blank());
|
|
448
|
-
for (const field of page.fields) {
|
|
449
|
-
if (field.type === "select")
|
|
450
|
-
renderSelectField(ui, field, lines);
|
|
451
|
-
else
|
|
452
|
-
renderTextField(ui, field, ps, lines, width);
|
|
453
|
-
push(ui.blank());
|
|
454
|
-
}
|
|
455
|
-
// ── Footer hints ──────────────────────────────────────────────────
|
|
456
|
-
const isFirst = pageStack.length === 1;
|
|
457
|
-
const ps2 = currentPageState();
|
|
458
|
-
const hints = [];
|
|
459
|
-
if (ps2.focusedFieldId) {
|
|
460
|
-
hints.push("enter to continue");
|
|
461
|
-
hints.push("esc to unfocus");
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
hints.push("↑/↓ to move");
|
|
465
|
-
hints.push("enter to select");
|
|
466
|
-
hints.push(!isFirst ? "esc to go back" : "esc to exit");
|
|
467
|
-
}
|
|
468
|
-
push(ui.hints(hints), ui.bar());
|
|
469
|
-
cachedLines = lines;
|
|
470
|
-
return lines;
|
|
471
|
-
}
|
|
472
|
-
return {
|
|
473
|
-
render,
|
|
474
|
-
invalidate: () => { cachedLines = undefined; },
|
|
475
|
-
handleInput,
|
|
476
|
-
};
|
|
477
|
-
});
|
|
478
|
-
}
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: swiftui
|
|
3
|
-
description: SwiftUI apps from scratch through App Store. Full lifecycle - create, debug, test, optimize, ship.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
<essential_principles>
|
|
7
|
-
## How We Work
|
|
8
|
-
|
|
9
|
-
**The user is the product owner. Claude is the developer.**
|
|
10
|
-
|
|
11
|
-
The user does not write code. The user does not read code. The user describes what they want and judges whether the result is acceptable. Claude implements, verifies, and reports outcomes.
|
|
12
|
-
|
|
13
|
-
### 1. Prove, Don't Promise
|
|
14
|
-
|
|
15
|
-
Never say "this should work." Prove it:
|
|
16
|
-
```bash
|
|
17
|
-
xcodebuild build 2>&1 | xcsift # Build passes
|
|
18
|
-
xcodebuild test # Tests pass
|
|
19
|
-
open .../App.app # App launches
|
|
20
|
-
```
|
|
21
|
-
If you didn't run it, you don't know it works.
|
|
22
|
-
|
|
23
|
-
### 2. Tests for Correctness, Eyes for Quality
|
|
24
|
-
|
|
25
|
-
| Question | How to Answer |
|
|
26
|
-
|----------|---------------|
|
|
27
|
-
| Does the logic work? | Write test, see it pass |
|
|
28
|
-
| Does it look right? | Launch app, user looks at it |
|
|
29
|
-
| Does it feel right? | User uses it |
|
|
30
|
-
| Does it crash? | Test + launch |
|
|
31
|
-
| Is it fast enough? | Profiler |
|
|
32
|
-
|
|
33
|
-
Tests verify *correctness*. The user verifies *desirability*.
|
|
34
|
-
|
|
35
|
-
### 3. Report Outcomes, Not Code
|
|
36
|
-
|
|
37
|
-
**Bad:** "I refactored the view model to use @Observable with environment injection"
|
|
38
|
-
**Good:** "Fixed the state bug. App now updates correctly when you add items. Ready for you to verify."
|
|
39
|
-
|
|
40
|
-
The user doesn't care what you changed. The user cares what's different.
|
|
41
|
-
|
|
42
|
-
### 4. Small Steps, Always Verified
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
Change → Verify → Report → Next change
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Never batch up work. Never say "I made several changes." Each change is verified before the next. If something breaks, you know exactly what caused it.
|
|
49
|
-
|
|
50
|
-
### 5. Ask Before, Not After
|
|
51
|
-
|
|
52
|
-
Unclear requirement? Ask now.
|
|
53
|
-
Multiple valid approaches? Ask which.
|
|
54
|
-
Scope creep? Ask if wanted.
|
|
55
|
-
Big refactor needed? Ask permission.
|
|
56
|
-
|
|
57
|
-
Wrong: Build for 30 minutes, then "is this what you wanted?"
|
|
58
|
-
Right: "Before I start, does X mean Y or Z?"
|
|
59
|
-
|
|
60
|
-
### 6. Always Leave It Working
|
|
61
|
-
|
|
62
|
-
Every stopping point = working state. Tests pass, app launches, changes committed. The user can walk away anytime and come back to something that works.
|
|
63
|
-
</essential_principles>
|
|
64
|
-
|
|
65
|
-
<swiftui_principles>
|
|
66
|
-
## SwiftUI Framework Principles
|
|
67
|
-
|
|
68
|
-
### Declarative Mindset
|
|
69
|
-
Describe what the UI should look like for a given state, not how to mutate it. Let SwiftUI manage the rendering. Never force updates - change the state and let the framework react.
|
|
70
|
-
|
|
71
|
-
### Single Source of Truth
|
|
72
|
-
Every piece of data has one authoritative location. Use the right property wrapper: @State for view-local, @Observable for shared objects, @Environment for app-wide. Derived data should be computed, not stored.
|
|
73
|
-
|
|
74
|
-
### Composition Over Inheritance
|
|
75
|
-
Build complex UIs by composing small, focused views. Extract reusable components when patterns emerge. Prefer many small views over few large ones.
|
|
76
|
-
|
|
77
|
-
### Platform-Adaptive Design
|
|
78
|
-
Write once but respect platform idioms. Use native navigation patterns, respect safe areas, adapt to screen sizes. Test on all target platforms.
|
|
79
|
-
</swiftui_principles>
|
|
80
|
-
|
|
81
|
-
<intake>
|
|
82
|
-
**What would you like to do?**
|
|
83
|
-
|
|
84
|
-
1. Build a new SwiftUI app
|
|
85
|
-
2. Debug an existing SwiftUI app
|
|
86
|
-
3. Add a feature to an existing app
|
|
87
|
-
4. Write/run tests
|
|
88
|
-
5. Optimize performance
|
|
89
|
-
6. Ship/release to App Store
|
|
90
|
-
7. Something else
|
|
91
|
-
|
|
92
|
-
**Then read the matching workflow from `workflows/` and follow it.**
|
|
93
|
-
</intake>
|
|
94
|
-
|
|
95
|
-
<routing>
|
|
96
|
-
| Response | Workflow |
|
|
97
|
-
|----------|----------|
|
|
98
|
-
| 1, "new", "create", "build", "start" | `workflows/build-new-app.md` |
|
|
99
|
-
| 2, "broken", "fix", "debug", "crash", "bug" | `workflows/debug-swiftui.md` |
|
|
100
|
-
| 3, "add", "feature", "implement", "change" | `workflows/add-feature.md` |
|
|
101
|
-
| 4, "test", "tests", "TDD", "coverage" | `workflows/write-tests.md` |
|
|
102
|
-
| 5, "slow", "optimize", "performance", "fast" | `workflows/optimize-performance.md` |
|
|
103
|
-
| 6, "ship", "release", "deploy", "publish", "app store" | `workflows/ship-app.md` |
|
|
104
|
-
| 7, other | Clarify, then select workflow or references |
|
|
105
|
-
</routing>
|
|
106
|
-
|
|
107
|
-
<verification_loop>
|
|
108
|
-
## After Every Change
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
# 1. Does it build?
|
|
112
|
-
xcodebuild -scheme AppName build 2>&1 | xcsift
|
|
113
|
-
|
|
114
|
-
# 2. Do tests pass? (use Core scheme for SwiftUI apps to avoid @main hang)
|
|
115
|
-
xcodebuild -scheme AppNameCore test
|
|
116
|
-
|
|
117
|
-
# 3. Does it launch?
|
|
118
|
-
# macOS:
|
|
119
|
-
open ./build/Build/Products/Debug/AppName.app
|
|
120
|
-
|
|
121
|
-
# iOS Simulator:
|
|
122
|
-
xcrun simctl boot "iPhone 15 Pro" 2>/dev/null || true
|
|
123
|
-
xcrun simctl install booted ./build/Build/Products/Debug-iphonesimulator/AppName.app
|
|
124
|
-
xcrun simctl launch booted com.yourcompany.appname
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Note: If tests hang, the test target likely depends on the app target which has `@main`. Extract testable code to a framework target. See `../macos-apps/references/testing-tdd.md` for the pattern.
|
|
128
|
-
|
|
129
|
-
Report to the user:
|
|
130
|
-
- "Build: ✓"
|
|
131
|
-
- "Tests: 12 pass, 0 fail"
|
|
132
|
-
- "App launches, ready for you to check [specific thing]"
|
|
133
|
-
</verification_loop>
|
|
134
|
-
|
|
135
|
-
<cli_infrastructure>
|
|
136
|
-
## CLI Workflow References
|
|
137
|
-
|
|
138
|
-
For building, debugging, testing, and shipping from CLI without opening Xcode, read these from `../macos-apps/references/`:
|
|
139
|
-
|
|
140
|
-
| Reference | Use For |
|
|
141
|
-
|-----------|---------|
|
|
142
|
-
| `cli-workflow.md` | Build, run, test commands; xcodebuild usage; code signing |
|
|
143
|
-
| `cli-observability.md` | Log streaming, crash analysis, memory debugging, LLDB |
|
|
144
|
-
| `project-scaffolding.md` | XcodeGen project.yml templates, file structure, entitlements |
|
|
145
|
-
| `testing-tdd.md` | Test patterns that work from CLI, avoiding @main hangs |
|
|
146
|
-
|
|
147
|
-
These docs are platform-agnostic. For iOS, change destinations:
|
|
148
|
-
```bash
|
|
149
|
-
# iOS Simulator
|
|
150
|
-
xcodebuild -scheme AppName -destination 'platform=iOS Simulator,name=iPhone 15 Pro' build
|
|
151
|
-
|
|
152
|
-
# macOS
|
|
153
|
-
xcodebuild -scheme AppName build
|
|
154
|
-
```
|
|
155
|
-
</cli_infrastructure>
|
|
156
|
-
|
|
157
|
-
<reference_index>
|
|
158
|
-
## Domain Knowledge
|
|
159
|
-
|
|
160
|
-
All in `references/`:
|
|
161
|
-
|
|
162
|
-
**Core:**
|
|
163
|
-
- architecture.md - MVVM patterns, project structure, dependency injection
|
|
164
|
-
- state-management.md - Property wrappers, @Observable, data flow
|
|
165
|
-
- layout-system.md - Stacks, grids, GeometryReader, custom layouts
|
|
166
|
-
|
|
167
|
-
**Navigation & Animation:**
|
|
168
|
-
- navigation.md - NavigationStack, sheets, tabs, deep linking
|
|
169
|
-
- animations.md - Built-in animations, transitions, matchedGeometryEffect
|
|
170
|
-
|
|
171
|
-
**Data & Platform:**
|
|
172
|
-
- swiftdata.md - Persistence, @Model, @Query, CloudKit sync
|
|
173
|
-
- platform-integration.md - iOS/macOS/watchOS/visionOS specifics
|
|
174
|
-
- uikit-appkit-interop.md - UIViewRepresentable, hosting controllers
|
|
175
|
-
|
|
176
|
-
**Support:**
|
|
177
|
-
- networking-async.md - async/await, .task modifier, API clients
|
|
178
|
-
- testing-debugging.md - Previews, unit tests, UI tests, debugging
|
|
179
|
-
- performance.md - Profiling, lazy loading, view identity
|
|
180
|
-
</reference_index>
|
|
181
|
-
|
|
182
|
-
<workflows_index>
|
|
183
|
-
## Workflows
|
|
184
|
-
|
|
185
|
-
All in `workflows/`:
|
|
186
|
-
|
|
187
|
-
| Workflow | Purpose |
|
|
188
|
-
|----------|---------|
|
|
189
|
-
| build-new-app.md | Create new SwiftUI app from scratch |
|
|
190
|
-
| debug-swiftui.md | Find and fix SwiftUI bugs |
|
|
191
|
-
| add-feature.md | Add functionality to existing app |
|
|
192
|
-
| write-tests.md | Write UI and unit tests |
|
|
193
|
-
| optimize-performance.md | Profile and improve performance |
|
|
194
|
-
| ship-app.md | App Store submission, TestFlight, distribution |
|
|
195
|
-
</workflows_index>
|
|
196
|
-
|
|
197
|
-
<canonical_terminology>
|
|
198
|
-
## Terminology
|
|
199
|
-
|
|
200
|
-
Use these terms consistently:
|
|
201
|
-
- **view** (not: widget, component, element)
|
|
202
|
-
- **@Observable** (not: ObservableObject, @Published for new iOS 17+ code)
|
|
203
|
-
- **NavigationStack** (not: NavigationView - deprecated)
|
|
204
|
-
- **SwiftData** (not: Core Data for new projects)
|
|
205
|
-
- **@Environment** (not: @EnvironmentObject for new code)
|
|
206
|
-
- **modifier** (not: method/function when describing view modifiers)
|
|
207
|
-
- **body** (not: render/build when describing view body)
|
|
208
|
-
</canonical_terminology>
|