@playcademy/sandbox 0.11.0 → 0.11.1-beta.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.
package/README.md CHANGED
@@ -199,17 +199,18 @@ bunx @playcademy/sandbox \
199
199
  --timeback-student-id your-student-sourcedId
200
200
  ```
201
201
 
202
- | Option | Description |
203
- | :------------------------------- | :----------------------------------------------------- |
204
- | `--timeback-local` | Enable local TimeBack mode (Docker) |
205
- | `--timeback-oneroster-url <url>` | OneRoster API URL |
206
- | `--timeback-caliper-url <url>` | Caliper API URL |
207
- | `--timeback-course-id <id>` | Course ID for enrollments |
208
- | `--timeback-student-id <id>` | Student ID to link to demo user |
209
- | `--timeback-role <role>` | User role (student, parent, teacher, administrator) |
210
- | `--timeback-org-id <id>` | Organization ID |
211
- | `--timeback-org-name <name>` | Organization display name |
212
- | `--timeback-org-type <type>` | Organization type (school, district, department, etc.) |
202
+ | Option | Description |
203
+ | :-------------------------------------- | :-------------------------------------------------------------------------------- |
204
+ | `--timeback-local` | Enable local TimeBack mode (Docker) |
205
+ | `--timeback-oneroster-url <url>` | OneRoster API URL |
206
+ | `--timeback-caliper-url <url>` | Caliper API URL |
207
+ | `--timeback-course-id <id>` | Course ID for enrollments |
208
+ | `--timeback-student-id <id>` | Student ID to link to demo user |
209
+ | `--timeback-role <role>` | User role (student, parent, teacher, administrator) |
210
+ | `--timeback-org-id <id>` | Organization ID |
211
+ | `--timeback-org-name <name>` | Organization display name |
212
+ | `--timeback-org-type <type>` | Organization type (school, district, department, etc.) |
213
+ | `--timeback-excluded-courses <courses>` | Comma-separated courses to exclude from enrollment (e.g. `FastMath:3,FastMath:4`) |
213
214
 
214
215
  ### Environment Variables
215
216
 
@@ -262,15 +263,34 @@ The sandbox provides these TimeBack endpoints (matching production platform):
262
263
  **Runtime:**
263
264
 
264
265
  - `POST /api/timeback/end-activity` - Submit activity results
265
-
266
- **XP Queries:**
267
-
268
- - `GET /api/timeback/xp/today` - Get today's XP
269
- - `PUT /api/timeback/xp/today` - Update today's XP
270
- - `GET /api/timeback/xp/total` - Get total XP
271
- - `GET /api/timeback/xp/history` - Get XP history
272
-
273
- These use the same api-core handlers as the production platform, ensuring consistency.
266
+ - `POST /api/timeback/heartbeat` - Record incremental activity heartbeat
267
+ - `POST /api/timeback/advance-course` - Advance a student to the next-grade course
268
+ - `POST /api/timeback/unenroll-course` - Unenroll a student from their active course
269
+ - `POST /api/timeback/populate-student` - No-op in the sandbox (always returns `no_record`)
270
+
271
+ **Assessments:**
272
+
273
+ - `POST /api/timeback/assessments/prepare` - Prepare the next assessment attempt
274
+ - `POST /api/timeback/assessments/start` - Start a new assessment attempt
275
+ - `GET /api/timeback/assessments/latest` - Get the user's latest attempt
276
+ - `GET /api/timeback/assessments/:attemptId` - Get a specific attempt
277
+ - `POST /api/timeback/assessments/:attemptId/resume` - Resume an attempt
278
+ - `POST /api/timeback/assessments/:attemptId/progress` - Record mastery progress
279
+ - `POST /api/timeback/assessments/:attemptId/save` - Checkpoint an in-progress attempt
280
+ - `POST /api/timeback/assessments/:attemptId/submit-item` - Score a single item
281
+ - `POST /api/timeback/assessments/:attemptId/submit` - Submit an attempt
282
+ - `POST /api/timeback/assessments/:attemptId/finalize` - Record XP and publish completion
283
+
284
+ **Student Data:**
285
+
286
+ - `GET /api/timeback/user` - Get the current user's TimeBack data
287
+ - `GET /api/timeback/user/enrollments` - Get the current user's enrollments
288
+ - `GET /api/timeback/user/:timebackId` - Get a student's TimeBack data
289
+ - `GET /api/timeback/student-xp/:timebackId` - Get a student's XP
290
+ - `GET /api/timeback/student-mastery/:timebackId` - Get a student's mastery
291
+ - `GET /api/timeback/student-highest-grade-mastered/:timebackId` - Get a student's highest grade mastered
292
+
293
+ These use the same api-core handlers as the production platform, ensuring consistency. The student data routes return mock data when TimeBack runs in mock mode.
274
294
 
275
295
  When properly configured, your game can call `client.timeback.endActivity()` during local development and see real events appear in your TimeBack dashboard.
276
296
 
package/dist/cli.js CHANGED
@@ -1142,7 +1142,7 @@ var package_default;
1142
1142
  var init_package = __esm(() => {
1143
1143
  package_default = {
1144
1144
  name: "@playcademy/sandbox",
1145
- version: "0.11.0",
1145
+ version: "0.11.1-beta.2",
1146
1146
  description: "Local development server for Playcademy game development",
1147
1147
  type: "module",
1148
1148
  exports: {
@@ -94381,6 +94381,12 @@ function resolvePrimaryAccountRole(roles2) {
94381
94381
  }
94382
94382
  return ACCOUNT_ROLE_PRECEDENCE.find((role) => roles2.some((entry) => entry.role === role)) ?? null;
94383
94383
  }
94384
+ function resolveEnrollableAccountRole(user) {
94385
+ if (user.status === "tobedeleted" || String(user.enabledUser) === "false") {
94386
+ return null;
94387
+ }
94388
+ return resolvePrimaryAccountRole(user.roles?.filter((entry) => entry.status !== "tobedeleted"));
94389
+ }
94384
94390
  var ACCOUNT_ROLE_PRECEDENCE;
94385
94391
  var init_timeback_admin_util = __esm(() => {
94386
94392
  init_errors2();
@@ -99997,13 +100003,8 @@ class TimebackAdminService {
99997
100003
  let allUsers = [];
99998
100004
  try {
99999
100005
  const page = await client2.api.oneroster.users.list({
100000
- where: {
100001
- OR: [
100002
- { givenName: { contains: trimmedQuery } },
100003
- { familyName: { contains: trimmedQuery } },
100004
- { email: { contains: trimmedQuery } }
100005
- ]
100006
- },
100006
+ search: trimmedQuery,
100007
+ where: { status: "active", enabledUser: "true" },
100007
100008
  limit: 25
100008
100009
  });
100009
100010
  allUsers = page.data;
@@ -100032,7 +100033,7 @@ class TimebackAdminService {
100032
100033
  pastEnrollmentsByStudent.set(studentId, list);
100033
100034
  }
100034
100035
  const students = allUsers.flatMap((entry) => {
100035
- const primaryRole = resolvePrimaryAccountRole(entry.roles);
100036
+ const primaryRole = resolveEnrollableAccountRole(entry);
100036
100037
  if (!entry.sourcedId || !primaryRole) {
100037
100038
  return [];
100038
100039
  }
@@ -110366,12 +110367,13 @@ class LeaderboardService {
110366
110367
  eq(gameScores.gameId, gameId),
110367
110368
  eq(users.isAnonymous, isAnonymousUser)
110368
110369
  ];
110369
- const bestScoresSubquery = db2.select({
110370
+ const bestScoresSubquery = db2.selectDistinctOn([gameScores.userId, gameScores.gameId], {
110370
110371
  userId: gameScores.userId,
110371
110372
  gameId: gameScores.gameId,
110372
- score: sql`MAX(${gameScores.score})`.as("max_score"),
110373
- achievedAt: sql`MAX(${gameScores.achievedAt})`.as("max_achieved_at")
110374
- }).from(gameScores).innerJoin(users, eq(gameScores.userId, users.id)).where(and(...conditions2)).groupBy(gameScores.userId, gameScores.gameId).as("best_scores");
110373
+ score: gameScores.score,
110374
+ achievedAt: gameScores.achievedAt,
110375
+ metadata: gameScores.metadata
110376
+ }).from(gameScores).innerJoin(users, eq(gameScores.userId, users.id)).where(and(...conditions2)).orderBy(gameScores.userId, gameScores.gameId, desc(gameScores.score), asc(gameScores.achievedAt)).as("best_scores");
110375
110377
  const scores = await db2.select({
110376
110378
  userId: users.id,
110377
110379
  username: users.username,
@@ -110379,11 +110381,11 @@ class LeaderboardService {
110379
110381
  userImage: users.image,
110380
110382
  score: bestScoresSubquery.score,
110381
110383
  achievedAt: bestScoresSubquery.achievedAt,
110382
- metadata: gameScores.metadata,
110384
+ metadata: bestScoresSubquery.metadata,
110383
110385
  gameId: games.id,
110384
110386
  gameTitle: games.displayName,
110385
110387
  gameSlug: games.slug
110386
- }).from(bestScoresSubquery).innerJoin(users, eq(bestScoresSubquery.userId, users.id)).innerJoin(games, eq(bestScoresSubquery.gameId, games.id)).innerJoin(gameScores, and(eq(gameScores.userId, bestScoresSubquery.userId), eq(gameScores.gameId, bestScoresSubquery.gameId), eq(gameScores.score, bestScoresSubquery.score))).orderBy(desc(bestScoresSubquery.score)).limit(limit).offset(offset);
110388
+ }).from(bestScoresSubquery).innerJoin(users, eq(bestScoresSubquery.userId, users.id)).innerJoin(games, eq(bestScoresSubquery.gameId, games.id)).orderBy(desc(bestScoresSubquery.score), asc(bestScoresSubquery.achievedAt), asc(bestScoresSubquery.userId)).limit(limit).offset(offset);
110387
110389
  setAttributes({
110388
110390
  "app.leaderboard.result_count": scores.length,
110389
110391
  "app.leaderboard.no_result": scores.length === 0
@@ -110623,7 +110625,7 @@ class UserService {
110623
110625
  "app.user.timeback_enriched": false
110624
110626
  });
110625
110627
  const localDay = await this.resolveLocalDay(userData, observedTimeZone);
110626
- const timeback2 = userData.timebackId ? await this.fetchTimebackData(userData.timebackId, gameId) : undefined;
110628
+ const timeback2 = userData.timebackId ? await this.fetchTimebackProfile(userData.timebackId) : undefined;
110627
110629
  setAttribute("app.user.timeback_enriched", Boolean(timeback2));
110628
110630
  if (gameId) {
110629
110631
  return {
@@ -110728,18 +110730,10 @@ class UserService {
110728
110730
  observedAt: null
110729
110731
  };
110730
110732
  }
110731
- async fetchTimebackData(timebackId, gameId) {
110732
- const [{ role, organizations: allOrganizations }, allEnrollments] = await Promise.all([
110733
- withSpan("timeback.fetch_profile", () => this.fetchStudentProfile(timebackId)),
110734
- withSpan("timeback.fetch_enrollments", () => this.fetchEnrollments(timebackId))
110735
- ]);
110736
- const enrollments = gameId ? filterEnrollmentsByGame(allEnrollments, gameId) : allEnrollments;
110737
- const organizations = gameId ? this.filterOrganizationsByEnrollments(allOrganizations, enrollments) : allOrganizations;
110738
- setAttributes({
110739
- "app.timeback.enrollment_count": enrollments.length,
110740
- "app.timeback.organization_count": organizations.length
110741
- });
110742
- return { id: timebackId, role, enrollments, organizations };
110733
+ async fetchTimebackProfile(timebackId) {
110734
+ const { role, organizations } = await withSpan("timeback.fetch_profile", () => this.fetchStudentProfile(timebackId));
110735
+ setAttribute("app.timeback.organization_count", organizations.length);
110736
+ return { id: timebackId, role, organizations };
110743
110737
  }
110744
110738
  async fetchStudentProfile(timebackId) {
110745
110739
  if (!this.deps.timeback) {
@@ -110778,46 +110772,14 @@ class UserService {
110778
110772
  return { role: "student", organizations: [] };
110779
110773
  }
110780
110774
  }
110781
- async fetchEnrollments(timebackId) {
110782
- if (!this.deps.timeback) {
110783
- setAttribute("app.timeback.client", "unavailable");
110784
- return [];
110785
- }
110786
- try {
110787
- const enrollments = await this.deps.timeback.roster.getEnrollments(timebackId);
110788
- const courseIds = enrollments.map((e) => e.courseId).filter((id) => Boolean(id));
110789
- if (courseIds.length === 0) {
110790
- return [];
110791
- }
110792
- const integrations = await this.deps.db.query.gameTimebackIntegrations.findMany({
110793
- where: and(inArray(gameTimebackIntegrations.courseId, courseIds), isActiveGameTimebackIntegrationStatus())
110794
- });
110795
- return mapEnrollmentsToUserEnrollments(enrollments, integrations);
110796
- } catch (error88) {
110797
- addEvent("timeback.enrollment_fetch_failed", {
110798
- "exception.type": errorType(error88),
110799
- "app.error.message": errorMessage(error88)
110800
- });
110801
- return [];
110802
- }
110803
- }
110804
- filterOrganizationsByEnrollments(organizations, enrollments) {
110805
- const enrollmentOrgIds = new Set(enrollments.map((e) => e.orgId).filter(Boolean));
110806
- if (enrollmentOrgIds.size === 0) {
110807
- return [];
110808
- }
110809
- return organizations.filter((o) => enrollmentOrgIds.has(o.id));
110810
- }
110811
110775
  }
110812
110776
  var LEARNING_TIME_ZONE_REFRESH_INTERVAL_MS;
110813
110777
  var init_user_service = __esm(() => {
110814
110778
  init_drizzle_orm();
110815
110779
  init_src();
110816
- init_helpers_index();
110817
110780
  init_tables_index();
110818
110781
  init_spans();
110819
110782
  init_errors2();
110820
- init_timeback_util();
110821
110783
  LEARNING_TIME_ZONE_REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1000;
110822
110784
  });
110823
110785
 
@@ -169346,8 +169308,12 @@ async function buildLocalDayContext(db2, user, observedTimeZone) {
169346
169308
  observedAt: null
169347
169309
  };
169348
169310
  }
169311
+ function getMockTimebackProfile(timebackId) {
169312
+ const { role, organizations } = getMockStudentProfile();
169313
+ return { id: timebackId, role, organizations };
169314
+ }
169349
169315
  async function buildMockUserResponse(db2, user, gameId, observedTimeZone) {
169350
- const timeback3 = user.timebackId ? await getMockTimebackData(db2, user.timebackId, gameId) : undefined;
169316
+ const timeback3 = user.timebackId ? getMockTimebackProfile(user.timebackId) : undefined;
169351
169317
  const localDay = await buildLocalDayContext(db2, user, observedTimeZone);
169352
169318
  if (gameId) {
169353
169319
  return {
@@ -32,7 +32,7 @@ export declare function getMockTimebackData(db: DatabaseInstance, timebackId: st
32
32
  export declare function getMockTimebackUser(db: DatabaseInstance, gameId?: string): Promise<UserTimebackData>;
33
33
  export declare function getMockHighestGradeMastered(enrollments: UserEnrollment[], subject: string): number | null;
34
34
  /**
35
- * Build a complete user response with mock timeback data.
35
+ * Build a complete user response with mock timeback identity.
36
36
  * Used to bypass api-core when in mock mode (avoids real API calls).
37
37
  */
38
38
  export declare function buildMockUserResponse(db: DatabaseInstance, user: User, gameId?: string, observedTimeZone?: string | null): Promise<AuthenticatedUser | GameUser>;
package/dist/server.js CHANGED
@@ -1141,7 +1141,7 @@ var package_default;
1141
1141
  var init_package = __esm(() => {
1142
1142
  package_default = {
1143
1143
  name: "@playcademy/sandbox",
1144
- version: "0.11.0",
1144
+ version: "0.11.1-beta.2",
1145
1145
  description: "Local development server for Playcademy game development",
1146
1146
  type: "module",
1147
1147
  exports: {
@@ -94380,6 +94380,12 @@ function resolvePrimaryAccountRole(roles2) {
94380
94380
  }
94381
94381
  return ACCOUNT_ROLE_PRECEDENCE.find((role) => roles2.some((entry) => entry.role === role)) ?? null;
94382
94382
  }
94383
+ function resolveEnrollableAccountRole(user) {
94384
+ if (user.status === "tobedeleted" || String(user.enabledUser) === "false") {
94385
+ return null;
94386
+ }
94387
+ return resolvePrimaryAccountRole(user.roles?.filter((entry) => entry.status !== "tobedeleted"));
94388
+ }
94383
94389
  var ACCOUNT_ROLE_PRECEDENCE;
94384
94390
  var init_timeback_admin_util = __esm(() => {
94385
94391
  init_errors2();
@@ -99996,13 +100002,8 @@ class TimebackAdminService {
99996
100002
  let allUsers = [];
99997
100003
  try {
99998
100004
  const page = await client2.api.oneroster.users.list({
99999
- where: {
100000
- OR: [
100001
- { givenName: { contains: trimmedQuery } },
100002
- { familyName: { contains: trimmedQuery } },
100003
- { email: { contains: trimmedQuery } }
100004
- ]
100005
- },
100005
+ search: trimmedQuery,
100006
+ where: { status: "active", enabledUser: "true" },
100006
100007
  limit: 25
100007
100008
  });
100008
100009
  allUsers = page.data;
@@ -100031,7 +100032,7 @@ class TimebackAdminService {
100031
100032
  pastEnrollmentsByStudent.set(studentId, list);
100032
100033
  }
100033
100034
  const students = allUsers.flatMap((entry) => {
100034
- const primaryRole = resolvePrimaryAccountRole(entry.roles);
100035
+ const primaryRole = resolveEnrollableAccountRole(entry);
100035
100036
  if (!entry.sourcedId || !primaryRole) {
100036
100037
  return [];
100037
100038
  }
@@ -110365,12 +110366,13 @@ class LeaderboardService {
110365
110366
  eq(gameScores.gameId, gameId),
110366
110367
  eq(users.isAnonymous, isAnonymousUser)
110367
110368
  ];
110368
- const bestScoresSubquery = db2.select({
110369
+ const bestScoresSubquery = db2.selectDistinctOn([gameScores.userId, gameScores.gameId], {
110369
110370
  userId: gameScores.userId,
110370
110371
  gameId: gameScores.gameId,
110371
- score: sql`MAX(${gameScores.score})`.as("max_score"),
110372
- achievedAt: sql`MAX(${gameScores.achievedAt})`.as("max_achieved_at")
110373
- }).from(gameScores).innerJoin(users, eq(gameScores.userId, users.id)).where(and(...conditions2)).groupBy(gameScores.userId, gameScores.gameId).as("best_scores");
110372
+ score: gameScores.score,
110373
+ achievedAt: gameScores.achievedAt,
110374
+ metadata: gameScores.metadata
110375
+ }).from(gameScores).innerJoin(users, eq(gameScores.userId, users.id)).where(and(...conditions2)).orderBy(gameScores.userId, gameScores.gameId, desc(gameScores.score), asc(gameScores.achievedAt)).as("best_scores");
110374
110376
  const scores = await db2.select({
110375
110377
  userId: users.id,
110376
110378
  username: users.username,
@@ -110378,11 +110380,11 @@ class LeaderboardService {
110378
110380
  userImage: users.image,
110379
110381
  score: bestScoresSubquery.score,
110380
110382
  achievedAt: bestScoresSubquery.achievedAt,
110381
- metadata: gameScores.metadata,
110383
+ metadata: bestScoresSubquery.metadata,
110382
110384
  gameId: games.id,
110383
110385
  gameTitle: games.displayName,
110384
110386
  gameSlug: games.slug
110385
- }).from(bestScoresSubquery).innerJoin(users, eq(bestScoresSubquery.userId, users.id)).innerJoin(games, eq(bestScoresSubquery.gameId, games.id)).innerJoin(gameScores, and(eq(gameScores.userId, bestScoresSubquery.userId), eq(gameScores.gameId, bestScoresSubquery.gameId), eq(gameScores.score, bestScoresSubquery.score))).orderBy(desc(bestScoresSubquery.score)).limit(limit).offset(offset);
110387
+ }).from(bestScoresSubquery).innerJoin(users, eq(bestScoresSubquery.userId, users.id)).innerJoin(games, eq(bestScoresSubquery.gameId, games.id)).orderBy(desc(bestScoresSubquery.score), asc(bestScoresSubquery.achievedAt), asc(bestScoresSubquery.userId)).limit(limit).offset(offset);
110386
110388
  setAttributes({
110387
110389
  "app.leaderboard.result_count": scores.length,
110388
110390
  "app.leaderboard.no_result": scores.length === 0
@@ -110622,7 +110624,7 @@ class UserService {
110622
110624
  "app.user.timeback_enriched": false
110623
110625
  });
110624
110626
  const localDay = await this.resolveLocalDay(userData, observedTimeZone);
110625
- const timeback2 = userData.timebackId ? await this.fetchTimebackData(userData.timebackId, gameId) : undefined;
110627
+ const timeback2 = userData.timebackId ? await this.fetchTimebackProfile(userData.timebackId) : undefined;
110626
110628
  setAttribute("app.user.timeback_enriched", Boolean(timeback2));
110627
110629
  if (gameId) {
110628
110630
  return {
@@ -110727,18 +110729,10 @@ class UserService {
110727
110729
  observedAt: null
110728
110730
  };
110729
110731
  }
110730
- async fetchTimebackData(timebackId, gameId) {
110731
- const [{ role, organizations: allOrganizations }, allEnrollments] = await Promise.all([
110732
- withSpan("timeback.fetch_profile", () => this.fetchStudentProfile(timebackId)),
110733
- withSpan("timeback.fetch_enrollments", () => this.fetchEnrollments(timebackId))
110734
- ]);
110735
- const enrollments = gameId ? filterEnrollmentsByGame(allEnrollments, gameId) : allEnrollments;
110736
- const organizations = gameId ? this.filterOrganizationsByEnrollments(allOrganizations, enrollments) : allOrganizations;
110737
- setAttributes({
110738
- "app.timeback.enrollment_count": enrollments.length,
110739
- "app.timeback.organization_count": organizations.length
110740
- });
110741
- return { id: timebackId, role, enrollments, organizations };
110732
+ async fetchTimebackProfile(timebackId) {
110733
+ const { role, organizations } = await withSpan("timeback.fetch_profile", () => this.fetchStudentProfile(timebackId));
110734
+ setAttribute("app.timeback.organization_count", organizations.length);
110735
+ return { id: timebackId, role, organizations };
110742
110736
  }
110743
110737
  async fetchStudentProfile(timebackId) {
110744
110738
  if (!this.deps.timeback) {
@@ -110777,46 +110771,14 @@ class UserService {
110777
110771
  return { role: "student", organizations: [] };
110778
110772
  }
110779
110773
  }
110780
- async fetchEnrollments(timebackId) {
110781
- if (!this.deps.timeback) {
110782
- setAttribute("app.timeback.client", "unavailable");
110783
- return [];
110784
- }
110785
- try {
110786
- const enrollments = await this.deps.timeback.roster.getEnrollments(timebackId);
110787
- const courseIds = enrollments.map((e) => e.courseId).filter((id) => Boolean(id));
110788
- if (courseIds.length === 0) {
110789
- return [];
110790
- }
110791
- const integrations = await this.deps.db.query.gameTimebackIntegrations.findMany({
110792
- where: and(inArray(gameTimebackIntegrations.courseId, courseIds), isActiveGameTimebackIntegrationStatus())
110793
- });
110794
- return mapEnrollmentsToUserEnrollments(enrollments, integrations);
110795
- } catch (error88) {
110796
- addEvent("timeback.enrollment_fetch_failed", {
110797
- "exception.type": errorType(error88),
110798
- "app.error.message": errorMessage(error88)
110799
- });
110800
- return [];
110801
- }
110802
- }
110803
- filterOrganizationsByEnrollments(organizations, enrollments) {
110804
- const enrollmentOrgIds = new Set(enrollments.map((e) => e.orgId).filter(Boolean));
110805
- if (enrollmentOrgIds.size === 0) {
110806
- return [];
110807
- }
110808
- return organizations.filter((o) => enrollmentOrgIds.has(o.id));
110809
- }
110810
110774
  }
110811
110775
  var LEARNING_TIME_ZONE_REFRESH_INTERVAL_MS;
110812
110776
  var init_user_service = __esm(() => {
110813
110777
  init_drizzle_orm();
110814
110778
  init_src();
110815
- init_helpers_index();
110816
110779
  init_tables_index();
110817
110780
  init_spans();
110818
110781
  init_errors2();
110819
- init_timeback_util();
110820
110782
  LEARNING_TIME_ZONE_REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1000;
110821
110783
  });
110822
110784
 
@@ -169345,8 +169307,12 @@ async function buildLocalDayContext(db2, user, observedTimeZone) {
169345
169307
  observedAt: null
169346
169308
  };
169347
169309
  }
169310
+ function getMockTimebackProfile(timebackId) {
169311
+ const { role, organizations } = getMockStudentProfile();
169312
+ return { id: timebackId, role, organizations };
169313
+ }
169348
169314
  async function buildMockUserResponse(db2, user, gameId, observedTimeZone) {
169349
- const timeback3 = user.timebackId ? await getMockTimebackData(db2, user.timebackId, gameId) : undefined;
169315
+ const timeback3 = user.timebackId ? getMockTimebackProfile(user.timebackId) : undefined;
169350
169316
  const localDay = await buildLocalDayContext(db2, user, observedTimeZone);
169351
169317
  if (gameId) {
169352
169318
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sandbox",
3
- "version": "0.11.0",
3
+ "version": "0.11.1-beta.2",
4
4
  "description": "Local development server for Playcademy game development",
5
5
  "type": "module",
6
6
  "exports": {