@burdenoff/microfe-technospam 2026.1030.6 → 2026.1031.2

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.
Files changed (52) hide show
  1. package/dist/technospam/api/technospamApi.d.ts.map +1 -1
  2. package/dist/technospam/api/technospamApi.js +254 -253
  3. package/dist/technospam/api/technospamApi.js.map +1 -1
  4. package/dist/technospam/components/StatusBadge.js +1 -1
  5. package/dist/technospam/components/StatusBadge.js.map +1 -1
  6. package/dist/technospam/components/SubmissionViews.js +1 -1
  7. package/dist/technospam/components/SubmissionViews.js.map +1 -1
  8. package/dist/technospam/components/lesson/InteractiveWorkspace.d.ts.map +1 -1
  9. package/dist/technospam/components/lesson/InteractiveWorkspace.js +88 -81
  10. package/dist/technospam/components/lesson/InteractiveWorkspace.js.map +1 -1
  11. package/dist/technospam/lib/errorMessage.d.ts +7 -0
  12. package/dist/technospam/lib/errorMessage.d.ts.map +1 -0
  13. package/dist/technospam/lib/errorMessage.js +21 -0
  14. package/dist/technospam/lib/errorMessage.js.map +1 -0
  15. package/dist/technospam/lib/toast.js +1 -1
  16. package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
  17. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +194 -186
  18. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  19. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  20. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js +5 -2
  21. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  22. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js +1 -0
  23. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js.map +1 -1
  24. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +2 -1
  25. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  26. package/dist/technospam/pages/ApplicationsProblemsPagePage.d.ts.map +1 -1
  27. package/dist/technospam/pages/ApplicationsProblemsPagePage.js +167 -162
  28. package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
  29. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +2 -2
  30. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  31. package/dist/technospam/pages/CoursesCoursesPagePage.d.ts.map +1 -1
  32. package/dist/technospam/pages/CoursesCoursesPagePage.js +77 -77
  33. package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
  34. package/dist/technospam/pages/CoursesLessonPlayerPagePage.js +2 -2
  35. package/dist/technospam/pages/CoursesLessonPlayerPagePage.js.map +1 -1
  36. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +3 -3
  37. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
  38. package/dist/technospam/pages/HackathonsHackathonsPagePage.d.ts.map +1 -1
  39. package/dist/technospam/pages/HackathonsHackathonsPagePage.js +71 -71
  40. package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
  41. package/dist/technospam/pages/MentorshipMentorProfilePagePage.d.ts.map +1 -1
  42. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +6 -1
  43. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
  44. package/dist/technospam/pages/MentorshipMentorsPagePage.d.ts.map +1 -1
  45. package/dist/technospam/pages/MentorshipMentorsPagePage.js +1 -0
  46. package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
  47. package/dist/technospam/pages/MentorshipSessionsPagePage.d.ts.map +1 -1
  48. package/dist/technospam/pages/MentorshipSessionsPagePage.js +97 -98
  49. package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
  50. package/package.json +9 -7
  51. package/dist/node_modules/sonner/dist/index.js +0 -600
  52. package/dist/node_modules/sonner/dist/index.js.map +0 -1
@@ -1,14 +1,15 @@
1
1
  import { cn as e } from "../../utils/cn.js";
2
2
  import { humanizeEnum as t } from "../../utils/format.js";
3
3
  import { IoBlock as n } from "../SubmissionViews.js";
4
- import { useRunLessonCode as r } from "../../api/technospamApi.js";
5
- import { useEffect as i, useRef as a, useState as o } from "react";
6
- import { CheckCircle2 as s, Clock as c, Code2 as l, Loader2 as u, Play as d, RotateCcw as f, Terminal as p, XCircle as m } from "lucide-react";
7
- import { jsx as h, jsxs as g } from "react/jsx-runtime";
8
- import { Button as _, Textarea as v } from "@burdenoff/fe-libs/ui";
4
+ import { userMessageFromError as r } from "../../lib/errorMessage.js";
5
+ import { useRunLessonCode as i } from "../../api/technospamApi.js";
6
+ import { useEffect as a, useRef as o, useState as s } from "react";
7
+ import { CheckCircle2 as c, Clock as l, Code2 as u, Loader2 as d, Play as f, RotateCcw as p, Terminal as m, XCircle as h } from "lucide-react";
8
+ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
9
+ import { Button as v, Textarea as y } from "@burdenoff/fe-libs/ui";
9
10
  //#region src/technospam/components/lesson/InteractiveWorkspace.tsx
