@appsforgood/next-supabase-kit 0.1.6 → 0.1.8

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.
@@ -600,6 +600,36 @@ code {
600
600
  max-height: calc(100vh - 64px);
601
601
  }
602
602
 
603
+ .studio-controls {
604
+ display: grid;
605
+ gap: 10px;
606
+ margin-bottom: 12px;
607
+ padding-bottom: 12px;
608
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
609
+ }
610
+
611
+ .studio-label {
612
+ font-size: 12px;
613
+ color: #94a3b8;
614
+ }
615
+
616
+ .studio-note-form {
617
+ display: grid;
618
+ gap: 8px;
619
+ }
620
+
621
+ .studio-note-form input,
622
+ .studio-note-form select,
623
+ #session-picker {
624
+ width: 100%;
625
+ padding: 8px 10px;
626
+ border-radius: 8px;
627
+ border: 1px solid rgba(255, 255, 255, 0.12);
628
+ background: #0f172a;
629
+ color: #e2e8f0;
630
+ font-size: 13px;
631
+ }
632
+
603
633
  .transcript-panel h2 {
604
634
  margin: 0 0 8px;
605
635
  font-size: 14px;
@@ -35,6 +35,7 @@
35
35
  handoffPulse: null,
36
36
  studioSessionId: boot.activeSessionId || "",
37
37
  studioEvents: [],
38
+ studioEventSource: null,
38
39
  speechBubbles: [],
39
40
  agenticLevel: null
40
41
  };
@@ -71,7 +72,12 @@
71
72
  bubbleLayer: document.getElementById("bubble-layer"),
72
73
  officeHint: document.getElementById("office-hint"),
73
74
  canvasWrap: document.querySelector(".canvas-wrap"),
74
- transcriptList: document.getElementById("transcript-list")
75
+ transcriptList: document.getElementById("transcript-list"),
76
+ sessionPicker: document.getElementById("session-picker"),
77
+ studioNoteForm: document.getElementById("studio-note-form"),
78
+ studioNoteAgent: document.getElementById("studio-note-agent"),
79
+ studioNoteText: document.getElementById("studio-note-text"),
80
+ studioRenderBtn: document.getElementById("studio-render-btn")
75
81
  };
76
82
 
77
83
  const ctx = els.canvas?.getContext("2d");
@@ -210,11 +216,15 @@
210
216
 
