@appsforgood/next-supabase-kit 0.1.1 → 0.1.4
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 +33 -0
- package/README.md +20 -3
- package/REPOSITORY_SETTINGS.md +1 -0
- package/UPGRADE.md +9 -5
- package/antigravity/commands/audit.toml +16 -0
- package/antigravity/commands/copy.toml +16 -0
- package/antigravity/commands/frontend.toml +17 -0
- package/antigravity/commands/handoff.toml +16 -0
- package/antigravity/commands/plan.toml +18 -0
- package/antigravity/commands/security.toml +16 -0
- package/antigravity/commands/setup.toml +16 -0
- package/antigravity/commands/ship.toml +17 -0
- package/antigravity/commands/upgrade.toml +17 -0
- package/antigravity/plugin.json +58 -0
- package/assistant-adapters/README.md +1 -0
- package/assistant-adapters/antigravity.md +56 -0
- package/assistant-adapters/claude-code-subagents.md +1 -1
- package/assistant-adapters/codex-agents.md +17 -1
- package/assistant-adapters/cursor-agent-kit.mdc +3 -2
- package/assistant-adapters/cursor-frontend.mdc +16 -0
- package/assistant-adapters/cursor-planner.mdc +14 -0
- package/assistant-adapters/cursor-security.mdc +18 -0
- package/assistant-adapters/github-copilot-instructions.md +1 -1
- package/assistant-adapters/github-next-supabase.instructions.md +1 -1
- package/assistant-adapters/model-selection/codex-config.example.toml +3 -0
- package/dist/index.js +5036 -1838
- package/dist/index.js.map +1 -1
- package/dist/studio/office/assets/office.css +551 -0
- package/dist/studio/office/assets/office.js +1105 -0
- package/dist/studio/wizard/assets/wizard.css +525 -0
- package/dist/studio/wizard/assets/wizard.js +692 -0
- package/examples/next-supabase-installed/.agent-kit/manifest.json +59 -58
- package/examples/next-supabase-installed/.agent-kit/model-routing.json +7 -0
- package/examples/next-supabase-installed/.agent-kit/overrides.json +1 -7
- package/examples/next-supabase-installed/audit-output.json +360 -1
- package/examples/next-supabase-installed/tree.txt +6 -0
- package/model-routing/default-model-routing.json +7 -0
- package/package.json +13 -5
- package/runtime-skills/README.md +7 -0
- package/runtime-skills/accessibility-wcag/SKILL.md +8 -0
- package/runtime-skills/agent-handoff-tracing/SKILL.md +8 -0
- package/runtime-skills/best-practice-maturity-review/SKILL.md +8 -0
- package/runtime-skills/content-first-design/SKILL.md +8 -0
- package/runtime-skills/conversion-copywriting/SKILL.md +8 -0
- package/runtime-skills/deployment-observability/SKILL.md +8 -0
- package/runtime-skills/docs-maintainer/SKILL.md +8 -0
- package/runtime-skills/frontend-design-system/SKILL.md +8 -0
- package/runtime-skills/frontend-distinctiveness-benchmark/SKILL.md +8 -0
- package/runtime-skills/frontend-product-quality-rubric/SKILL.md +8 -0
- package/runtime-skills/landing-page-copy/SKILL.md +8 -0
- package/runtime-skills/nextjs-app-router/SKILL.md +8 -0
- package/runtime-skills/onboarding-empty-state-copy/SKILL.md +8 -0
- package/runtime-skills/owasp-security-review/SKILL.md +8 -0
- package/runtime-skills/planning-council/SKILL.md +8 -0
- package/runtime-skills/positioning-messaging/SKILL.md +8 -0
- package/runtime-skills/postgres-migrations/SKILL.md +8 -0
- package/runtime-skills/product-voice-tone/SKILL.md +8 -0
- package/runtime-skills/reference-led-design-critique/SKILL.md +8 -0
- package/runtime-skills/supabase-auth-rls/SKILL.md +8 -0
- package/runtime-skills/testing-qa/SKILL.md +8 -0
- package/runtime-skills/upgrade-maintenance/SKILL.md +8 -0
- package/runtime-skills/visual-regression-qa/SKILL.md +8 -0
- package/schemas/onboarding-state.schema.json +33 -0
- package/templates/next-supabase/.github/workflows/agent-kit-audit.yml +35 -0
- package/templates/next-supabase/AGENTS.md +1 -1
- package/templates/next-supabase/ASSISTANT_ADAPTERS.md +46 -4
- package/templates/next-supabase/CLAUDE.md +39 -0
- package/templates/next-supabase/DECISIONS.md +14 -0
- package/templates/next-supabase/DOCS.md +4 -0
- package/templates/next-supabase/MODEL_ROUTING.md +12 -0
- package/templates/next-supabase/QUALITY_GATES.md +4 -0
- package/templates/next-supabase/SPEC.md +2 -0
- package/templates/next-supabase/TESTING.md +3 -0
- package/templates/next-supabase/UPGRADE.md +4 -0
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
/* global WIZARD_BOOT */
|
|
2
|
+
(function wizardApp() {
|
|
3
|
+
const boot = window.WIZARD_BOOT || {};
|
|
4
|
+
const RECOMMENDED_SUPABASE_AUTH = boot.recommendedSupabaseAuth || "";
|
|
5
|
+
|
|
6
|
+
const state = {
|
|
7
|
+
view: "home",
|
|
8
|
+
stepIndex: 0,
|
|
9
|
+
depth: "undecided",
|
|
10
|
+
onboarding: {},
|
|
11
|
+
progress: {},
|
|
12
|
+
form: {},
|
|
13
|
+
hasExistingContext: false,
|
|
14
|
+
designDraft: null,
|
|
15
|
+
messagingDraft: null,
|
|
16
|
+
ideSurfaces: boot.ideSurfaces || [],
|
|
17
|
+
agents: boot.agents || []
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const els = {
|
|
21
|
+
status: document.getElementById("status"),
|
|
22
|
+
projectName: document.getElementById("project-name"),
|
|
23
|
+
ringPct: document.getElementById("ring-pct"),
|
|
24
|
+
ring: document.getElementById("progress-ring"),
|
|
25
|
+
sectionNav: document.getElementById("section-nav"),
|
|
26
|
+
card: document.getElementById("wizard-card"),
|
|
27
|
+
footer: document.getElementById("wizard-footer"),
|
|
28
|
+
backBtn: document.getElementById("back-btn"),
|
|
29
|
+
nextBtn: document.getElementById("next-btn"),
|
|
30
|
+
saveBtn: document.getElementById("save-btn")
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function setStatus(kind, message) {
|
|
34
|
+
els.status.className = kind ? "status " + kind : "status";
|
|
35
|
+
els.status.textContent = message || "";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function escapeHtml(value) {
|
|
39
|
+
return String(value ?? "")
|
|
40
|
+
.replace(/&/g, "&")
|
|
41
|
+
.replace(/</g, "<")
|
|
42
|
+
.replace(/>/g, ">")
|
|
43
|
+
.replace(/"/g, """);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function fieldValue(name) {
|
|
47
|
+
const el = document.querySelector('[name="' + name + '"]');
|
|
48
|
+
return el && "value" in el ? String(el.value).trim() : state.form[name] || "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function collectForm() {
|
|
52
|
+
document.querySelectorAll("[name]").forEach((el) => {
|
|
53
|
+
if ("value" in el) state.form[el.name] = el.value;
|
|
54
|
+
});
|
|
55
|
+
return state.form;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function api(path, options) {
|
|
59
|
+
const response = await fetch(path, options);
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
if (!response.ok) throw new Error(data.error || "Request failed");
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function saveDraft() {
|
|
66
|
+
collectForm();
|
|
67
|
+
const data = await api("/api/draft", {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: { "Content-Type": "application/json" },
|
|
70
|
+
body: JSON.stringify({ form: state.form })
|
|
71
|
+
});
|
|
72
|
+
state.progress = data.progress;
|
|
73
|
+
renderRail();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function loadAll() {
|
|
77
|
+
const data = await api("/api/state");
|
|
78
|
+
state.onboarding = data.onboarding;
|
|
79
|
+
state.progress = data.progress;
|
|
80
|
+
state.form = data.form || {};
|
|
81
|
+
state.hasExistingContext = Boolean(data.hasExistingContext);
|
|
82
|
+
state.depth = data.onboarding.depth || "undecided";
|
|
83
|
+
state.designDraft = data.designDraft;
|
|
84
|
+
state.messagingDraft = data.messagingDraft;
|
|
85
|
+
if (Array.isArray(data.agents) && data.agents.length) state.agents = data.agents;
|
|
86
|
+
els.projectName.textContent = data.projectName || "your project";
|
|
87
|
+
const pct = data.progress?.percent ?? 0;
|
|
88
|
+
els.ringPct.textContent = pct + "%";
|
|
89
|
+
els.ring.style.setProperty("--pct", String(pct));
|
|
90
|
+
render();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function patchState(patch) {
|
|
94
|
+
const data = await api("/api/state", {
|
|
95
|
+
method: "PATCH",
|
|
96
|
+
headers: { "Content-Type": "application/json" },
|
|
97
|
+
body: JSON.stringify(patch)
|
|
98
|
+
});
|
|
99
|
+
state.onboarding = data.onboarding;
|
|
100
|
+
state.progress = data.progress;
|
|
101
|
+
renderRail();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function stepsForCurrentDepth() {
|
|
105
|
+
return (boot.steps || []).filter((s) => s.depth.includes(state.depth));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function currentStepDef() {
|
|
109
|
+
return stepsForCurrentDepth()[state.stepIndex];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function validateCurrentStep() {
|
|
113
|
+
const step = currentStepDef();
|
|
114
|
+
if (!step) return true;
|
|
115
|
+
let valid = true;
|
|
116
|
+
for (const field of step.fields) {
|
|
117
|
+
const val = fieldValue(field);
|
|
118
|
+
const err = document.querySelector('[data-error-for="' + field + '"]');
|
|
119
|
+
const required = !step.optional;
|
|
120
|
+
const empty = !val;
|
|
121
|
+
if (err) err.classList.toggle("show", required && empty);
|
|
122
|
+
if (required && empty) valid = false;
|
|
123
|
+
}
|
|
124
|
+
return valid;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function renderRail() {
|
|
128
|
+
const sections = state.progress?.sections || [];
|
|
129
|
+
els.sectionNav.innerHTML = sections
|
|
130
|
+
.map((s) => {
|
|
131
|
+
const chipClass =
|
|
132
|
+
s.status === "done" ? "done" : s.status === "in_progress" ? "progress" : s.status === "optional" ? "optional" : "";
|
|
133
|
+
const chipLabel =
|
|
134
|
+
s.status === "done" ? "Done" : s.status === "in_progress" ? "Now" : s.status === "optional" ? "Optional" : "—";
|
|
135
|
+
return (
|
|
136
|
+
'<li><button type="button" data-section="' +
|
|
137
|
+
s.id +
|
|
138
|
+
'" ' +
|
|
139
|
+
(s.status === "in_progress" ? 'aria-current="step"' : "") +
|
|
140
|
+
">" +
|
|
141
|
+
escapeHtml(s.label) +
|
|
142
|
+
'<span class="chip ' +
|
|
143
|
+
chipClass +
|
|
144
|
+
'">' +
|
|
145
|
+
chipLabel +
|
|
146
|
+
"</span></button></li>"
|
|
147
|
+
);
|
|
148
|
+
})
|
|
149
|
+
.join("");
|
|
150
|
+
els.sectionNav.querySelectorAll("[data-section]").forEach((btn) => {
|
|
151
|
+
btn.addEventListener("click", () => {
|
|
152
|
+
const id = btn.getAttribute("data-section");
|
|
153
|
+
const idx = stepsForCurrentDepth().findIndex((s) => s.section === id);
|
|
154
|
+
if (idx >= 0) {
|
|
155
|
+
state.view = "step";
|
|
156
|
+
state.stepIndex = idx;
|
|
157
|
+
render();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function renderTeamIntro() {
|
|
164
|
+
const cards = state.agents
|
|
165
|
+
.map(
|
|
166
|
+
(a) =>
|
|
167
|
+
'<li class="agent-card"><strong>' +
|
|
168
|
+
escapeHtml(a.name) +
|
|
169
|
+
"</strong><p>" +
|
|
170
|
+
escapeHtml(a.roleSummary) +
|
|
171
|
+
"</p></li>"
|
|
172
|
+
)
|
|
173
|
+
.join("");
|
|
174
|
+
return (
|
|
175
|
+
'<p class="why">Next you will brief each specialist — one step per agent. Skip any you are not ready to answer; you can return later.</p>' +
|
|
176
|
+
'<ul class="agent-roster">' +
|
|
177
|
+
cards +
|
|
178
|
+
"</ul>"
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function renderAgentBriefField(fieldName, step) {
|
|
183
|
+
const val = escapeHtml(state.form[fieldName] || "");
|
|
184
|
+
return (
|
|
185
|
+
'<div class="agent-brief">' +
|
|
186
|
+
'<p class="agent-role">' +
|
|
187
|
+
escapeHtml(step.roleSummary || "") +
|
|
188
|
+
'</p><label for="' +
|
|
189
|
+
fieldName +
|
|
190
|
+
'">Project briefing for ' +
|
|
191
|
+
escapeHtml(step.agentName || "this agent") +
|
|
192
|
+
'<span>Optional — like onboarding a freelancer who already knows their craft.</span></label><textarea id="' +
|
|
193
|
+
fieldName +
|
|
194
|
+
'" name="' +
|
|
195
|
+
fieldName +
|
|
196
|
+
'" placeholder="What is unique about this project for ' +
|
|
197
|
+
escapeHtml(step.agentName || "them") +
|
|
198
|
+
'? Constraints, priorities, things not obvious from the repo…">' +
|
|
199
|
+
val +
|
|
200
|
+
"</textarea></div>"
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function renderHome() {
|
|
205
|
+
const pills = (boot.stackSignals || [])
|
|
206
|
+
.map((s) => '<span class="pill">' + escapeHtml(s) + "</span>")
|
|
207
|
+
.join("");
|
|
208
|
+
const agentCount = state.agents.length || boot.agents?.length || 0;
|
|
209
|
+
const officePromo =
|
|
210
|
+
'<div class="office-promo">' +
|
|
211
|
+
"<h3>Try the Agent Office</h3>" +
|
|
212
|
+
"<p>Walk a pixel office floor — click agent desks to brief your team, or visit zone stations for product, security, and design setup.</p>" +
|
|
213
|
+
'<a href="/" class="btn">Open Agent Office →</a>' +
|
|
214
|
+
"</div>";
|
|
215
|
+
return (
|
|
216
|
+
officePromo +
|
|
217
|
+
'<div class="eyebrow">Form view · setup home</div>' +
|
|
218
|
+
"<h2>Brief your agent team (step-by-step)</h2>" +
|
|
219
|
+
'<p class="why">You have <strong>' +
|
|
220
|
+
agentCount +
|
|
221
|
+
" specialists</strong> — planner, architect, engineers, design, copy, security, QA, and more. They already know their fields. This wizard captures what is unique about <em>your</em> project, like briefing freelancers you just hired.</p>" +
|
|
222
|
+
'<p class="why">Forms start empty so you can answer fresh. Progress auto-saves as you go.</p>' +
|
|
223
|
+
(state.hasExistingContext
|
|
224
|
+
? '<p class="why"><button type="button" class="link-btn" id="import-context">Load answers from existing project context</button> (optional)</p>'
|
|
225
|
+
: "") +
|
|
226
|
+
'<div class="stack-pills">' +
|
|
227
|
+
pills +
|
|
228
|
+
"</div>" +
|
|
229
|
+
'<p class="why" style="margin-top:20px"><strong>Choose your path</strong></p>' +
|
|
230
|
+
'<div class="depth-grid">' +
|
|
231
|
+
depthCard("quick", "Quick (~10 min)", "IDE setup, agent briefings, and product essentials.") +
|
|
232
|
+
depthCard("standard", "Standard (~15 min)", "Quick plus visual QA tier for UI changes.") +
|
|
233
|
+
depthCard("complete", "Complete (~25 min)", "Standard plus DESIGN and MESSAGING intake drafts.") +
|
|
234
|
+
"</div>" +
|
|
235
|
+
(state.progress?.recommendedNext
|
|
236
|
+
? '<p class="why" style="margin-top:18px">Continue: <strong>' +
|
|
237
|
+
escapeHtml(state.progress.recommendedNext) +
|
|
238
|
+
"</strong></p>"
|
|
239
|
+
: "")
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function depthCard(id, title, desc) {
|
|
244
|
+
const sel = state.depth === id ? " selected" : "";
|
|
245
|
+
return (
|
|
246
|
+
'<button type="button" class="depth-card' +
|
|
247
|
+
sel +
|
|
248
|
+
'" data-depth="' +
|
|
249
|
+
id +
|
|
250
|
+
'"><strong>' +
|
|
251
|
+
escapeHtml(title) +
|
|
252
|
+
"</strong><p>" +
|
|
253
|
+
escapeHtml(desc) +
|
|
254
|
+
"</p></button>"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function inputField(name, label, hint, type, placeholder) {
|
|
259
|
+
const val = escapeHtml(state.form[name] || "");
|
|
260
|
+
if (type === "textarea") {
|
|
261
|
+
return (
|
|
262
|
+
"<label for=\"" +
|
|
263
|
+
name +
|
|
264
|
+
"\">" +
|
|
265
|
+
escapeHtml(label) +
|
|
266
|
+
(hint ? "<span>" + escapeHtml(hint) + "</span>" : "") +
|
|
267
|
+
'</label><textarea id="' +
|
|
268
|
+
name +
|
|
269
|
+
'" name="' +
|
|
270
|
+
name +
|
|
271
|
+
'" placeholder="' +
|
|
272
|
+
escapeHtml(placeholder || "") +
|
|
273
|
+
'">' +
|
|
274
|
+
val +
|
|
275
|
+
'</textarea><p class="field-error" data-error-for="' +
|
|
276
|
+
name +
|
|
277
|
+
'">This field is required.</p>'
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
return (
|
|
281
|
+
"<label for=\"" +
|
|
282
|
+
name +
|
|
283
|
+
"\">" +
|
|
284
|
+
escapeHtml(label) +
|
|
285
|
+
(hint ? "<span>" + escapeHtml(hint) + "</span>" : "") +
|
|
286
|
+
'</label><input id="' +
|
|
287
|
+
name +
|
|
288
|
+
'" name="' +
|
|
289
|
+
name +
|
|
290
|
+
'" type="text" value="' +
|
|
291
|
+
val +
|
|
292
|
+
'" placeholder="' +
|
|
293
|
+
escapeHtml(placeholder || "") +
|
|
294
|
+
'"><p class="field-error" data-error-for="' +
|
|
295
|
+
name +
|
|
296
|
+
'">This field is required.</p>'
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function renderStepFields(step) {
|
|
301
|
+
const map = {
|
|
302
|
+
productSummary: () =>
|
|
303
|
+
inputField(
|
|
304
|
+
"productSummary",
|
|
305
|
+
"Product summary",
|
|
306
|
+
"One paragraph: what it does, who it serves, how data or content flows.",
|
|
307
|
+
"textarea",
|
|
308
|
+
"Describe the product in plain language."
|
|
309
|
+
),
|
|
310
|
+
productCategory: () => {
|
|
311
|
+
const opts = (boot.categories || [])
|
|
312
|
+
.map(
|
|
313
|
+
(c) =>
|
|
314
|
+
'<option value="' +
|
|
315
|
+
c +
|
|
316
|
+
'"' +
|
|
317
|
+
(state.form.productCategory === c ? " selected" : "") +
|
|
318
|
+
">" +
|
|
319
|
+
c +
|
|
320
|
+
"</option>"
|
|
321
|
+
)
|
|
322
|
+
.join("");
|
|
323
|
+
return (
|
|
324
|
+
'<label for="productCategory">Category</label><select id="productCategory" name="productCategory">' +
|
|
325
|
+
opts +
|
|
326
|
+
"</select>"
|
|
327
|
+
);
|
|
328
|
+
},
|
|
329
|
+
primaryAudience: () =>
|
|
330
|
+
inputField("primaryAudience", "Primary user or buyer", "", "text", "Who uses or pays for this product?"),
|
|
331
|
+
primaryWorkflows: () =>
|
|
332
|
+
inputField(
|
|
333
|
+
"primaryWorkflows",
|
|
334
|
+
"Top workflows",
|
|
335
|
+
"One per line.",
|
|
336
|
+
"textarea",
|
|
337
|
+
"Sign up and configure an account\nComplete the primary task\nReview or export results"
|
|
338
|
+
),
|
|
339
|
+
tenantModel: () => {
|
|
340
|
+
const opts = (boot.tenantModels || [])
|
|
341
|
+
.map(
|
|
342
|
+
(c) =>
|
|
343
|
+
'<option value="' +
|
|
344
|
+
c +
|
|
345
|
+
'"' +
|
|
346
|
+
(state.form.tenantModel === c ? " selected" : "") +
|
|
347
|
+
">" +
|
|
348
|
+
c +
|
|
349
|
+
"</option>"
|
|
350
|
+
)
|
|
351
|
+
.join("");
|
|
352
|
+
return '<label for="tenantModel">Who uses the system?</label><select id="tenantModel" name="tenantModel">' + opts + "</select>";
|
|
353
|
+
},
|
|
354
|
+
owner: () => inputField("owner", "Project owner", "Optional.", "text", ""),
|
|
355
|
+
authModel: () =>
|
|
356
|
+
(boot.hasSupabase
|
|
357
|
+
? '<div class="hint">Supabase detected. Insert the kit baseline, then customize for your roles.<button type="button" id="apply-supabase-auth">Insert Supabase auth baseline</button></div>'
|
|
358
|
+
: "") +
|
|
359
|
+
inputField(
|
|
360
|
+
"authModel",
|
|
361
|
+
"Authentication model",
|
|
362
|
+
"Sign-in methods, roles, and rules agents must preserve.",
|
|
363
|
+
"textarea",
|
|
364
|
+
"Describe auth boundaries agents must not break."
|
|
365
|
+
),
|
|
366
|
+
uiPreferred: () =>
|
|
367
|
+
inputField("uiPreferred", "UI should feel like…", "", "textarea", "Task-first, clear hierarchy, readable typography."),
|
|
368
|
+
uiAvoid: () =>
|
|
369
|
+
inputField("uiAvoid", "UI should avoid…", "Optional.", "textarea", "Generic SaaS heroes, card soup, fake metrics."),
|
|
370
|
+
valueProposition: () =>
|
|
371
|
+
inputField("valueProposition", "Value proposition", "", "textarea", "What outcome do users get?"),
|
|
372
|
+
proof: () => inputField("proof", "Proof points", "One per line. Real evidence only.", "textarea", ""),
|
|
373
|
+
objections: () => inputField("objections", "Objections", "One per line. Optional.", "textarea", ""),
|
|
374
|
+
qualityTarget: () => {
|
|
375
|
+
const q = state.form.qualityTarget || "baseline-setup";
|
|
376
|
+
return (
|
|
377
|
+
'<label for="qualityTarget">Quality target</label><select id="qualityTarget" name="qualityTarget">' +
|
|
378
|
+
optionQuality("baseline-setup", "baseline-setup — kit installed, filling evidence", q) +
|
|
379
|
+
optionQuality("needs-improvement", "needs-improvement — active delivery", q) +
|
|
380
|
+
optionQuality("best-practice-candidate", "best-practice-candidate — clean audit goal", q) +
|
|
381
|
+
"</select>"
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
ideSurface: () => {
|
|
385
|
+
const opts = state.ideSurfaces
|
|
386
|
+
.map(
|
|
387
|
+
(s) =>
|
|
388
|
+
'<option value="' +
|
|
389
|
+
s.id +
|
|
390
|
+
'"' +
|
|
391
|
+
(state.form.ideSurface === s.id ? " selected" : "") +
|
|
392
|
+
">" +
|
|
393
|
+
escapeHtml(s.label) +
|
|
394
|
+
"</option>"
|
|
395
|
+
)
|
|
396
|
+
.join("");
|
|
397
|
+
return (
|
|
398
|
+
'<label for="ideSurface">Primary AI coding tool</label><select id="ideSurface" name="ideSurface" required>' +
|
|
399
|
+
'<option value="">Choose your IDE…</option>' +
|
|
400
|
+
opts +
|
|
401
|
+
'</select><p class="why">We configure instructions for this path: <code id="ide-path"></code></p>'
|
|
402
|
+
);
|
|
403
|
+
},
|
|
404
|
+
visualQaTier: () => {
|
|
405
|
+
const t = state.form.visualQaTier || "baseline";
|
|
406
|
+
return (
|
|
407
|
+
'<label for="visualQaTier">Visual QA tier</label><select id="visualQaTier" name="visualQaTier">' +
|
|
408
|
+
optionTier("baseline", "Baseline — manual screenshot review", t) +
|
|
409
|
+
optionTier("strong", "Strong — Playwright screenshots + review", t) +
|
|
410
|
+
optionTier("mature", "Mature — Storybook / visual regression CI", t) +
|
|
411
|
+
"</select>"
|
|
412
|
+
);
|
|
413
|
+
},
|
|
414
|
+
designAudience: () => inputField("designAudience", "Design audience", "", "text", state.form.primaryAudience || ""),
|
|
415
|
+
designContent: () => inputField("designContent", "Content inventory", "Real nouns, labels, data types.", "textarea", ""),
|
|
416
|
+
designAntiReferences: () => inputField("designAntiReferences", "Anti-references", "Patterns to avoid.", "textarea", ""),
|
|
417
|
+
msgAudience: () => inputField("msgAudience", "Primary audience", "", "text", state.form.primaryAudience || ""),
|
|
418
|
+
msgPain: () => inputField("msgPain", "Painful problem", "In customer language.", "textarea", ""),
|
|
419
|
+
msgOutcome: () => inputField("msgOutcome", "Desired outcome", "", "textarea", "")
|
|
420
|
+
};
|
|
421
|
+
return step.fields
|
|
422
|
+
.map((f) => {
|
|
423
|
+
if (map[f]) return map[f]();
|
|
424
|
+
if (f.startsWith("agentBrief_")) return renderAgentBriefField(f, step);
|
|
425
|
+
return "";
|
|
426
|
+
})
|
|
427
|
+
.join("");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function optionQuality(v, l, current) {
|
|
431
|
+
return '<option value="' + v + '"' + (current === v ? " selected" : "") + ">" + escapeHtml(l) + "</option>";
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function optionTier(v, l, current) {
|
|
435
|
+
return '<option value="' + v + '"' + (current === v ? " selected" : "") + ">" + escapeHtml(l) + "</option>";
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function renderReview() {
|
|
439
|
+
const ide = state.ideSurfaces.find((s) => s.id === fieldValue("ideSurface"));
|
|
440
|
+
const briefCount = Object.keys(state.form).filter((k) => k.startsWith("agentBrief_") && state.form[k]?.trim()).length;
|
|
441
|
+
const items = [
|
|
442
|
+
["IDE / AI tool", ide ? ide.label : fieldValue("ideSurface") || "—"],
|
|
443
|
+
["Agent briefings", briefCount ? briefCount + " specialist(s) briefed" : "—"],
|
|
444
|
+
["Product summary", fieldValue("productSummary")],
|
|
445
|
+
["Audience", fieldValue("primaryAudience")],
|
|
446
|
+
["Workflows", fieldValue("primaryWorkflows")],
|
|
447
|
+
["Auth", fieldValue("authModel")],
|
|
448
|
+
["UI preferred", fieldValue("uiPreferred")],
|
|
449
|
+
["Value proposition", fieldValue("valueProposition")],
|
|
450
|
+
["Quality target", fieldValue("qualityTarget")]
|
|
451
|
+
];
|
|
452
|
+
return (
|
|
453
|
+
'<div class="eyebrow">Review</div><h2>Check your answers</h2><p class="why">Nothing is written to project context until you click Save. Agent briefings go to <code>.agent-kit/agent-briefs.md</code>.</p><dl class="review">' +
|
|
454
|
+
items.map(([k, v]) => "<div><dt>" + escapeHtml(k) + "</dt><dd>" + escapeHtml(v || "—") + "</dd></div>").join("") +
|
|
455
|
+
"</dl>"
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function renderComplete() {
|
|
460
|
+
return (
|
|
461
|
+
'<div class="complete-icon" aria-hidden="true">✓</div><h2>Setup saved</h2><p class="why">Agents read <code>.agent-kit/project-context.md</code> and <code>.agent-kit/agent-briefs.md</code> before meaningful work.</p><ol class="next-steps"><li>Run <code>agent-kit audit</code></li><li>Reload your IDE so it picks up instructions for your chosen tool</li><li>Return anytime with <code>agent-kit setup</code></li></ol>'
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function sectionLabel(section) {
|
|
466
|
+
const labels = {
|
|
467
|
+
ide: "Your IDE",
|
|
468
|
+
team: "Agent team",
|
|
469
|
+
product: "Product",
|
|
470
|
+
access: "Access",
|
|
471
|
+
ui: "UI",
|
|
472
|
+
messaging: "Messaging",
|
|
473
|
+
visualQa: "Visual QA",
|
|
474
|
+
designDoc: "Design",
|
|
475
|
+
messagingDoc: "Copy",
|
|
476
|
+
applyDrafts: "Apply drafts"
|
|
477
|
+
};
|
|
478
|
+
return labels[section] || section;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function renderStep() {
|
|
482
|
+
const step = currentStepDef();
|
|
483
|
+
if (!step) return renderHome();
|
|
484
|
+
const total = stepsForCurrentDepth().length;
|
|
485
|
+
const body = step.id === "team-intro" ? renderTeamIntro() : renderStepFields(step);
|
|
486
|
+
return (
|
|
487
|
+
'<div class="eyebrow">Step ' +
|
|
488
|
+
(state.stepIndex + 1) +
|
|
489
|
+
" of " +
|
|
490
|
+
total +
|
|
491
|
+
" · " +
|
|
492
|
+
escapeHtml(sectionLabel(step.section)) +
|
|
493
|
+
'</div><h2 id="step-title">' +
|
|
494
|
+
escapeHtml(step.title) +
|
|
495
|
+
'</h2><p class="why">' +
|
|
496
|
+
escapeHtml(step.why) +
|
|
497
|
+
"</p>" +
|
|
498
|
+
body
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function render() {
|
|
503
|
+
renderRail();
|
|
504
|
+
if (state.view === "home") {
|
|
505
|
+
els.card.innerHTML = renderHome();
|
|
506
|
+
bindHome();
|
|
507
|
+
els.backBtn.classList.add("hidden");
|
|
508
|
+
els.nextBtn.textContent = "Start wizard";
|
|
509
|
+
els.saveBtn.classList.add("hidden");
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (state.view === "complete") {
|
|
513
|
+
els.card.innerHTML = renderComplete();
|
|
514
|
+
els.footer.classList.add("hidden");
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (state.view === "review") {
|
|
518
|
+
collectForm();
|
|
519
|
+
els.card.innerHTML = renderReview();
|
|
520
|
+
els.backBtn.classList.remove("hidden");
|
|
521
|
+
els.nextBtn.classList.add("hidden");
|
|
522
|
+
els.saveBtn.classList.remove("hidden");
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
els.card.innerHTML = renderStep();
|
|
526
|
+
bindStep();
|
|
527
|
+
els.backBtn.classList.remove("hidden");
|
|
528
|
+
els.nextBtn.classList.remove("hidden");
|
|
529
|
+
els.saveBtn.classList.add("hidden");
|
|
530
|
+
els.nextBtn.textContent = state.stepIndex >= stepsForCurrentDepth().length - 1 ? "Review" : "Next";
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function bindHome() {
|
|
534
|
+
const importBtn = document.getElementById("import-context");
|
|
535
|
+
if (importBtn) {
|
|
536
|
+
importBtn.addEventListener("click", async () => {
|
|
537
|
+
setStatus("", "Loading existing context…");
|
|
538
|
+
try {
|
|
539
|
+
const data = await api("/api/context/import", { method: "POST" });
|
|
540
|
+
state.form = data.form || state.form;
|
|
541
|
+
state.hasExistingContext = false;
|
|
542
|
+
setStatus("ok", "Loaded from existing project context.");
|
|
543
|
+
render();
|
|
544
|
+
} catch (error) {
|
|
545
|
+
setStatus("error", error.message);
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
els.card.querySelectorAll("[data-depth]").forEach((btn) => {
|
|
550
|
+
btn.addEventListener("click", async () => {
|
|
551
|
+
state.depth = btn.getAttribute("data-depth");
|
|
552
|
+
await patchState({ depth: state.depth, currentSection: "ide", currentStep: 0 });
|
|
553
|
+
state.view = "step";
|
|
554
|
+
state.stepIndex = 0;
|
|
555
|
+
render();
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function bindStep() {
|
|
561
|
+
const applyBtn = document.getElementById("apply-supabase-auth");
|
|
562
|
+
if (applyBtn) {
|
|
563
|
+
applyBtn.addEventListener("click", () => {
|
|
564
|
+
const el = document.querySelector('[name="authModel"]');
|
|
565
|
+
if (el) el.value = RECOMMENDED_SUPABASE_AUTH;
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
const ideSelect = document.querySelector('[name="ideSurface"]');
|
|
569
|
+
if (ideSelect) {
|
|
570
|
+
const updatePath = () => {
|
|
571
|
+
const id = ideSelect.value;
|
|
572
|
+
const surface = state.ideSurfaces.find((s) => s.id === id);
|
|
573
|
+
const pathEl = document.getElementById("ide-path");
|
|
574
|
+
if (pathEl && surface) pathEl.textContent = surface.path;
|
|
575
|
+
};
|
|
576
|
+
ideSelect.addEventListener("change", updatePath);
|
|
577
|
+
updatePath();
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
els.backBtn.addEventListener("click", () => {
|
|
582
|
+
if (state.view === "step" && state.stepIndex > 0) {
|
|
583
|
+
collectForm();
|
|
584
|
+
state.stepIndex -= 1;
|
|
585
|
+
render();
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (state.view === "review") {
|
|
589
|
+
state.view = "step";
|
|
590
|
+
state.stepIndex = stepsForCurrentDepth().length - 1;
|
|
591
|
+
render();
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
state.view = "home";
|
|
595
|
+
render();
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
els.nextBtn.addEventListener("click", async () => {
|
|
599
|
+
if (state.view === "home") {
|
|
600
|
+
if (state.depth === "undecided") {
|
|
601
|
+
setStatus("error", "Choose Quick, Standard, or Complete to continue.");
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
state.view = "step";
|
|
605
|
+
state.stepIndex = 0;
|
|
606
|
+
render();
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
collectForm();
|
|
610
|
+
if (!validateCurrentStep()) return;
|
|
611
|
+
const step = currentStepDef();
|
|
612
|
+
try {
|
|
613
|
+
await saveDraft();
|
|
614
|
+
} catch (error) {
|
|
615
|
+
setStatus("error", error.message);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (step) {
|
|
619
|
+
await patchState({ currentSection: step.section, currentStep: state.stepIndex });
|
|
620
|
+
}
|
|
621
|
+
if (step?.section === "ide" && fieldValue("ideSurface")) {
|
|
622
|
+
await api("/api/checklist/ide", {
|
|
623
|
+
method: "POST",
|
|
624
|
+
headers: { "Content-Type": "application/json" },
|
|
625
|
+
body: JSON.stringify({ ideSurface: fieldValue("ideSurface") })
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
if (step?.section === "visualQa" && fieldValue("visualQaTier")) {
|
|
629
|
+
await api("/api/checklist/visual-qa", {
|
|
630
|
+
method: "POST",
|
|
631
|
+
headers: { "Content-Type": "application/json" },
|
|
632
|
+
body: JSON.stringify({ tier: fieldValue("visualQaTier") })
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
if (step?.section === "designDoc") {
|
|
636
|
+
await api("/api/drafts/design", {
|
|
637
|
+
method: "POST",
|
|
638
|
+
headers: { "Content-Type": "application/json" },
|
|
639
|
+
body: JSON.stringify({
|
|
640
|
+
audience: fieldValue("designAudience"),
|
|
641
|
+
contentInventory: fieldValue("designContent"),
|
|
642
|
+
antiReferences: fieldValue("designAntiReferences")
|
|
643
|
+
})
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
if (step?.section === "messagingDoc") {
|
|
647
|
+
await api("/api/drafts/messaging", {
|
|
648
|
+
method: "POST",
|
|
649
|
+
headers: { "Content-Type": "application/json" },
|
|
650
|
+
body: JSON.stringify({
|
|
651
|
+
audience: fieldValue("msgAudience"),
|
|
652
|
+
pain: fieldValue("msgPain"),
|
|
653
|
+
outcome: fieldValue("msgOutcome")
|
|
654
|
+
})
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
if (step?.section === "applyDrafts") {
|
|
658
|
+
await api("/api/drafts/apply", { method: "POST" });
|
|
659
|
+
}
|
|
660
|
+
if (state.stepIndex >= stepsForCurrentDepth().length - 1) {
|
|
661
|
+
state.view = "review";
|
|
662
|
+
render();
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
state.stepIndex += 1;
|
|
666
|
+
render();
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
els.saveBtn.addEventListener("click", async () => {
|
|
670
|
+
collectForm();
|
|
671
|
+
setStatus("", "Saving…");
|
|
672
|
+
els.saveBtn.disabled = true;
|
|
673
|
+
try {
|
|
674
|
+
await api("/api/context", {
|
|
675
|
+
method: "POST",
|
|
676
|
+
headers: { "Content-Type": "application/json" },
|
|
677
|
+
body: JSON.stringify(state.form)
|
|
678
|
+
});
|
|
679
|
+
state.view = "complete";
|
|
680
|
+
setStatus("ok", "Saved to .agent-kit/project-context.json");
|
|
681
|
+
render();
|
|
682
|
+
} catch (error) {
|
|
683
|
+
setStatus("error", error.message);
|
|
684
|
+
} finally {
|
|
685
|
+
els.saveBtn.disabled = false;
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
const resume = state.onboarding?.lastVisitedAt && state.progress?.percent > 0;
|
|
690
|
+
state.view = resume ? "home" : "home";
|
|
691
|
+
loadAll().catch((e) => setStatus("error", e.message));
|
|
692
|
+
})();
|