10
- var y = 4e3, b = 4 * 6e4;
11
- function x(e) {
11
+ var b = 4e3, x = 4 * 6e4;
12
+ function S(e) {
12
13
  return e.compileFailed ? {
13
14
  tone: "error",
14
15
  text: "Compile error"
@@ -32,160 +33,166 @@ function x(e) {
32
33
  text: `Exited with code ${e.exitCode}`
33
34
  };
34
35
  }
35
- function S({ lesson: S, onCompleted: C }) {
36
- let w = S.starterCode ?? "", [T, E] = o(w), [D, O] = o(S.sampleInput ?? ""), [k, A] = o(null), [j, M] = o(!1), [N, P] = o(!1), F = a(null), I = a(null), [L] = r();
37
- i(() => () => {
38
- I.current && clearTimeout(I.current);
36
+ function C({ lesson: C, onCompleted: w }) {
37
+ let T = C.starterCode ?? "", [E, D] = s(T), [O, k] = s(C.sampleInput ?? ""), [A, j] = s(null), [M, N] = s(!1), [P, F] = s(!1), [I, L] = s(null), R = o(null), z = o(null), [B] = i();
38
+ a(() => () => {
39
+ z.current && clearTimeout(z.current);
39
40
  }, []);
40
- let R = async () => {
41
- P(!0);
41
+ let V = async () => {
42
+ F(!0), L(null);
42
43
  try {
43
- let e = (await L({ variables: { input: {
44
- lessonId: S.id,
45
- language: S.language ?? void 0,
46
- code: T,
47
- stdin: D
48
- } } })).data?.tecRunLessonCode;
49
- if (!e) {
50
- M(!1), F.current = null;
44
+ let e = await B({ variables: { input: {
45
+ lessonId: C.id,
46
+ language: C.language ?? void 0,
47
+ code: E,
48
+ stdin: O
49
+ } } }), t = e.data?.tecRunLessonCode;
50
+ if (!t) {
51
+ N(!1), R.current = null, j(null), L(r(e.error, "Your code could not be run. Please try again."));
51
52
  return;
52
53
  }
53
- if (e.status === "WARMING") {
54
- if (F.current ??= Date.now(), Date.now() - F.current < b) {
55
- M(!0), I.current = setTimeout(() => void R(), y);
54
+ if (t.status === "WARMING") {
55
+ if (R.current ??= Date.now(), Date.now() - R.current < x) {
56
+ N(!0), z.current = setTimeout(() => void V(), b);
56
57
  return;
57
58
  }
58
- M(!1), F.current = null, A({
59
- ...e,
59
+ N(!1), R.current = null, j({
60
+ ...t,
60
61
  status: "UNAVAILABLE"
61
62
  });
62
63
  return;
63
64
  }
64
- M(!1), F.current = null, A(e), e.lessonCompleted && C();
65
+ N(!1), R.current = null, j(t), t.lessonCompleted && w();
65
66
  } finally {
66
- P(!1);
67
+ F(!1);
67
68
  }
68
- }, z = N || j, B = k && k.status === "COMPLETED" ? x(k) : null;
69
- return /* @__PURE__ */ g("div", {
69
+ }, H = P || M, U = A && A.status === "COMPLETED" ? S(A) : null;
70
+ return /* @__PURE__ */ _("div", {
70
71
  className: "space-y-4",
71
72
  children: [
72
- /* @__PURE__ */ g("div", {
73
+ /* @__PURE__ */ _("div", {
73
74
  className: "overflow-hidden rounded-xl border border-border-strong bg-bg-sunken shadow-sm",
74
- children: [/* @__PURE__ */ g("div", {
75
+ children: [/* @__PURE__ */ _("div", {
75
76
  className: "flex items-center justify-between gap-2 border-b border-border-seam bg-bg-canvas/40 px-4 py-2",
76
- children: [/* @__PURE__ */ g("div", {
77
+ children: [/* @__PURE__ */ _("div", {
77
78
  className: "flex items-center gap-1.5",
78
79
  children: [
79
- /* @__PURE__ */ h("span", { className: "h-2.5 w-2.5 rounded-full bg-status-error-border" }),
80
- /* @__PURE__ */ h("span", { className: "h-2.5 w-2.5 rounded-full bg-status-warning-border" }),
81
- /* @__PURE__ */ h("span", { className: "h-2.5 w-2.5 rounded-full bg-status-success-border" })
80
+ /* @__PURE__ */ g("span", { className: "h-2.5 w-2.5 rounded-full bg-status-error-border" }),
81
+ /* @__PURE__ */ g("span", { className: "h-2.5 w-2.5 rounded-full bg-status-warning-border" }),
82
+ /* @__PURE__ */ g("span", { className: "h-2.5 w-2.5 rounded-full bg-status-success-border" })
82
83
  ]
83
- }), /* @__PURE__ */ g("span", {
84
- className: "inline-flex items-center gap-1.5 font-mono text-xs text-text-muted",
84
+ }), /* @__PURE__ */ _("span", {
85
+ className: "inline-flex items-center gap-1.5 font-mono text-xs text-text-secondary",
85
86
  children: [
86
- /* @__PURE__ */ h(l, { size: 13 }),
87
+ /* @__PURE__ */ g(u, { size: 13 }),
87
88
  " ",
88
- S.language ? t(S.language) : "code"
89
+ C.language ? t(C.language) : "code"
89
90
  ]
90
91
  })]
91
- }), /* @__PURE__ */ h(v, {
92
+ }), /* @__PURE__ */ g(y, {
92
93
  "aria-label": "Lesson code editor",
93
- value: T,
94
- onChange: (e) => E(e.target.value),
94
+ value: E,
95
+ onChange: (e) => D(e.target.value),
95
96
  placeholder: "Write your code…",
96
97
  className: "min-h-[260px] resize-y rounded-none border-0 bg-transparent font-mono text-xs leading-relaxed focus-visible:ring-0 focus-visible:ring-offset-0",
97
98
  spellCheck: !1
98
99
  })]
99
100
  }),
100
- /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
101
- htmlFor: `tec-lesson-stdin-${S.id}`,
101
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
102
+ htmlFor: `tec-lesson-stdin-${C.id}`,
102
103
  className: "mb-1 block text-xs font-medium uppercase tracking-wide text-text-muted",
103
104
  children: "Input (stdin)"
104
- }), /* @__PURE__ */ h(v, {
105
- id: `tec-lesson-stdin-${S.id}`,
106
- value: D,
107
- onChange: (e) => O(e.target.value),
105
+ }), /* @__PURE__ */ g(y, {
106
+ id: `tec-lesson-stdin-${C.id}`,
107
+ value: O,
108
+ onChange: (e) => k(e.target.value),
108
109
  placeholder: "Optional input your program reads",
109
110
  className: "min-h-[64px] font-mono text-xs",
110
111
  spellCheck: !1
111
112
  })] }),
112
- /* @__PURE__ */ g("div", {
113
+ /* @__PURE__ */ _("div", {
113
114
  className: "flex flex-wrap items-center gap-2",
114
115
  children: [
115
- /* @__PURE__ */ g(_, {
116
- onClick: () => void R(),
117
- disabled: z || !T.trim(),
118
- children: [z ? /* @__PURE__ */ h(u, {
116
+ /* @__PURE__ */ _(v, {
117
+ onClick: () => void V(),
118
+ disabled: H || !E.trim(),
119
+ children: [H ? /* @__PURE__ */ g(d, {
119
120
  size: 16,
120
121
  className: "animate-spin"
121
- }) : /* @__PURE__ */ h(d, { size: 16 }), j ? "Starting sandbox…" : N ? "Running…" : "Run code"]
122
+ }) : /* @__PURE__ */ g(f, { size: 16 }), M ? "Starting sandbox…" : P ? "Running…" : "Run code"]
122
123
  }),
123
- /* @__PURE__ */ g(_, {
124
+ /* @__PURE__ */ _(v, {
124
125
  variant: "outline",
125
126
  onClick: () => {
126
- E(w), A(null);
127
+ D(T), j(null), L(null);
127
128
  },
128
- disabled: z || T === w,
129
- children: [/* @__PURE__ */ h(f, { size: 15 }), " Reset to starter"]
129
+ disabled: H || E === T,
130
+ children: [/* @__PURE__ */ g(p, { size: 15 }), " Reset to starter"]
130
131
  }),
131
- /* @__PURE__ */ h("span", {
132
+ /* @__PURE__ */ g("span", {
132
133
  className: "text-xs text-text-muted",
133
134
  children: "Runs in an isolated sandbox."
134
135
  })
135
136
  ]
136
137
  }),
137
- j ? /* @__PURE__ */ h("p", {
138
+ M ? /* @__PURE__ */ g("p", {
138
139
  role: "status",
139
140
  className: "rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary",
140
141
  children: "The code sandbox is starting up. Your code will run automatically as soon as it is ready."
141
142
  }) : null,
142
- k?.status === "UNAVAILABLE" ? /* @__PURE__ */ h("p", {
143
+ I ? /* @__PURE__ */ g("p", {
144
+ role: "alert",
145
+ "data-testid": "lesson-run-error",
146
+ className: "rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
147
+ children: I
148
+ }) : null,
149
+ A?.status === "UNAVAILABLE" ? /* @__PURE__ */ g("p", {
143
150
  role: "alert",
144
151
  className: "rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
145
152
  children: "The code sandbox is not available right now. Please try again in a moment."
146
153
  }) : null,
147
- k && B ? /* @__PURE__ */ g("div", {
154
+ A && U ? /* @__PURE__ */ _("div", {
148
155
  className: "space-y-3 rounded-xl border border-border-seam p-4",
149
156
  "data-testid": "lesson-run-output",
150
157
  children: [
151
- /* @__PURE__ */ g("div", {
158
+ /* @__PURE__ */ _("div", {
152
159
  className: "flex flex-wrap items-center gap-3",
153
160
  children: [
154
- /* @__PURE__ */ g("span", {
155
- className: e("inline-flex items-center gap-1.5 text-sm font-medium", B.tone === "success" && "text-status-success-text", B.tone === "error" && "text-status-error-text", B.tone === "neutral" && "text-text-primary"),
156
- children: [B.tone === "error" ? /* @__PURE__ */ h(m, { size: 16 }) : /* @__PURE__ */ h(s, { size: 16 }), B.text]
161
+ /* @__PURE__ */ _("span", {
162
+ className: e("inline-flex items-center gap-1.5 text-sm font-medium", U.tone === "success" && "text-status-success-text", U.tone === "error" && "text-status-error-text", U.tone === "neutral" && "text-text-primary"),
163
+ children: [U.tone === "error" ? /* @__PURE__ */ g(h, { size: 16 }) : /* @__PURE__ */ g(c, { size: 16 }), U.text]
157
164
  }),
158
- /* @__PURE__ */ g("span", {
165
+ /* @__PURE__ */ _("span", {
159
166
  className: "inline-flex items-center gap-1 text-xs text-text-muted",
160
167
  children: [
161
- /* @__PURE__ */ h(c, { size: 12 }),
168
+ /* @__PURE__ */ g(l, { size: 12 }),
162
169
  " ",
163
- k.durationMs,
170
+ A.durationMs,
164
171
  " ms"
165
172
  ]
166
173
  }),
167
- k.lessonCompleted ? /* @__PURE__ */ h("span", {
174
+ A.lessonCompleted ? /* @__PURE__ */ g("span", {
168
175
  className: "rounded-full border border-status-success-border bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text",
169
176
  children: "Lesson completed"
170
177
  }) : null
171
178
  ]
172
179
  }),
173
- /* @__PURE__ */ g("div", {
180
+ /* @__PURE__ */ _("div", {
174
181
  className: "grid grid-cols-1 gap-3 md:grid-cols-2",
175
- children: [/* @__PURE__ */ h(n, {
182
+ children: [/* @__PURE__ */ g(n, {
176
183
  label: "Output",
177
- value: k.stdout
178
- }), S.expectedOutput ? /* @__PURE__ */ h(n, {
184
+ value: A.stdout
185
+ }), C.expectedOutput ? /* @__PURE__ */ g(n, {
179
186
  label: "Expected output",
180
- value: S.expectedOutput
187
+ value: C.expectedOutput
181
188
  }) : null]
182
189
  }),
183
- k.stderr ? /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("p", {
190
+ A.stderr ? /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("p", {
184
191
  className: "mb-1 flex items-center gap-1 text-xs font-medium uppercase tracking-wide text-text-muted",
185
- children: [/* @__PURE__ */ h(p, { size: 12 }), " Errors"]
186
- }), /* @__PURE__ */ h("pre", {
192
+ children: [/* @__PURE__ */ g(m, { size: 12 }), " Errors"]
193
+ }), /* @__PURE__ */ g("pre", {
187
194
  className: "overflow-auto rounded-lg bg-status-error-bg-subtle px-3 py-2 font-mono text-xs text-status-error-text",
188
- children: k.stderr
195
+ children: A.stderr
189
196
  })] }) : null
190
197
  ]
191
198
  }) : null
@@ -193,6 +200,6 @@ function S({ lesson: S, onCompleted: C }) {
193
200
  });
194
201
  }
195
202
  //#endregion
196
- export { S as InteractiveWorkspace };
203
+ export { C as InteractiveWorkspace };
197
204
 
198
205
  //# sourceMappingURL=InteractiveWorkspace.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"InteractiveWorkspace.js","names":[],"sources":["../../../../src/technospam/components/lesson/InteractiveWorkspace.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport {\n CheckCircle2,\n Clock,\n Code2,\n Loader2,\n Play,\n RotateCcw,\n Terminal,\n XCircle,\n} from 'lucide-react';\nimport { Button, Textarea } from '@burdenoff/fe-libs/ui';\nimport { IoBlock } from '../SubmissionViews';\nimport { humanizeEnum } from '../../utils/format';\nimport { cn } from '../../utils/cn';\nimport { useRunLessonCode, type TecLesson, type TecLessonCodeRun } from '../../api/technospamApi';\n\n/**\n * A cold code sandbox can take up to a few minutes to start. While the API\n * answers WARMING we call it again on this interval, giving up after the\n * budget below so the learner can retry by hand.\n */\nconst WARMING_RETRY_MS = 4_000;\nconst WARMING_BUDGET_MS = 4 * 60_000;\n\nfunction runSummary(run: TecLessonCodeRun): {\n tone: 'success' | 'error' | 'neutral';\n text: string;\n} {\n if (run.compileFailed) return { tone: 'error', text: 'Compile error' };\n if (run.timedOut) return { tone: 'error', text: 'Time limit exceeded' };\n if (run.memoryLimitExceeded) return { tone: 'error', text: 'Memory limit exceeded' };\n if (run.exitCode !== 0) return { tone: 'error', text: `Exited with code ${run.exitCode}` };\n if (run.matchedExpected === true)\n return { tone: 'success', text: 'Output matches the expected output' };\n if (run.matchedExpected === false) return { tone: 'error', text: 'Output does not match yet' };\n return { tone: 'neutral', text: 'Ran successfully' };\n}\n\n/**\n * Runnable workspace for an INTERACTIVE lesson (BOFF-4107). Mount it with\n * `key={lesson.id}` so moving to another lesson starts a fresh editor: an editor\n * pre-filled with the lesson's starter code, an optional stdin box, and the\n * run output. Code runs only in the platform sandbox via `tecRunLessonCode`;\n * when the lesson has an expected output, a matching run completes the lesson\n * server-side and `onCompleted` lets the page refresh progress.\n */\nexport function InteractiveWorkspace({\n lesson,\n onCompleted,\n}: {\n lesson: TecLesson;\n onCompleted: () => void;\n}) {\n const starter = lesson.starterCode ?? '';\n const [code, setCode] = useState(starter);\n const [stdin, setStdin] = useState(lesson.sampleInput ?? '');\n const [run, setRun] = useState<TecLessonCodeRun | null>(null);\n const [warming, setWarming] = useState(false);\n const [running, setRunning] = useState(false);\n const warmingSince = useRef<number | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const [runLessonCode] = useRunLessonCode();\n\n // The page mounts one workspace per lesson (keyed by lesson id), so the\n // editor starts fresh on every lesson; only the pending retry needs cleanup.\n useEffect(\n () => () => {\n if (timer.current) clearTimeout(timer.current);\n },\n []\n );\n\n const execute = async (): Promise<void> => {\n setRunning(true);\n try {\n const res = await runLessonCode({\n variables: {\n input: {\n lessonId: lesson.id,\n language: lesson.language ?? undefined,\n code,\n stdin,\n },\n },\n });\n const result = res.data?.tecRunLessonCode;\n if (!result) {\n // The mutation failed (for example a spent sandbox quota); the error\n // toast explains why, so stop any warm-up polling and re-enable Run.\n setWarming(false);\n warmingSince.current = null;\n return;\n }\n if (result.status === 'WARMING') {\n warmingSince.current ??= Date.now();\n if (Date.now() - warmingSince.current < WARMING_BUDGET_MS) {\n setWarming(true);\n timer.current = setTimeout(() => void execute(), WARMING_RETRY_MS);\n return;\n }\n setWarming(false);\n warmingSince.current = null;\n setRun({ ...result, status: 'UNAVAILABLE' });\n return;\n }\n setWarming(false);\n warmingSince.current = null;\n setRun(result);\n if (result.lessonCompleted) onCompleted();\n } finally {\n setRunning(false);\n }\n };\n\n const busy = running || warming;\n const summary = run && run.status === 'COMPLETED' ? runSummary(run) : null;\n\n return (\n <div className=\"space-y-4\">\n <div className=\"overflow-hidden rounded-xl border border-border-strong bg-bg-sunken shadow-sm\">\n <div className=\"flex items-center justify-between gap-2 border-b border-border-seam bg-bg-canvas/40 px-4 py-2\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-error-border\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-warning-border\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-success-border\" />\n </div>\n <span className=\"inline-flex items-center gap-1.5 font-mono text-xs text-text-muted\">\n <Code2 size={13} /> {lesson.language ? humanizeEnum(lesson.language) : 'code'}\n </span>\n </div>\n <Textarea\n aria-label=\"Lesson code editor\"\n value={code}\n onChange={(e) => setCode(e.target.value)}\n placeholder=\"Write your code…\"\n className=\"min-h-[260px] resize-y rounded-none border-0 bg-transparent font-mono text-xs leading-relaxed focus-visible:ring-0 focus-visible:ring-offset-0\"\n spellCheck={false}\n />\n </div>\n\n <div>\n <label\n htmlFor={`tec-lesson-stdin-${lesson.id}`}\n className=\"mb-1 block text-xs font-medium uppercase tracking-wide text-text-muted\"\n >\n Input (stdin)\n </label>\n <Textarea\n id={`tec-lesson-stdin-${lesson.id}`}\n value={stdin}\n onChange={(e) => setStdin(e.target.value)}\n placeholder=\"Optional input your program reads\"\n className=\"min-h-[64px] font-mono text-xs\"\n spellCheck={false}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button onClick={() => void execute()} disabled={busy || !code.trim()}>\n {busy ? <Loader2 size={16} className=\"animate-spin\" /> : <Play size={16} />}\n {warming ? 'Starting sandbox…' : running ? 'Running…' : 'Run code'}\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => {\n setCode(starter);\n setRun(null);\n }}\n disabled={busy || code === starter}\n >\n <RotateCcw size={15} /> Reset to starter\n </Button>\n <span className=\"text-xs text-text-muted\">Runs in an isolated sandbox.</span>\n </div>\n\n {warming ? (\n <p\n role=\"status\"\n className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\"\n >\n The code sandbox is starting up. Your code will run automatically as soon as it is ready.\n </p>\n ) : null}\n\n {run?.status === 'UNAVAILABLE' ? (\n <p\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n The code sandbox is not available right now. Please try again in a moment.\n </p>\n ) : null}\n\n {run && summary ? (\n <div\n className=\"space-y-3 rounded-xl border border-border-seam p-4\"\n data-testid=\"lesson-run-output\"\n >\n <div className=\"flex flex-wrap items-center gap-3\">\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 text-sm font-medium',\n summary.tone === 'success' && 'text-status-success-text',\n summary.tone === 'error' && 'text-status-error-text',\n summary.tone === 'neutral' && 'text-text-primary'\n )}\n >\n {summary.tone === 'error' ? <XCircle size={16} /> : <CheckCircle2 size={16} />}\n {summary.text}\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {run.durationMs} ms\n </span>\n {run.lessonCompleted ? (\n <span className=\"rounded-full border border-status-success-border bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text\">\n Lesson completed\n </span>\n ) : null}\n </div>\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n <IoBlock label=\"Output\" value={run.stdout} />\n {lesson.expectedOutput ? (\n <IoBlock label=\"Expected output\" value={lesson.expectedOutput} />\n ) : null}\n </div>\n {run.stderr ? (\n <div>\n <p className=\"mb-1 flex items-center gap-1 text-xs font-medium uppercase tracking-wide text-text-muted\">\n <Terminal size={12} /> Errors\n </p>\n <pre className=\"overflow-auto rounded-lg bg-status-error-bg-subtle px-3 py-2 font-mono text-xs text-status-error-text\">\n {run.stderr}\n </pre>\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"InteractiveWorkspace.js","names":[],"sources":["../../../../src/technospam/components/lesson/InteractiveWorkspace.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport {\n CheckCircle2,\n Clock,\n Code2,\n Loader2,\n Play,\n RotateCcw,\n Terminal,\n XCircle,\n} from 'lucide-react';\nimport { Button, Textarea } from '@burdenoff/fe-libs/ui';\nimport { IoBlock } from '../SubmissionViews';\nimport { humanizeEnum } from '../../utils/format';\nimport { cn } from '../../utils/cn';\nimport { userMessageFromError } from '../../lib/errorMessage';\nimport { useRunLessonCode, type TecLesson, type TecLessonCodeRun } from '../../api/technospamApi';\n\n/**\n * A cold code sandbox can take up to a few minutes to start. While the API\n * answers WARMING we call it again on this interval, giving up after the\n * budget below so the learner can retry by hand.\n */\nconst WARMING_RETRY_MS = 4_000;\nconst WARMING_BUDGET_MS = 4 * 60_000;\n\nfunction runSummary(run: TecLessonCodeRun): {\n tone: 'success' | 'error' | 'neutral';\n text: string;\n} {\n if (run.compileFailed) return { tone: 'error', text: 'Compile error' };\n if (run.timedOut) return { tone: 'error', text: 'Time limit exceeded' };\n if (run.memoryLimitExceeded) return { tone: 'error', text: 'Memory limit exceeded' };\n if (run.exitCode !== 0) return { tone: 'error', text: `Exited with code ${run.exitCode}` };\n if (run.matchedExpected === true)\n return { tone: 'success', text: 'Output matches the expected output' };\n if (run.matchedExpected === false) return { tone: 'error', text: 'Output does not match yet' };\n return { tone: 'neutral', text: 'Ran successfully' };\n}\n\n/**\n * Runnable workspace for an INTERACTIVE lesson (BOFF-4107). Mount it with\n * `key={lesson.id}` so moving to another lesson starts a fresh editor: an editor\n * pre-filled with the lesson's starter code, an optional stdin box, and the\n * run output. Code runs only in the platform sandbox via `tecRunLessonCode`;\n * when the lesson has an expected output, a matching run completes the lesson\n * server-side and `onCompleted` lets the page refresh progress.\n */\nexport function InteractiveWorkspace({\n lesson,\n onCompleted,\n}: {\n lesson: TecLesson;\n onCompleted: () => void;\n}) {\n const starter = lesson.starterCode ?? '';\n const [code, setCode] = useState(starter);\n const [stdin, setStdin] = useState(lesson.sampleInput ?? '');\n const [run, setRun] = useState<TecLessonCodeRun | null>(null);\n const [warming, setWarming] = useState(false);\n const [running, setRunning] = useState(false);\n /** Why the last run was refused (for example a spent sandbox quota), shown inline. */\n const [runError, setRunError] = useState<string | null>(null);\n const warmingSince = useRef<number | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const [runLessonCode] = useRunLessonCode();\n\n // The page mounts one workspace per lesson (keyed by lesson id), so the\n // editor starts fresh on every lesson; only the pending retry needs cleanup.\n useEffect(\n () => () => {\n if (timer.current) clearTimeout(timer.current);\n },\n []\n );\n\n const execute = async (): Promise<void> => {\n setRunning(true);\n setRunError(null);\n try {\n const res = await runLessonCode({\n variables: {\n input: {\n lessonId: lesson.id,\n language: lesson.language ?? undefined,\n code,\n stdin,\n },\n },\n });\n const result = res.data?.tecRunLessonCode;\n if (!result) {\n // The mutation failed (for example a spent sandbox quota). Stop any\n // warm-up polling, re-enable Run and say why next to the editor, where\n // the learner is looking, not only in a transient toast.\n setWarming(false);\n warmingSince.current = null;\n setRun(null);\n setRunError(\n userMessageFromError(res.error, 'Your code could not be run. Please try again.')\n );\n return;\n }\n if (result.status === 'WARMING') {\n warmingSince.current ??= Date.now();\n if (Date.now() - warmingSince.current < WARMING_BUDGET_MS) {\n setWarming(true);\n timer.current = setTimeout(() => void execute(), WARMING_RETRY_MS);\n return;\n }\n setWarming(false);\n warmingSince.current = null;\n setRun({ ...result, status: 'UNAVAILABLE' });\n return;\n }\n setWarming(false);\n warmingSince.current = null;\n setRun(result);\n if (result.lessonCompleted) onCompleted();\n } finally {\n setRunning(false);\n }\n };\n\n const busy = running || warming;\n const summary = run && run.status === 'COMPLETED' ? runSummary(run) : null;\n\n return (\n <div className=\"space-y-4\">\n <div className=\"overflow-hidden rounded-xl border border-border-strong bg-bg-sunken shadow-sm\">\n <div className=\"flex items-center justify-between gap-2 border-b border-border-seam bg-bg-canvas/40 px-4 py-2\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-error-border\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-warning-border\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-success-border\" />\n </div>\n <span className=\"inline-flex items-center gap-1.5 font-mono text-xs text-text-secondary\">\n <Code2 size={13} /> {lesson.language ? humanizeEnum(lesson.language) : 'code'}\n </span>\n </div>\n <Textarea\n aria-label=\"Lesson code editor\"\n value={code}\n onChange={(e) => setCode(e.target.value)}\n placeholder=\"Write your code…\"\n className=\"min-h-[260px] resize-y rounded-none border-0 bg-transparent font-mono text-xs leading-relaxed focus-visible:ring-0 focus-visible:ring-offset-0\"\n spellCheck={false}\n />\n </div>\n\n <div>\n <label\n htmlFor={`tec-lesson-stdin-${lesson.id}`}\n className=\"mb-1 block text-xs font-medium uppercase tracking-wide text-text-muted\"\n >\n Input (stdin)\n </label>\n <Textarea\n id={`tec-lesson-stdin-${lesson.id}`}\n value={stdin}\n onChange={(e) => setStdin(e.target.value)}\n placeholder=\"Optional input your program reads\"\n className=\"min-h-[64px] font-mono text-xs\"\n spellCheck={false}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button onClick={() => void execute()} disabled={busy || !code.trim()}>\n {busy ? <Loader2 size={16} className=\"animate-spin\" /> : <Play size={16} />}\n {warming ? 'Starting sandbox…' : running ? 'Running…' : 'Run code'}\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => {\n setCode(starter);\n setRun(null);\n setRunError(null);\n }}\n disabled={busy || code === starter}\n >\n <RotateCcw size={15} /> Reset to starter\n </Button>\n <span className=\"text-xs text-text-muted\">Runs in an isolated sandbox.</span>\n </div>\n\n {warming ? (\n <p\n role=\"status\"\n className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\"\n >\n The code sandbox is starting up. Your code will run automatically as soon as it is ready.\n </p>\n ) : null}\n\n {runError ? (\n <p\n role=\"alert\"\n data-testid=\"lesson-run-error\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {runError}\n </p>\n ) : null}\n\n {run?.status === 'UNAVAILABLE' ? (\n <p\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n The code sandbox is not available right now. Please try again in a moment.\n </p>\n ) : null}\n\n {run && summary ? (\n <div\n className=\"space-y-3 rounded-xl border border-border-seam p-4\"\n data-testid=\"lesson-run-output\"\n >\n <div className=\"flex flex-wrap items-center gap-3\">\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 text-sm font-medium',\n summary.tone === 'success' && 'text-status-success-text',\n summary.tone === 'error' && 'text-status-error-text',\n summary.tone === 'neutral' && 'text-text-primary'\n )}\n >\n {summary.tone === 'error' ? <XCircle size={16} /> : <CheckCircle2 size={16} />}\n {summary.text}\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {run.durationMs} ms\n </span>\n {run.lessonCompleted ? (\n <span className=\"rounded-full border border-status-success-border bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text\">\n Lesson completed\n </span>\n ) : null}\n </div>\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n <IoBlock label=\"Output\" value={run.stdout} />\n {lesson.expectedOutput ? (\n <IoBlock label=\"Expected output\" value={lesson.expectedOutput} />\n ) : null}\n </div>\n {run.stderr ? (\n <div>\n <p className=\"mb-1 flex items-center gap-1 text-xs font-medium uppercase tracking-wide text-text-muted\">\n <Terminal size={12} /> Errors\n </p>\n <pre className=\"overflow-auto rounded-lg bg-status-error-bg-subtle px-3 py-2 font-mono text-xs text-status-error-text\">\n {run.stderr}\n </pre>\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The message to show a person for a failed GraphQL operation: the service's
3
+ * `userMessage` when there is one, then the GraphQL error message, then the
4
+ * transport error's own message, then `fallback`.
5
+ */
6
+ export declare function userMessageFromError(error: unknown, fallback?: string): string;
7
+ //# sourceMappingURL=errorMessage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errorMessage.d.ts","sourceRoot":"","sources":["../../../src/technospam/lib/errorMessage.ts"],"names":[],"mappings":"AAiCA;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAsB,GAAG,MAAM,CAS3F"}
@@ -0,0 +1,21 @@
1
+ import { CombinedGraphQLErrors as e, ServerError as t } from "@apollo/client";
2
+ //#region src/technospam/lib/errorMessage.ts
3
+ function n(e) {
4
+ if (!e) return null;
5
+ let t = e.extensions?.userMessage;
6
+ return typeof t == "string" && t.trim() ? t.trim() : typeof e.message == "string" && e.message.trim() ? e.message.trim() : null;
7
+ }
8
+ function r(e) {
9
+ try {
10
+ return n(JSON.parse(e)?.errors?.[0]);
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function i(i, a = "Please try again.") {
16
+ return e.is(i) ? n(i.errors[0]) ?? a : t.is(i) ? r(i.bodyText) ?? a : i instanceof Error && i.message.trim() ? i.message.trim() : a;
17
+ }
18
+ //#endregion
19
+ export { i as userMessageFromError };
20
+
21
+ //# sourceMappingURL=errorMessage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errorMessage.js","names":[],"sources":["../../../src/technospam/lib/errorMessage.ts"],"sourcesContent":["import { CombinedGraphQLErrors, ServerError } from '@apollo/client';\n\n/** The fields a platform GraphQL error carries that are worth showing a learner. */\ninterface GraphQLErrorShape {\n readonly message?: unknown;\n readonly extensions?: { readonly userMessage?: unknown } | null;\n}\n\nfunction messageOf(error: GraphQLErrorShape | undefined): string | null {\n if (!error) return null;\n const userMessage = error.extensions?.userMessage;\n if (typeof userMessage === 'string' && userMessage.trim()) return userMessage.trim();\n if (typeof error.message === 'string' && error.message.trim()) return error.message.trim();\n return null;\n}\n\n/**\n * Read the first GraphQL error out of a raw response body. The workspace\n * gateway answers a quota block (`QUOTA_EXHAUSTED` / `QUOTA_NOT_PROVISIONED`)\n * with HTTP 429 and a normal GraphQL error body; Apollo Client surfaces that as\n * a `ServerError` whose message is only \"Response not successful: Received\n * status code 429\", so the real reason has to come from the body.\n */\nfunction messageFromBody(bodyText: string): string | null {\n try {\n // A GraphQL response body; anything that is not one falls through to null.\n const body = JSON.parse(bodyText) as { errors?: readonly GraphQLErrorShape[] | null } | null;\n return messageOf(body?.errors?.[0]);\n } catch {\n return null;\n }\n}\n\n/**\n * The message to show a person for a failed GraphQL operation: the service's\n * `userMessage` when there is one, then the GraphQL error message, then the\n * transport error's own message, then `fallback`.\n */\nexport function userMessageFromError(error: unknown, fallback = 'Please try again.'): string {\n if (CombinedGraphQLErrors.is(error)) {\n return messageOf(error.errors[0]) ?? fallback;\n }\n if (ServerError.is(error)) {\n return messageFromBody(error.bodyText) ?? fallback;\n }\n if (error instanceof Error && error.message.trim()) return error.message.trim();\n return fallback;\n}\n"],"mappings":";;AAQA,SAAS,EAAU,GAAqD;AACtE,KAAI,CAAC,EAAO,QAAO;CACnB,IAAM,IAAc,EAAM,YAAY;AAGtC,QAFI,OAAO,KAAgB,YAAY,EAAY,MAAM,GAAS,EAAY,MAAM,GAChF,OAAO,EAAM,WAAY,YAAY,EAAM,QAAQ,MAAM,GAAS,EAAM,QAAQ,MAAM,GACnF;;AAUT,SAAS,EAAgB,GAAiC;AACxD,KAAI;AAGF,SAAO,EADM,KAAK,MAAM,EAAS,EACV,SAAS,GAAG;SAC7B;AACN,SAAO;;;AASX,SAAgB,EAAqB,GAAgB,IAAW,qBAA6B;AAQ3F,QAPI,EAAsB,GAAG,EAAM,GAC1B,EAAU,EAAM,OAAO,GAAG,IAAI,IAEnC,EAAY,GAAG,EAAM,GAChB,EAAgB,EAAM,SAAS,IAAI,IAExC,aAAiB,SAAS,EAAM,QAAQ,MAAM,GAAS,EAAM,QAAQ,MAAM,GACxE"}
@@ -1,4 +1,4 @@
1
- import { ue as e } from "../../node_modules/sonner/dist/index.js";
1
+ import { toast as e } from "sonner";
2
2
  //#region src/technospam/lib/toast.ts
3
3
  var t = {
4
4
  success(t, n) {
@@ -1 +1 @@
1
- {"version":3,"file":"AdminAdminDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/AdminAdminDashboardPagePage.tsx"],"names":[],"mappings":"AAsnDA,wBAAgB,2BAA2B,gCAqD1C"}
1
+ {"version":3,"file":"AdminAdminDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/AdminAdminDashboardPagePage.tsx"],"names":[],"mappings":"AA4nDA,wBAAgB,2BAA2B,gCA0D1C"}