211
217
  async function loadStudioState() {
212
218
  const sessionsRes = await api("/api/sessions");
213
- state.studioSessionId = sessionsRes.activeSessionId || boot.activeSessionId || "";
219
+ const sessions = sessionsRes.sessions || [];
220
+ state.studioSessionId = sessionsRes.activeSessionId || boot.activeSessionId || sessions[0]?.sessionId || "";
221
+ populateSessionPicker(sessions, state.studioSessionId);
222
+ populateStudioAgents();
214
223
  if (els.sessionPill) {
215
224
  els.sessionPill.textContent = state.studioSessionId ? state.studioSessionId.slice(0, 24) : "No session";
216
225
  }
217
- if (els.projectName) els.projectName.textContent = sessionsRes.sessions?.[0]?.title || "Council session";
226
+ const activeSession = sessions.find((s) => s.sessionId === state.studioSessionId);
227
+ if (els.projectName) els.projectName.textContent = activeSession?.title || "Council session";
218
228
  if (els.officeHint) {
219
229
  els.officeHint.classList.remove("hidden");
220
230
  window.setTimeout(() => els.officeHint?.classList.add("hidden"), 6000);
@@ -222,10 +232,94 @@
222
232
  connectStudioStream();
223
233
  }
224
234
 
235
+ function populateSessionPicker(sessions, selectedId) {
236
+ if (!els.sessionPicker) return;
237
+ els.sessionPicker.innerHTML = sessions.length
238
+ ? sessions
239
+ .map(
240
+ (s) =>
241
+ '<option value="' +
242
+ escapeHtml(s.sessionId) +
243
+ '"' +
244
+ (s.sessionId === selectedId ? " selected" : "") +
245
+ ">" +
246
+ escapeHtml((s.title || s.sessionId).slice(0, 48)) +
247
+ "</option>"
248
+ )
249
+ .join("")
250
+ : '<option value="">No sessions</option>';
251
+ }
252
+
253
+ function populateStudioAgents() {
254
+ if (!els.studioNoteAgent) return;
255
+ els.studioNoteAgent.innerHTML = state.agents.length
256
+ ? state.agents.map((a) => '<option value="' + escapeHtml(a.id) + '">' + escapeHtml(a.name || a.id) + "</option>").join("")
257
+ : '<option value="planner">Planner</option>';
258
+ }
259
+
260
+ function bindStudioControls() {
261
+ if (els.sessionPicker) {
262
+ els.sessionPicker.addEventListener("change", () => {
263
+ state.studioSessionId = els.sessionPicker.value;
264
+ if (els.sessionPill) {
265
+ els.sessionPill.textContent = state.studioSessionId ? state.studioSessionId.slice(0, 24) : "No session";
266
+ }
267
+ connectStudioStream();
268
+ });
269
+ }
270
+ if (els.studioNoteForm) {
271
+ els.studioNoteForm.addEventListener("submit", async (event) => {
272
+ event.preventDefault();
273
+ if (!state.studioSessionId) {
274
+ setStatus("error", "Select a session first.");
275
+ return;
276
+ }
277
+ const agent = els.studioNoteAgent?.value || "planner";
278
+ const text = (els.studioNoteText?.value || "").trim();
279
+ if (!text) {
280
+ setStatus("error", "Enter note text.");
281
+ return;
282
+ }
283
+ try {
284
+ await api("/api/sessions/" + encodeURIComponent(state.studioSessionId) + "/note", {
285
+ method: "POST",
286
+ headers: { "Content-Type": "application/json" },
287
+ body: JSON.stringify({ agent, text })
288
+ });
289
+ if (els.studioNoteText) els.studioNoteText.value = "";
290
+ setStatus("ok", "Note recorded.");
291
+ } catch (error) {
292
+ setStatus("error", error.message);
293
+ }
294
+ });
295
+ }
296
+ if (els.studioRenderBtn) {
297
+ els.studioRenderBtn.addEventListener("click", async () => {
298
+ if (!state.studioSessionId) {
299
+ setStatus("error", "Select a session first.");
300
+ return;
301
+ }
302
+ try {
303
+ const result = await api("/api/sessions/" + encodeURIComponent(state.studioSessionId) + "/render", {
304
+ method: "POST"
305
+ });
306
+ setStatus("ok", "Rendered " + (result.files?.length || 0) + " markdown files.");
307
+ } catch (error) {
308
+ setStatus("error", error.message);
309
+ }
310
+ });
311
+ }
312
+ }
313
+
225
314
  function connectStudioStream() {
226
315
  if (!state.studioSessionId) return;
316
+ if (state.studioEventSource) {
317
+ state.studioEventSource.close();
318
+ state.studioEventSource = null;
319
+ }
227
320
  const url = "/api/events/stream?sessionId=" + encodeURIComponent(state.studioSessionId);
228
321
  const source = new EventSource(url);
322
+ state.studioEventSource = source;
229
323
  source.addEventListener("snapshot", (ev) => {
230
324
  try {
231
325
  const payload = JSON.parse(ev.data);
@@ -249,6 +343,7 @@
249
343
  });
250
344
  source.onerror = () => {
251
345
  source.close();
346
+ if (state.studioEventSource === source) state.studioEventSource = null;
252
347
  };
253
348
  }
254
349
 
@@ -1119,6 +1214,7 @@
1119
1214
  }
1120
1215
 
1121
1216
  loop();
1217
+ if (isStudio) bindStudioControls();
1122
1218
  loadState().catch((error) => setStatus("error", error.message));
1123
1219
  window.addEventListener("resize", () => {
1124
1220
  renderNameplates();
@@ -96,13 +96,7 @@
96
96
  "voice-tone",
97
97
  "pricing-copy"
98
98
  ],
99
- "skills": [
100
- "positioning-messaging",
101
- "conversion-copywriting",
102
- "landing-page-copy",
103
- "product-voice-tone",
104
- "onboarding-empty-state-copy"
105
- ],
99
+ "skills": ["positioning-messaging", "conversion-copywriting", "landing-page-copy", "product-voice-tone", "onboarding-empty-state-copy"],
106
100
  "handsOffTo": ["frontend-design-lead", "nextjs-engineer", "qa-engineer", "docs-maintainer"]
107
101
  },
108
102
  {
@@ -117,7 +111,7 @@
117
111
  "id": "qa-engineer",
118
112
  "name": "QA Engineer",
119
113
  "file": ".agent-kit/agents/qa-engineer.md",
120
- "defaultFor": ["testing", "regression", "smoke", "acceptance-evidence"],
114
+ "defaultFor": ["testing", "regression", "smoke", "acceptance-evidence", "test", "review", "code-review"],
121
115
  "skills": ["testing-qa", "best-practice-maturity-review", "visual-regression-qa", "accessibility-wcag"],
122
116
  "handsOffTo": ["docs-maintainer", "deployment-observability-engineer"]
123
117
  },
@@ -141,14 +135,26 @@
141
135
  "workflows": [
142
136
  {
143
137
  "id": "planning",
144
- "triggers": ["plan", "roadmap", "phase", "scope", "what should we do", "break this down"],
138
+ "triggers": ["plan", "roadmap", "phase", "scope", "spec", "specification", "acceptance criteria", "what should we do", "break this down"],
145
139
  "sequence": ["planner", "lead-architect", "qa-engineer", "docs-maintainer"],
146
140
  "council": ["planner", "lead-architect"],
147
141
  "requiredOutputs": ["phased checklist", "maturity target", "affected layers", "preserved capabilities", "verification plan", "docs impact"]
148
142
  },
149
143
  {
150
144
  "id": "core-change",
151
- "triggers": ["schema", "auth", "rls", "api", "route handler", "server action", "dependency", "upgrade", "release workflow", "package behavior", "cross-layer"],
145
+ "triggers": [
146
+ "schema",
147
+ "auth",
148
+ "rls",
149
+ "api",
150
+ "route handler",
151
+ "server action",
152
+ "dependency",
153
+ "upgrade",
154
+ "release workflow",
155
+ "package behavior",
156
+ "cross-layer"
157
+ ],
152
158
  "sequence": [
153
159
  "planner",
154
160
  "lead-architect",
@@ -195,7 +201,22 @@
195
201
  },
196
202
  {
197
203
  "id": "marketing-copy",
198
- "triggers": ["copy", "copywriting", "marketing", "positioning", "messaging", "value prop", "value proposition", "landing page", "headline", "cta", "conversion", "onboarding", "empty state", "pricing"],
204
+ "triggers": [
205
+ "copy",
206
+ "copywriting",
207
+ "marketing",
208
+ "positioning",
209
+ "messaging",
210
+ "value prop",
211
+ "value proposition",
212
+ "landing page",
213
+ "headline",
214
+ "cta",
215
+ "conversion",
216
+ "onboarding",
217
+ "empty state",
218
+ "pricing"
219
+ ],
199
220
  "sequence": ["planner", "marketing-copy-lead", "frontend-design-lead", "qa-engineer", "docs-maintainer"],
200
221
  "council": ["marketing-copy-lead", "frontend-design-lead"],
201
222
  "requiredOutputs": [
@@ -209,6 +230,20 @@
209
230
  "handoff notes for design and implementation"
210
231
  ]
211
232
  },
233
+ {
234
+ "id": "testing",
235
+ "triggers": ["test", "tests", "unit test", "regression test", "smoke test", "playwright", "acceptance test"],
236
+ "sequence": ["qa-engineer", "docs-maintainer"],
237
+ "council": ["qa-engineer"],
238
+ "requiredOutputs": ["test plan", "commands run", "pass/fail summary", "coverage gaps", "skipped-test rationale"]
239
+ },
240
+ {
241
+ "id": "code-review",
242
+ "triggers": ["review", "code review", "pre-merge", "pr review", "merge review"],
243
+ "sequence": ["qa-engineer", "security-reviewer", "docs-maintainer"],
244
+ "council": ["qa-engineer", "security-reviewer"],
245
+ "requiredOutputs": ["reviewed scope", "findings by severity", "required fixes", "security notes when applicable", "merge recommendation"]
246
+ },
212
247
  {
213
248
  "id": "security-review",
214
249
  "triggers": ["security", "owasp", "secret", "token", "permission", "ssrf", "idor", "dependency"],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "packageName": "@appsforgood/next-supabase-kit",
3
- "packageVersion": "0.1.6",
3
+ "packageVersion": "0.1.8",
4
4
  "stack": "next-supabase",
5
- "installedAt": "2026-06-17T11:49:38.732Z",
5
+ "installedAt": "2026-07-04T01:32:06.844Z",
6
6
  "docs": [
7
7
  "AGENTS.md",
8
8
  "AGENT_ROSTER.md",