dsh-aha-tutor 1.1.0 → 1.3.0

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/lib/index.js CHANGED
@@ -1,10 +1,16 @@
1
- import { $ as normalizePractice, A as materialImages, B as boardToPlainText, C as TutorError, D as EMPTY_PROFILE, E as generateText, F as readProgress, G as flattenLessons, H as conceptIndex, I as applyEvidence, J as lessonAfter, K as insertLesson, L as createConceptState, M as passagesFor, N as readMaterials, O as emptyProgress, P as readProfile, Q as normalizeCheatsheet, R as masteryPercent, S as summaryPrompt, T as generateSteps, U as courseProgress, V as normalizeBoard, W as findLesson, X as normalizeBlueprint, Y as nextLesson, Z as normalizeCards, _ as questionnairePrompt, a as blueprintPrompt, at as assertId, b as revisePrompt, c as digestPrompt, d as lessonPrompt, et as normalizeProject, f as materialBriefPrompt, g as projectsPrompt, h as projectReviewPrompt, i as assistPrompt, it as text, j as materialsSummary, k as masteryFor, l as flashcardsPrompt, m as profilePrompt, n as publishRuntime, nt as outlineForPrompt, o as cheatsheetPrompt, ot as newId, p as practicePrompt, q as intakeSummary, r as askPrompt, rt as safeRelativePath, s as detectLanguage, tt as normalizeQuestionnaire, u as gradePrompt, v as quizPrompt, w as generateJson, x as shortPrompt, y as reexplainPrompt, z as recommendNext } from "./runtime-CiA2I7-X.js";
1
+ import { $ as courseProgress, A as streamCounter, B as documentParts, C as reworkPrompt, D as generateJson, E as TutorError, F as materialsSummary, G as createConceptState, H as passageRange, I as passagesFor, J as extractJson, K as masteryPercent, L as readMaterials, M as emptyProgress, N as masteryFor, O as generateSteps, P as materialImages, Q as conceptIndex, R as readProfile, S as revisePrompt, T as summaryPrompt, U as spreadPassages, V as formatPassages, W as applyEvidence, X as normalizeBoard, Y as boardToPlainText, Z as blueprintOutline, _ as projectReviewPrompt, _t as newId, a as blueprintPrompt, at as nextLesson, b as quizPrompt, c as detectLanguage, ct as normalizeCheatsheet, d as flashcardsPrompt, dt as normalizeProject, et as findLesson, f as gradePrompt, ft as normalizeQuestionnaire, g as profilePrompt, gt as assertId, h as practicePrompt, ht as text, i as assistPrompt, it as lessonAfter, j as EMPTY_PROFILE, k as generateText, l as digestPartPrompt, lt as normalizeLesson, m as materialBriefPrompt, mt as safeRelativePath, n as publishRuntime, nt as insertLesson, o as cheatsheetPrompt, ot as normalizeBlueprint, p as lessonPrompt, pt as outlineForPrompt, q as recommendNext, r as askPrompt, rt as intakeSummary, s as correctionPrompt, st as normalizeCards, tt as flattenLessons, u as digestPrompt, ut as normalizePractice, v as projectsPrompt, w as shortPrompt, x as reexplainPrompt, y as questionnairePrompt, z as readProgress } from "./runtime-BCGFUDlg.js";
2
2
  import { createRequire } from "node:module";
3
3
  import { dirname, join, relative, sep } from "node:path";
4
4
  import { credentialKey } from "@deepseek-ai/dsh-credentials";
5
+ import { lstat, mkdir, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
6
+ import { lookup } from "node:dns";
7
+ import http from "node:http";
8
+ import https from "node:https";
9
+ import { isIP } from "node:net";
10
+ import { pipeline } from "node:stream";
11
+ import zlib from "node:zlib";
5
12
  import { spawn } from "node:child_process";
6
13
  import { existsSync } from "node:fs";
7
- import { lstat, mkdir, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
8
14
  import { fileURLToPath } from "node:url";
9
15
  import { homedir } from "node:os";
10
16
  import { createHash, randomUUID } from "node:crypto";
@@ -211,6 +217,25 @@ const TIMELINES = [
211
217
  "balanced",
212
218
  "relaxed"
213
219
  ];
220
+ const SIZES = [
221
+ "auto",
222
+ "short",
223
+ "standard",
224
+ "full"
225
+ ];
226
+ function outlineStream(api) {
227
+ let soFar = "";
228
+ const counter = streamCounter("\"estMinutes\"", (count) => {
229
+ api.progress(count, 0, "blueprint");
230
+ try {
231
+ api.preview(blueprintOutline(extractJson(soFar)));
232
+ } catch {}
233
+ });
234
+ return (delta) => {
235
+ soFar += String(delta ?? "");
236
+ counter(delta);
237
+ };
238
+ }
214
239
  function createCourses({ store, llm, jobs, now = () => /* @__PURE__ */ new Date() }) {
215
240
  const stamp = () => now().toISOString();
216
241
  async function readCourse(courseId) {
@@ -259,7 +284,11 @@ function createCourses({ store, llm, jobs, now = () => /* @__PURE__ */ new Date(
259
284
  }
260
285
  async function get({ courseId }) {
261
286
  const course = await readCourse(courseId);
262
- const [progress, intake] = await Promise.all([readProgress(store, courseId), store.readJson(store.paths.intake(courseId), null)]);
287
+ const [progress, intake, proposal] = await Promise.all([
288
+ readProgress(store, courseId),
289
+ store.readJson(store.paths.intake(courseId), null),
290
+ store.readJson(store.paths.proposal(courseId), null)
291
+ ]);
263
292
  const boards = new Set(await store.listIds(store.paths.boardsDir(courseId), "les"));
264
293
  const practice = new Set(await store.listIds(store.paths.practiceDir(courseId), "les"));
265
294
  const sheets = new Set(await store.listIds(store.paths.sheetsDir(courseId), "unt"));
@@ -267,7 +296,9 @@ function createCourses({ store, llm, jobs, now = () => /* @__PURE__ */ new Date(
267
296
  course,
268
297
  progress,
269
298
  intake,
299
+ proposal,
270
300
  jobs: {
301
+ questions: jobs.running(`questions:${courseId}`),
271
302
  blueprint: jobs.running(`blueprint:${courseId}`),
272
303
  confirm: jobs.running(`confirm:${courseId}`)
273
304
  },
@@ -327,9 +358,12 @@ function createCourses({ store, llm, jobs, now = () => /* @__PURE__ */ new Date(
327
358
  courseId,
328
359
  questionnaire
329
360
  };
330
- }, { meta: { courseId } });
361
+ }, {
362
+ key: `questions:${courseId}`,
363
+ meta: { courseId }
364
+ });
331
365
  }
332
- function submitIntake({ courseId, answers = {}, timeline = "balanced", weeklyMinutes, deadline }) {
366
+ function submitIntake({ courseId, answers = {}, timeline = "balanced", size = "auto", weeklyMinutes, deadline }) {
333
367
  assertId(courseId, "crs");
334
368
  return jobs.start("craft/blueprint", async (api) => {
335
369
  const course = await readCourse(courseId);
@@ -343,14 +377,16 @@ function createCourses({ store, llm, jobs, now = () => /* @__PURE__ */ new Date(
343
377
  answers: cleanAnswers,
344
378
  at: stamp()
345
379
  });
380
+ if (course.materialIds?.length) api.progress(0, 0, "materials");
346
381
  const materials = await readMaterials(store, course.materialIds ?? []);
347
382
  const profile = await readProfile(store);
348
383
  const settings = {
349
384
  timeline: TIMELINES.includes(timeline) ? timeline : "balanced",
385
+ size: SIZES.includes(size) ? size : "auto",
350
386
  weeklyMinutes: clampMinutes(weeklyMinutes),
351
387
  deadline: /^\d{4}-\d{2}-\d{2}$/u.test(String(deadline ?? "")) ? deadline : null
352
388
  };
353
- api.progress(0, 1, "blueprint");
389
+ api.progress(0, 0, "blueprint");
354
390
  const blueprint = await generateJson(llm, {
355
391
  purpose: "teach",
356
392
  prompt: blueprintPrompt({
@@ -360,11 +396,13 @@ function createCourses({ store, llm, jobs, now = () => /* @__PURE__ */ new Date(
360
396
  materialsSummary: materialsSummary(materials),
361
397
  profile,
362
398
  timeline: settings.timeline,
399
+ size: settings.size,
363
400
  weeklyMinutes: settings.weeklyMinutes
364
401
  }),
365
402
  images: llm.supportsImages?.("teach") ? await materialImages(store, materials) : void 0,
366
403
  maxTokens: 16e3,
367
404
  signal: api.signal,
405
+ onText: outlineStream(api),
368
406
  normalize: (raw) => attachProjectUnits(raw, normalizeBlueprint(raw, { language: course.language }))
369
407
  });
370
408
  const next = {
@@ -375,7 +413,6 @@ function createCourses({ store, llm, jobs, now = () => /* @__PURE__ */ new Date(
375
413
  settings
376
414
  };
377
415
  await saveCourse(next);
378
- api.progress(1, 1, "blueprint");
379
416
  return { course: next };
380
417
  }, {
381
418
  key: `blueprint:${courseId}`,
@@ -388,38 +425,100 @@ function createCourses({ store, llm, jobs, now = () => /* @__PURE__ */ new Date(
388
425
  return jobs.start("craft/revise", async (api) => {
389
426
  const course = await readCourse(courseId);
390
427
  if (!course.units) throw new TutorError("invalid", "There is no blueprint to revise yet");
428
+ const current = {
429
+ title: course.title,
430
+ subtitle: course.subtitle,
431
+ emoji: course.emoji,
432
+ summary: course.summary,
433
+ level: course.level,
434
+ units: course.units,
435
+ projects: course.projects
436
+ };
437
+ api.progress(0, 0, "blueprint");
391
438
  const revised = await generateJson(llm, {
392
439
  purpose: "teach",
393
440
  prompt: revisePrompt({
394
- blueprint: {
395
- title: course.title,
396
- subtitle: course.subtitle,
397
- emoji: course.emoji,
398
- summary: course.summary,
399
- level: course.level,
400
- units: course.units,
401
- projects: course.projects
402
- },
441
+ blueprint: current,
403
442
  instruction: request,
404
443
  language: course.language
405
444
  }),
406
445
  maxTokens: 16e3,
407
446
  signal: api.signal,
447
+ onText: streamCounter("\"estMinutes\"", (count) => api.progress(count, 0, "blueprint")),
408
448
  normalize: (raw) => attachProjectUnits(raw, normalizeBlueprint(raw, { language: course.language }))
409
449
  });
410
- const next = {
411
- ...course,
412
- ...revised,
413
- language: course.language,
414
- status: course.status === "ready" ? "ready" : "blueprint"
450
+ const proposal = {
451
+ instruction: request,
452
+ at: stamp(),
453
+ blueprint: revised
454
+ };
455
+ await store.writeJson(store.paths.proposal(courseId), proposal);
456
+ return {
457
+ course,
458
+ proposal
415
459
  };
416
- await saveCourse(next);
417
- return { course: next };
418
460
  }, {
419
461
  key: `blueprint:${courseId}`,
420
462
  meta: { courseId }
421
463
  });
422
464
  }
465
+ async function applyProposal({ courseId }) {
466
+ const course = await readCourse(courseId);
467
+ const proposal = await store.readJson(store.paths.proposal(courseId), null);
468
+ if (!proposal?.blueprint?.units) throw new TutorError("not-found", "There is no proposed change");
469
+ const next = {
470
+ ...course,
471
+ ...proposal.blueprint,
472
+ language: course.language,
473
+ status: course.status === "ready" ? "ready" : "blueprint"
474
+ };
475
+ await saveCourse(next);
476
+ await store.removeDir(store.paths.proposal(courseId));
477
+ return { course: next };
478
+ }
479
+ async function discardProposal({ courseId }) {
480
+ assertId(courseId, "crs");
481
+ await store.removeDir(store.paths.proposal(courseId));
482
+ return { ok: true };
483
+ }
484
+ function reworkLesson({ courseId, lesson: raw, unitTitle, lectureTitle, mode }) {
485
+ const split = mode === "split";
486
+ const lesson = normalizeLesson(raw);
487
+ if (!lesson) throw new TutorError("invalid", "Unknown lesson");
488
+ return jobs.start("craft/rework", async (api) => {
489
+ const course = await readCourse(courseId);
490
+ return { lessons: await generateJson(llm, {
491
+ purpose: "teach",
492
+ prompt: reworkPrompt({
493
+ outline: outlineForPrompt(course, { maxChars: 4e3 }),
494
+ unitTitle: text(unitTitle, 120),
495
+ lectureTitle: text(lectureTitle, 120),
496
+ lesson: {
497
+ title: lesson.title,
498
+ summary: lesson.summary,
499
+ objectives: lesson.objectives,
500
+ concepts: lesson.concepts.map((concept) => concept.name),
501
+ estMinutes: lesson.estMinutes
502
+ },
503
+ mode: split ? "split" : "deepen",
504
+ language: course.language
505
+ }),
506
+ maxTokens: 3e3,
507
+ signal: api.signal,
508
+ normalize: (value) => {
509
+ const list = (Array.isArray(value?.lessons) ? value.lessons : [value]).map((entry) => normalizeLesson({
510
+ ...entry,
511
+ id: void 0
512
+ })).filter(Boolean).slice(0, split ? 3 : 1);
513
+ if (split ? list.length < 2 : !list.length) throw new Error("no usable lessons");
514
+ return list;
515
+ }
516
+ }) };
517
+ }, {
518
+ key: `rework:${courseId}:${lesson.id}`,
519
+ meta: { courseId }
520
+ });
521
+ }
423
522
  async function saveBlueprint({ courseId, blueprint }) {
424
523
  const course = await readCourse(courseId);
425
524
  const normalized = normalizeBlueprint({
@@ -438,11 +537,40 @@ function createCourses({ store, llm, jobs, now = () => /* @__PURE__ */ new Date(
438
537
  await saveCourse(next);
439
538
  return { course: next };
440
539
  }
540
+ async function writeLecturePractice({ course, lecture, lessons = lecture.lessons, materials, signal }) {
541
+ const query = lessons.map((lesson) => `${lesson.title} ${lesson.objectives.join(" ")}`).join(" ");
542
+ const sources = materials ?? await readMaterials(store, course.materialIds ?? []);
543
+ const sets = await generateJson(llm, {
544
+ purpose: "teach",
545
+ prompt: practicePrompt({
546
+ course,
547
+ lecture,
548
+ lessons,
549
+ language: course.language,
550
+ passages: passagesFor(sources, query, 5e3)
551
+ }),
552
+ maxTokens: Math.min(16e3, 3e3 + lessons.length * 2600),
553
+ signal,
554
+ normalize: (raw) => raw?.lessons ?? raw
555
+ });
556
+ const written = {};
557
+ for (const lesson of lessons) {
558
+ const set = normalizePractice(sets?.[lesson.id] ?? sets?.[lesson.title] ?? (lessons.length === 1 ? Object.values(sets ?? {})[0] : void 0) ?? { items: [] }, lesson.concepts.map((concept) => concept.id));
559
+ if (!set.items.length) continue;
560
+ written[lesson.id] = {
561
+ lessonId: lesson.id,
562
+ ...set,
563
+ createdAt: stamp()
564
+ };
565
+ await store.writeJson(store.paths.practice(course.id, lesson.id), written[lesson.id]);
566
+ }
567
+ return written;
568
+ }
441
569
  function confirm({ courseId }) {
442
570
  return jobs.start("craft/confirm", async (api) => {
443
571
  const course = await readCourse(courseId);
444
572
  if (!course.units) throw new TutorError("invalid", "Generate a blueprint first");
445
- const lectures = course.units.flatMap((unit) => unit.lectures.map((lecture) => ({
573
+ const lectures = course.units.slice(0, 1).flatMap((unit) => unit.lectures.map((lecture) => ({
446
574
  unit,
447
575
  lecture
448
576
  })));
@@ -489,29 +617,12 @@ function createCourses({ store, llm, jobs, now = () => /* @__PURE__ */ new Date(
489
617
  api.signal.throwIfAborted();
490
618
  const { lecture } = queue.shift();
491
619
  try {
492
- const query = lecture.lessons.map((lesson) => `${lesson.title} ${lesson.objectives.join(" ")}`).join(" ");
493
- const sets = await generateJson(llm, {
494
- purpose: "teach",
495
- prompt: practicePrompt({
496
- course,
497
- lecture,
498
- lessons: lecture.lessons,
499
- language: course.language,
500
- passages: passagesFor(materials, query, 5e3)
501
- }),
502
- maxTokens: 9e3,
503
- signal: api.signal,
504
- normalize: (raw) => raw?.lessons ?? raw
620
+ await writeLecturePractice({
621
+ course,
622
+ lecture,
623
+ materials,
624
+ signal: api.signal
505
625
  });
506
- for (const lesson of lecture.lessons) {
507
- const conceptIds = lesson.concepts.map((concept) => concept.id);
508
- const set = normalizePractice(sets?.[lesson.id] ?? sets?.[lesson.title] ?? { items: [] }, conceptIds);
509
- if (set.items.length) await store.writeJson(store.paths.practice(course.id, lesson.id), {
510
- lessonId: lesson.id,
511
- ...set,
512
- createdAt: stamp()
513
- });
514
- }
515
626
  } catch (error) {
516
627
  if (api.signal.aborted) throw error;
517
628
  warnings.push(lecture.id);
@@ -617,7 +728,11 @@ function createCourses({ store, llm, jobs, now = () => /* @__PURE__ */ new Date(
617
728
  startCraft,
618
729
  submitIntake,
619
730
  revise,
731
+ applyProposal,
732
+ discardProposal,
733
+ reworkLesson,
620
734
  saveBlueprint,
735
+ writeLecturePractice,
621
736
  confirm,
622
737
  update,
623
738
  remove,
@@ -663,12 +778,297 @@ function safeFileName(value) {
663
778
  return /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/iu.test(cleaned) || !cleaned ? "course" : cleaned;
664
779
  }
665
780
  //#endregion
781
+ //#region src/core/board-fonts.js
782
+ const BOARD_FONT_FILES = Object.freeze([
783
+ Object.freeze({
784
+ family: "Aha Hand Latin",
785
+ file: "caveat-latin-d0b7b931.woff2",
786
+ bytes: 48836,
787
+ unicodeRange: "U+0,U+D,U+20-7E,U+A0-FF,U+131,U+152-153,U+20AC,U+2122"
788
+ }),
789
+ Object.freeze({
790
+ family: "Aha Hand",
791
+ file: "wenkai-gb-0-88f1a875.woff2",
792
+ bytes: 93632,
793
+ unicodeRange: "U+20-7E,U+A0-AC,U+AE-FF,U+101,U+113,U+11B,U+12B,U+14D,U+16B,U+1CE,U+1D0,U+1D2,U+1D4,U+1D6,U+1D8,U+1DA,U+1DC,U+2C7,U+2C9,U+391-3A1,U+3A3-3A9,U+3B1-3C1,U+3C3-3C9,U+401,U+410-44F,U+451,U+2010-2016,U+2018-2022,U+2025-2027,U+2030-2037,U+2039-203F,U+2042,U+2044,U+2047-2049,U+2051-2052,U+205A,U+205D,U+2100-2101,U+2103,U+2105-2106,U+2109-210A,U+210E-210F,U+2113,U+2116-2117,U+211E,U+2120-2122,U+2126-2127,U+2129-212B,U+212E,U+2132,U+2135,U+213B,U+2141-2144,U+214B,U+214D-214E,U+2150-218B,U+2190-2199,U+21B6-21B9,U+21C4-21C6,U+21CB-21D0,U+21D2,U+21D4,U+21E6-21EA,U+21F5,U+2200-220D,U+220F,U+2211-2213,U+2215-2216,U+2219-2220,U+2223-2230,U+2234-2237,U+223C-223E,U+2242-2243,U+2245,U+2248,U+224B-224C,U+2251-2253,U+225C,U+2260-2262,U+2264-226B,U+226E-226F,U+2272-2273,U+2276-2277,U+2282-228B,U+2295-2299,U+229C-229E,U+22A0,U+22A2-22A5,U+22BF-22C5,U+22DA-22DB,U+22EE-22F1,U+2312,U+2460-249F,U+2500-25AB,U+25B1-25B3,U+25B6-25B7,U+25BC-25BD,U+25C0-25C1,U+25C6-25C7,U+25C9-25CC,U+25CE-25D3,U+25E2-25E6,U+25EF,U+25FB-25FC,U+2605-2606,U+2640,U+2642,U+3000-302D,U+3030,U+3033-303F,U+3041-3093,U+30A1-30F6,U+30FB,U+3105-3129,U+3220-3229,U+FF01-FF5E,U+FFE0-FFE1,U+FFE3,U+FFE5"
794
+ }),
795
+ Object.freeze({
796
+ family: "Aha Hand",
797
+ file: "wenkai-gb-1-78a38e72.woff2",
798
+ bytes: 315108,
799
+ unicodeRange: "U+4E01,U+4E0D,U+4E11,U+4E19,U+4E1B-4E1C,U+4E22,U+4E2A,U+4E30,U+4E32,U+4E39,U+4E4E-4E4F,U+4E56,U+4E58,U+4E8C,U+4E92,U+4EA4-4EA5,U+4EA7-4EA8,U+4EC7,U+4ECE,U+4ED3,U+4ED8,U+4EE3,U+4EF6-4EF7,U+4EFD,U+4EFF,U+4F0E-4F10,U+4F19-4F1A,U+4F20,U+4F2F-4F30,U+4F34,U+4F43,U+4F46,U+4F4E,U+4F55,U+4F5B,U+4F70,U+4F73,U+4F88,U+4F97,U+4F9B,U+4FA7,U+4FAF,U+4FBF,U+4FC3-4FC4,U+4FD8,U+4FDD,U+4FED,U+4FEF,U+4FFA,U+500D,U+5012,U+5019,U+5021,U+5047,U+5065,U+507F,U+5085,U+508D,U+50A3,U+50A8,U+50AC,U+50B2,U+50F5,U+513F,U+5145,U+5149,U+5151,U+515A,U+515C,U+516B-516C,U+5171,U+5173,U+5175,U+5178,U+517C,U+5180,U+5188,U+518C,U+51A0,U+51AC,U+51AF-51B0,U+51B2,U+51BB,U+51CB,U+51CF,U+51D1,U+51E0-51E1,U+51E4,U+51F0,U+51F3,U+51F9-51FB,U+51FD,U+5200-5201,U+5206,U+5212,U+521A-521B,U+521D,U+522B,U+522E,U+5230,U+523A,U+523D,U+5241-5242,U+5250-5251,U+5265,U+526A,U+526F,U+5272,U+529E-52A0,U+52A8,U+52C3,U+52FE,U+5305-5306,U+5316-5317,U+5319,U+5320,U+532A,U+5349-534A,U+534E,U+5351,U+5355,U+535A,U+535C,U+535E,U+5373,U+5382,U+5384,U+5395,U+539A,U+53A8,U+53C2,U+53C9-53CA,U+53CD,U+53D1,U+53D8,U+53E0,U+53E4,U+53ED-53EE,U+53F7,U+53FC,U+5403-5404,U+5408-540A,U+540E,U+5420,U+5426-5429,U+542B,U+5435,U+5439,U+543C,U+5446,U+5448,U+544A,U+5475,U+547C,U+548C,U+5490,U+5495,U+54C0,U+54C4,U+54C6,U+54C8,U+54CE,U+54D7,U+54E5,U+54FA,U+54FC,U+5507,U+5509,U+5524,U+552C,U+5531,U+554A,U+5561,U+5589-558A,U+5598,U+559D,U+5609,U+560E,U+5632,U+563F,U+5676,U+568E,U+56DE,U+56E4,U+56F1,U+56FA,U+56FD,U+572D,U+5730,U+573A,U+573E,U+574A,U+574F,U+575A,U+575D,U+575F,U+5782,U+579B,U+57A2,U+57AB,U+57C2-57C3,U+57CE,U+57E0,U+57FA,U+5806,U+5815,U+5821,U+5824,U+5835,U+58A9,U+58C1,U+58D5,U+58F6,U+5904,U+5907,U+590D,U+591A,U+591F,U+5927,U+592B,U+592F,U+5939-593A,U+5949,U+594B,U+5954,U+5956,U+5960,U+5965,U+5978,U+597D,U+5987,U+5992-5993,U+59A8,U+59D1,U+59DC,U+59EC,U+5A25,U+5A5A,U+5AC1,U+5AC9,U+5AE1,U+5B58,U+5B63-5B64,U+5B69,U+5B75,U+5B89,U+5B8F,U+5B98,U+5B9A,U+5B9D,U+5BA0,U+5BA6,U+5BAB,U+5BB3,U+5BB6,U+5BBE,U+5BC2,U+5BC4,U+5BCC,U+5BD2,U+5BDF,U+5BE1,U+5BF8-5BF9,U+5BFC,U+5C01,U+5C06,U+5C14,U+5C16,U+5C18,U+5C1D,U+5C3A,U+5C42,U+5C94,U+5C97,U+5C9B,U+5CB8,U+5CE8,U+5CF0,U+5D07,U+5D14,U+5D29,U+5DDD,U+5DE2,U+5DE5,U+5DE9,U+5DEE,U+5DF1,U+5DF4,U+5E01,U+5E03,U+5E06,U+5E1B,U+5E1D,U+5E26,U+5E2E,U+5E38,U+5E45,U+5E4C,U+5E62,U+5E72,U+5E76,U+5E7B,U+5E7F,U+5E87,U+5E8A,U+5E95,U+5E97,U+5E9A,U+5E9C,U+5E9F,U+5EA6,U+5EFA,U+5F0A,U+5F13,U+5F17-5F18,U+5F1B,U+5F1F,U+5F27,U+5F39,U+5F52-5F53,U+5F69-5F6A,U+5F6C,U+5F7B-5F7C,U+5F85,U+5F88,U+5F8A,U+5F97,U+5FB7,U+5FBD,U+5FC5,U+5FCC,U+5FF1,U+5FFD,U+5FFF-6000,U+6016,U+6020,U+6025,U+602A,U+604D,U+6052,U+6062,U+6068-6069,U+606B,U+606D,U+6076,U+608D,U+6094,U+60A3,U+60B2,U+60B8,U+60BC,U+60D1,U+60E0,U+60E6,U+60E8-60E9,U+60EB,U+60ED-60F0,U+60F6,U+6101,U+611F,U+6124,U+6148,U+614C,U+6167,U+618B,U+61A8,U+61BE,U+61C2,U+61CA,U+6208,U+6210,U+6216,U+6233-6234,U+6237,U+623F,U+6241,U+624D,U+6252-6253,U+626E-626F,U+6273,U+6276,U+627C,U+627F-6280,U+6284,U+628A,U+6296,U+629A,U+62A4-62A5,U+62B1,U+62B5,U+62BD,U+62C2,U+62C5-62C6,U+62CC,U+62D0,U+62D4,U+62DC,U+62E3,U+62E8,U+62F1,U+6301-6302,U+6309,U+6321,U+6324-6325,U+6328,U+632B,U+634C-634D,U+6355,U+6361-6363,U+6376,U+6382,U+6387,U+6389,U+63A3,U+63AA,U+63B8,U+63BA,U+63D2,U+63E3,U+6400-6401,U+640F-6410,U+6413,U+641E,U+642C-642D,U+643D,U+6446,U+6448,U+6467,U+6491,U+64A4,U+64AD-64AE,U+64BC,U+64CD,U+64E6,U+6539,U+653B,U+653E,U+6545,U+654C,U+6556,U+655D-655E,U+6562,U+6566,U+6577,U+658C,U+6591,U+6597,U+65A5,U+65A7,U+65AD,U+65B9,U+65E2,U+65E6,U+65F1,U+6602,U+660C,U+660F,U+6625,U+6643,U+6666,U+6668,U+6697,U+66B4,U+66F4,U+66F9,U+670D,U+671D,U+672C,U+6735,U+673A,U+6746,U+6750-6751,U+675C,U+6760,U+676D,U+676F,U+677F,U+6781,U+6784,U+679C,U+67AB,U+67B6-67B7,U+67C4,U+67CF,U+67D1,U+67DC,U+67E5,U+67EC,U+67F4,U+6807,U+680B,U+6838-6839,U+683C,U+6842,U+6848,U+6853,U+6863,U+6868,U+6886,U+6897,U+68C0,U+68CD,U+68D2,U+68D8,U+68FA,U+6912,U+693D,U+693F,U+695A,U+6982,U+699C,U+69D0,U+69DB,U+69FD,U+6A0A,U+6A2A,U+6A59,U+6A71,U+6B21-6B22,U+6B4C,U+6B64-6B65,U+6B79,U+6B7C,U+6B86,U+6B8B,U+6BB5,U+6BBF,U+6BC1,U+6BD2,U+6BD4-6BD6,U+6BD9,U+6BEB,U+6C1B,U+6C1F,U+6C26,U+6C28,U+6C2E,U+6C47,U+6C49,U+6C57,U+6C5E-6C60,U+6C72,U+6C7E,U+6C89,U+6C9F,U+6CA7,U+6CAA,U+6CB3,U+6CB8,U+6CBD,U+6CCA,U+6CD5,U+6CDB,U+6CE2,U+6CF5,U+6D1E,U+6D2A,U+6D31,U+6D3B,U+6D46-6D47,U+6D4B,U+6D4E,U+6D51,U+6D69,U+6D6E,U+6D77,U+6DA3-6DA4,U+6DA7,U+6DAA,U+6DB5,U+6DB8,U+6DC0,U+6DE1,U+6DEC,U+6DEE,U+6DF3,U+6DF7,U+6E10,U+6E21,U+6E24,U+6E2F,U+6E56,U+6E85,U+6E89,U+6EC1,U+6EC7,U+6ED1,U+6EDA,U+6EE8,U+6EF4,U+6F6E,U+6F84,U+6F88,U+6FB3,U+6FC0,U+6FD2,U+704C,U+706B,U+706F-7070,U+707F,U+708A,U+7092,U+70B3,U+70B9,U+70BD,U+70D8,U+70E6,U+70E9,U+70FD,U+710A,U+7115,U+7119-711A,U+7126,U+714C,U+714E,U+71AC,U+7206,U+7231,U+7236,U+7238-7239,U+7248,U+728A,U+72AF,U+72C4,U+72C8,U+72D0,U+72D7,U+72E0,U+72EC,U+7316,U+731C,U+7334,U+733E,U+73AF,U+73BB,U+73D0,U+73ED,U+745A,U+7470,U+74DC,U+74E3,U+74F7,U+7518,U+752B,U+752D,U+7532,U+7535,U+7538,U+753B,U+7545,U+756A,U+7574,U+7578,U+7586,U+7599,U+75A4,U+75AE-75AF,U+75B5,U+75BE,U+75C5,U+75D5,U+75D8,U+75EA,U+75F4,U+75F9,U+7601,U+7629-762A,U+764C,U+7678,U+767B,U+767D-767E,U+7684,U+7687,U+768B,U+7691,U+76CE,U+76D1-76D2,U+76D6-76D7,U+76EF,U+76FE,U+7763,U+776C,U+7779,U+7785,U+77AA,U+77D7,U+77ED-77EE,U+77FE,U+7840,U+7845,U+7877,U+7889,U+788D,U+7891,U+7898,U+789F,U+78A7,U+78B1,U+78B4,U+78C1,U+78C5,U+78CB,U+78FA,U+7901,U+796D,U+7977-7978,U+798F,U+79BE,U+79C6,U+79C9,U+79E4,U+79EF-79F0,U+79FD,U+7A0B,U+7A17,U+7A20,U+7A3B-7A3D,U+7A3F,U+7A7F,U+7A97,U+7A9C,U+7AEF,U+7AFF,U+7B06,U+7B14,U+7B1B,U+7B26,U+7B28,U+7B2C,U+7B3A,U+7B49,U+7B4F,U+7B54,U+7B56,U+7B79,U+7B80,U+7B8D,U+7B94-7B95,U+7BA1,U+7BAD,U+7BD9,U+7BE1,U+7C07,U+7C27,U+7C3F,U+7C4D,U+7C89,U+7C97,U+7CAA,U+7CB9,U+7CCA,U+7CD5,U+7CD9,U+7E41,U+7EA2,U+7EA7,U+7EAA,U+7EAF,U+7EB2,U+7EB7,U+7EBA,U+7ECA,U+7ED1,U+7ED8-7ED9,U+7EE7,U+7EE9,U+7EF0,U+7EF7-7EF8,U+7F04,U+7F09,U+7F0E,U+7F13-7F14,U+7F16,U+7F1A,U+7F1D,U+7F20,U+7F38,U+7F50,U+7F55,U+7F5A,U+7F62,U+7F94,U+7FB9,U+7FC5,U+7FDF-7FE0,U+7FF0-7FF1,U+7FFB,U+800C,U+8015,U+8017,U+8019,U+8033,U+803B,U+803D,U+803F,U+806A,U+808C,U+809A-809B,U+809D,U+80A0-80A1,U+80A4-80A5,U+80A9-80AA,U+80AE,U+80BA,U+80C6,U+80CC,U+80DE,U+80E1,U+80F3,U+80F6,U+80FA,U+8106,U+810A,U+8116,U+812F,U+8150-8151,U+8179,U+8180,U+818A,U+818F,U+8198,U+81C2,U+81E3,U+81ED,U+822A,U+822C,U+8230-8231,U+8235-8236,U+8239,U+8270,U+827E,U+82AC-82AD,U+82B1,U+82B3,U+82CD,U+82DE-82DF,U+82EF,U+8303,U+8327-8328,U+832C,U+8336,U+8349,U+8350,U+8352,U+835A,U+8361,U+8364,U+8377,U+83B7,U+83C7,U+83CF,U+83DC,U+83E0,U+83F2,U+845B,U+8463,U+846B,U+8471,U+8482,U+848B,U+84D6,U+84DF,U+8521,U+853C-853D,U+8549,U+8584,U+85CF,U+85E9,U+864E,U+866B,U+8679,U+868C,U+8695,U+86CA-86CB,U+86D4,U+86E4,U+86FE,U+8702,U+8749,U+8757,U+8774,U+8776,U+8822,U+8861,U+8865,U+8868,U+886C,U+8884,U+888B,U+88AB,U+88B1,U+88C1,U+88F9,U+8902,U+8910,U+8912,U+8986,U+89C1-89C2,U+89C4,U+89E6,U+8BA1-8BA3,U+8BA5,U+8BB0,U+8BB2-8BB3,U+8BB9,U+8BBD,U+8BBF,U+8BCD,U+8BDA,U+8BDD-8BDE,U+8BE1,U+8BE5,U+8BE7,U+8BF2,U+8BFB,U+8BFD,U+8C03,U+8C0D-8C0E,U+8C17,U+8C24,U+8C37,U+8C41,U+8C46,U+8C62,U+8C6A,U+8C79-8C7A,U+8C89,U+8D1D,U+8D1F,U+8D21-8D22,U+8D25,U+8D27,U+8D29,U+8D2C-8D2D,U+8D2F-8D31,U+8D35,U+8D37,U+8D39-8D3A,U+8D3E-8D3F,U+8D4B-8D4C,U+8D50,U+8D63-8D64,U+8D6B,U+8D74,U+8D76,U+8D81,U+8D85,U+8DCB-8DCC,U+8DDF,U+8DEA,U+8DF5,U+8DFA,U+8E0C,U+8E29,U+8E48,U+8E66,U+8E6C-8E6D,U+8E72,U+8E7F,U+8E87,U+8EAC,U+8EB2,U+8F66,U+8F68,U+8F70,U+8F85,U+8F88-8F8A,U+8F90-8F91,U+8F9C,U+8F9E-8F9F,U+8FA8-8FA9,U+8FAB,U+8FB0,U+8FB9,U+8FBE,U+8FC7,U+8FD4,U+8FD8,U+8FDF,U+8FEA,U+8FED,U+8FF8-8FF9,U+9012,U+9017,U+901B,U+901E,U+9022,U+902E,U+903C,U+9041,U+904D,U+904F,U+9053,U+907F,U+9093,U+90A6,U+90AF,U+90CA,U+90DD,U+90E8,U+90ED,U+90F4,U+90F8,U+90FD,U+9102,U+9119,U+915A,U+9163,U+916C,U+9171,U+9187,U+918B,U+91C7,U+91DC,U+9274,U+9489,U+9492-9493,U+9499,U+949D-949E,U+94A1-94A2,U+94A9,U+94B5,U+94BE,U+94C2,U+94EC,U+94F2,U+9504-9505,U+950B,U+9519,U+9524,U+952D-952E,U+953B,U+9540,U+9550-9551,U+957F,U+95ED,U+95EF,U+95F4,U+95FA,U+9600-9602,U+9610,U+961C,U+961F,U+9632,U+963F,U+9644-9645,U+9648,U+964D,U+965B,U+9661,U+9664,U+9694,U+9698,U+96C6-96C7,U+96CC,U+96CF,U+96D5,U+96F9,U+970D,U+9738,U+975B,U+975E,U+9769,U+9776,U+978D,U+97AD,U+97E9,U+9876,U+987E-987F,U+9881,U+988A,U+989D,U+98A0,U+98A4,U+98CE,U+98DE,U+9910,U+9965,U+996D,U+996F,U+9971,U+9975,U+997C,U+997F,U+9986,U+998B,U+9A70,U+9A73,U+9A7E,U+9A84,U+9A87,U+9A8B,U+9AA8,U+9AB8,U+9AD8,U+9B3C,U+9B42,U+9C8D,U+9CD6,U+9E21,U+9E3D,U+9E3F,U+9E45,U+9E64,U+9EC4,U+9ED1,U+9F0E,U+9F13,U+9F3B,U+9F7F,U+9F9A,U+9F9F"
800
+ }),
801
+ Object.freeze({
802
+ family: "Aha Hand",
803
+ file: "wenkai-gb-2-2a649892.woff2",
804
+ bytes: 317648,
805
+ unicodeRange: "U+4E03,U+4E09-4E0A,U+4E14,U+4E16,U+4E18,U+4E24,U+4E27,U+4E34,U+4E3D-4E3E,U+4E43,U+4E45,U+4E48,U+4E50,U+4E52-4E54,U+4E5D-4E5E,U+4E70-4E71,U+4E73,U+4E7E,U+4E86,U+4E8B,U+4E8F,U+4E95,U+4EA2,U+4EA9,U+4EAC,U+4EAE,U+4EB2,U+4EBA,U+4EC0-4EC1,U+4EC5-4EC6,U+4ECA-4ECB,U+4ECD,U+4ED1,U+4ED5,U+4EDF,U+4EE4,U+4EEC,U+4EFB,U+4F01,U+4F1E,U+4F24,U+4F26,U+4F36,U+4F38,U+4F60,U+4F69,U+4F6C,U+4F7F,U+4F8B,U+4F8D,U+4FA3,U+4FA5,U+4FA8-4FA9,U+4FB5,U+4FCA,U+4FCF-4FD0,U+4FE9,U+4FF1,U+5014,U+501F,U+5026,U+502A,U+503E,U+504F,U+5076,U+5080,U+5088,U+50BB,U+50DA,U+50E7,U+50FB,U+5112,U+5121,U+514B,U+514D,U+5162,U+5165,U+5168,U+516D,U+5170,U+5176-5177,U+5185,U+5189,U+5192,U+5195,U+5197,U+519B-519C,U+51B3,U+51B5,U+51B7,U+51C0,U+51C4,U+51C9,U+51CC,U+51DB,U+51DD,U+51ED,U+51EF,U+5203,U+5207,U+520A,U+5217-5218,U+5220,U+5224,U+5228-5229,U+5238-5239,U+523B,U+524D,U+5256,U+5267,U+5269,U+527F,U+5288,U+529B,U+529D,U+52A3,U+52AA-52AB,U+52B1-52B3,U+52BF,U+52C9,U+52D2,U+52D8,U+52DF,U+52E4,U+52FA,U+5321,U+5339-533A,U+533F,U+5341,U+5343,U+5347,U+5356-5357,U+5361-5362,U+5364,U+536F,U+5374-5375,U+5377,U+537F,U+5386,U+5389,U+5398,U+53A9,U+53BB,U+53C1,U+53D6-53D7,U+53DB,U+53E3,U+53E5-53E6,U+53EB,U+53EF,U+53F2,U+540D,U+540F,U+5415,U+5417,U+541B,U+541D,U+542D,U+542F,U+5450,U+5455,U+545B,U+5462,U+5478,U+547B,U+547D,U+5480,U+5486,U+548E,U+5496,U+5499,U+54AF,U+54B3,U+54C1,U+54E6,U+54E8-54EA,U+54ED,U+552E,U+5543,U+5546,U+5564-5566,U+556A,U+556E,U+5580,U+5584,U+5587,U+55B7,U+55D3,U+55DC,U+561B,U+5668,U+566C,U+56B7,U+56BC,U+56CA,U+56DA,U+56F0,U+5703,U+5708,U+5723,U+5747,U+574E,U+5751,U+5757,U+5761,U+5764,U+576A,U+576F,U+5777,U+5783-5784,U+5792,U+57A6,U+57AE,U+57CB,U+57D4,U+57F9,U+5811,U+582A,U+585E,U+5883,U+5892-5893,U+5899,U+58A8,U+58E4,U+58EB-58EC,U+58F0,U+58F3,U+5931,U+5938,U+5947-5948,U+594E,U+5951,U+5962,U+5973-5974,U+5976,U+5982,U+5988,U+598A,U+5999,U+59AE,U+59B9,U+59BB,U+59C6,U+59CB,U+59D0,U+59E5,U+5A04,U+5A07,U+5A18,U+5A1C,U+5A1F-5A20,U+5A29,U+5A36,U+5A46,U+5A6A,U+5A76,U+5A92,U+5A9A,U+5AC2,U+5AE9,U+5B54,U+5B5F,U+5B6A,U+5B7A,U+5B7D,U+5B81,U+5B88,U+5B9E,U+5BA1-5BA2,U+5BA4,U+5BB9,U+5BBD,U+5BC6-5BC7,U+5BD0,U+5BDD-5BDE,U+5BE5,U+5BFF,U+5C04,U+5C11,U+5C1A,U+5C31,U+5C38,U+5C3C-5C3D,U+5C3F-5C41,U+5C45,U+5C48,U+5C4A,U+5C4E-5C4F,U+5C61,U+5C65,U+5C71,U+5C82,U+5CAD,U+5CBF,U+5CE6,U+5CED,U+5CFB,U+5D0E,U+5D4C,U+5DE7-5DE8,U+5DFE,U+5E02,U+5E08,U+5E15,U+5E18,U+5E3D,U+5E42,U+5E55,U+5E73-5E74,U+5E86,U+5E90,U+5E93,U+5E99,U+5E9E,U+5EB7,U+5EC9-5ECA,U+5ED3,U+5ED6,U+5F00,U+5F03-5F04,U+5F0F,U+5F25,U+5F31,U+5F3A,U+5F55,U+5F6D,U+5F84,U+5F8B,U+5F98,U+5FCD,U+5FD9,U+5FEB,U+5FF5,U+6012,U+6015,U+601C,U+602F,U+6043,U+604B,U+6050,U+6070,U+6073,U+607C,U+6084,U+60A8,U+60AF,U+60C5,U+60CA,U+60E7,U+60F9,U+6127,U+614E,U+6151,U+6155,U+6162,U+6168,U+6177,U+61D2,U+61E6,U+620E,U+6212,U+621A,U+622A,U+622E,U+6247,U+624B,U+6251,U+6254,U+625B,U+6263,U+6266,U+6269,U+626B,U+626D,U+6270,U+6279,U+6289,U+6297,U+629B,U+62A0-62A2,U+62A8,U+62AB,U+62B9,U+62BF,U+62C7-62C9,U+62CD-62CE,U+62D2,U+62D8,U+62DF,U+62E2,U+62E6-62E7,U+62EC-62ED,U+62F3,U+62F7,U+62FC,U+62FE-62FF,U+630E,U+631B,U+6320,U+632A,U+6346,U+634E-6350,U+635E,U+6367,U+636E,U+6377,U+637B,U+6388,U+6390,U+6392,U+6398,U+63A0,U+63A5,U+63A7,U+63B3,U+63C9,U+63CF,U+63E9-63EA,U+63ED,U+63FD,U+6402,U+6405,U+6414,U+6444,U+6469,U+6478-6479,U+6482,U+6485,U+6487,U+6492,U+64A9,U+64AC,U+64B5,U+64C2,U+64C5,U+64CE,U+64D2,U+6500,U+6518,U+652B,U+6536,U+654F,U+6551,U+6559,U+655B,U+6563,U+656C,U+6572,U+6599,U+65A4,U+65BD,U+65C1,U+65C5,U+65D7,U+65E5,U+65E7,U+65F6-65F7,U+6606,U+660E,U+6627,U+662F,U+664B-664C,U+6652,U+666E-666F,U+6674,U+6676,U+667E,U+6696,U+66AE,U+66DD,U+66F2,U+66FC,U+670B,U+6717,U+671F,U+6728,U+672B,U+6734,U+6740,U+6743,U+6749,U+674E,U+6765,U+6770,U+6797,U+679A,U+67AA,U+67AF,U+67D0,U+67D2-67D4,U+67E0,U+67EF,U+67F3,U+67FF,U+680F,U+6816-6817,U+6846,U+6851,U+6854,U+6865,U+6881,U+6885,U+68A2,U+68A6,U+68A8,U+68C9,U+68CB,U+68DA,U+68EE,U+68F1,U+68F5,U+695E,U+6977,U+697C,U+6994,U+69B4,U+69B7,U+6A21,U+6A47,U+6AAC,U+6B20,U+6B27,U+6B3A,U+6B3E,U+6B49,U+6B67,U+6BB4,U+6BCD,U+6BCF,U+6BD7,U+6BDB,U+6C0F,U+6C11,U+6C13-6C14,U+6C16,U+6C22,U+6C2F-6C30,U+6C42,U+6C55,U+6C5D,U+6C7D,U+6C81,U+6C88,U+6C8F,U+6C99,U+6C9B,U+6CA1,U+6CA4-6CA6,U+6CAB,U+6CAE,U+6CC5,U+6CC9,U+6CCC,U+6CDE,U+6CE1,U+6CE3,U+6CE5,U+6CEA,U+6CFC,U+6D01,U+6D12,U+6D1B,U+6D25,U+6D3D-6D3E,U+6D41,U+6D45,U+6D53,U+6D5A,U+6D66,U+6D6A,U+6D78,U+6D85,U+6D89,U+6D9D,U+6D9F,U+6DA6,U+6DA9,U+6DCB,U+6DD6,U+6DF1,U+6E05,U+6E17,U+6E20,U+6E34,U+6E3A,U+6E43,U+6E7F,U+6E83,U+6E9C,U+6EB6,U+6EBA,U+6EE1,U+6EE4-6EE6,U+6F02,U+6F06,U+6F0F,U+6F13,U+6F20,U+6F2B,U+6F58,U+6F5C,U+6F5E,U+6F66,U+6F8E,U+6F9C,U+7011,U+706D,U+7075,U+7078,U+7089,U+7094-7095,U+70AC,U+70AE-70AF,U+70BC,U+70C2,U+70C8,U+70D9,U+70E4,U+70E7,U+70EC-70ED,U+70F9,U+7136,U+715E,U+7164,U+717D,U+7194,U+71C3,U+71CE,U+722C,U+7235,U+7247,U+724C,U+725B,U+725F,U+7261-7262,U+7267,U+7272,U+7275,U+7281,U+72AC,U+72C2,U+72D9,U+72DE,U+72E1,U+72EE,U+72F8,U+72FC,U+730E,U+731B,U+732B,U+7387,U+7396,U+739B,U+73AB,U+73B2,U+73CA,U+7403,U+7405-7406,U+7409,U+7433-7436,U+743C,U+745E-745F,U+7483,U+74E2,U+74E4,U+74F6,U+751A,U+751F,U+7525,U+7533,U+7537,U+754C,U+7554,U+7559,U+7565-7566,U+7597,U+759A,U+759F,U+75A5,U+75B2,U+75BD,U+75C9-75CA,U+75DE,U+75E2,U+7624,U+7638,U+7686,U+76AE,U+76BF,U+76C6,U+76D4,U+76D8,U+76DB,U+76DF,U+76EE,U+76F2,U+76FC,U+7701,U+7709,U+770B,U+7720,U+772F,U+7736-7737,U+775B,U+7766,U+776B,U+7784,U+7792,U+77A5,U+77A7,U+77DB,U+77E2,U+77E9,U+77EB,U+77F3,U+77FF,U+7801-7802,U+780C-780D,U+7812,U+7830,U+7834,U+7837,U+783E,U+786B,U+786E,U+787C,U+788C,U+78B0,U+78BE,U+78CA,U+78D0,U+78D5,U+78E8,U+78F7,U+793A,U+793C,U+793E,U+7941,U+7948,U+795E,U+7968,U+7981,U+7984,U+79BB,U+79BD,U+79CB,U+79D1-79D2,U+79D8,U+79E6,U+79F8,U+7A0D,U+7A46,U+7A76-7A77,U+7A7A,U+7A83,U+7A8D,U+7A96,U+7A98,U+7A9F,U+7AA5,U+7ABF,U+7ACB,U+7ADE-7ADF,U+7AE3,U+7AED,U+7B3C,U+7B4B,U+7B50,U+7B5B,U+7B77,U+7B7E,U+7BA9,U+7BC7,U+7BD3,U+7BEE,U+7BF1,U+7BF7,U+7C73,U+7C7B,U+7C92,U+7C95,U+7CAE,U+7CB1,U+7CB3,U+7CBE,U+7CDC,U+7CE0,U+7CEF,U+7D27,U+7D2F,U+7EA0,U+7EAB,U+7EB1,U+7EB3,U+7EB6,U+7EBD,U+7EC3,U+7EC5,U+7ECD,U+7ECF,U+7ED2-7ED3,U+7ED5,U+7EDC-7EDE,U+7EE2,U+7EF3,U+7EF5,U+7EFF,U+7F05-7F06,U+7F15,U+7F2E,U+7F34,U+7F3A,U+7F57,U+7F8C,U+7F8E,U+7F9A,U+7FA4,U+7FD8,U+8001,U+8003,U+8010,U+802A,U+8042,U+804A-804B,U+8054,U+8058,U+805A,U+8089,U+808B,U+80AF,U+80BE,U+80D6,U+80DA,U+80DC,U+80EF,U+80FD,U+8109,U+8110-8111,U+8113,U+811A,U+8138,U+813E,U+814A,U+8154,U+816E,U+817B,U+819C,U+81A8,U+81B3,U+81FC,U+8205,U+820C-820D,U+826F,U+8272,U+8282,U+828D,U+8292,U+82A5-82A6,U+82B9,U+82D7,U+82DB,U+82E5-82E6,U+82EB,U+82F9,U+8302,U+8304-8305,U+830E,U+832B,U+8338-8339,U+8346,U+8354,U+8363,U+8386,U+8389,U+838E,U+83AB,U+83B1-83B2,U+83BD,U+83CA,U+83CC,U+83E9,U+83F1,U+840C-840D,U+841D,U+8428,U+843D,U+8461,U+8475,U+8499,U+84B2,U+84C9,U+84DD,U+84EC,U+8511,U+8513,U+852B,U+8537,U+854A,U+857E,U+85C9,U+85D0,U+85D5,U+8611,U+864F-8651,U+8671,U+8680,U+8682,U+86C6-86C7,U+86EE,U+871C,U+8721,U+878D,U+879F,U+87BA,U+8815,U+8857,U+886B,U+888D,U+88C2,U+88D9,U+88E4,U+88F3-88F4,U+88F8,U+8925,U+895F,U+89C5-89C6,U+89C8-89C9,U+89D2,U+89E3,U+8A93,U+8B66,U+8B6C,U+8BA4,U+8BA9,U+8BAB,U+8BBA,U+8BBE,U+8BC0,U+8BC4,U+8BC6,U+8BD5,U+8BD7,U+8BEB,U+8BF7,U+8BFA,U+8BFE,U+8C05,U+8C0B,U+8C1C,U+8C26,U+8C28-8C29,U+8C2C,U+8C30-8C31,U+8C34,U+8C8C,U+8D2B,U+8D38,U+8D41-8D42,U+8D4A,U+8D4F,U+8D54,U+8D56,U+8D5B,U+8D61,U+8D66,U+8D77,U+8D8B,U+8DA3,U+8DB4,U+8DD1,U+8DDD,U+8DE8,U+8DEF,U+8E1E,U+8EAB,U+8EAF,U+8F6E-8F6F,U+8F7B,U+8F7F,U+8F83,U+8F86,U+8FA3,U+8FB1,U+8FBD,U+8FC1,U+8FC4,U+8FC8,U+8FD1,U+8FDB,U+8FDE,U+8FEB,U+8FF7,U+9002,U+9006,U+901D,U+903B,U+9063,U+90A3,U+90B1,U+90B5,U+90BB,U+90CE,U+90E1,U+914B,U+914D,U+9152,U+916A,U+9175-9177,U+917F,U+919A-919B,U+91CA,U+91CC,U+91CF,U+91D1,U+948E,U+94A0,U+94A6-94A7,U+94AE,U+94B1,U+94B3,U+94C3,U+94C5-94C6,U+94DD,U+94ED,U+94F0,U+94FA,U+94FE,U+9510,U+951A,U+9523,U+9526,U+952F-9530,U+9539,U+9541,U+954A,U+954D,U+955C,U+9563,U+956D,U+9570,U+95E8,U+95EA,U+95F0,U+95F7,U+95F9,U+95FD,U+9611,U+9614,U+962E,U+9636,U+9646-9647,U+964B-964C,U+9655,U+966A,U+9675,U+9686,U+96B6,U+96BE,U+96C0,U+96F6-96F7,U+9709,U+9713,U+9716,U+9732,U+9739,U+9752,U+9756,U+9759,U+9760-9762,U+9773,U+9798,U+97A0,U+97E7,U+97ED,U+97F6,U+9877,U+9885-9888,U+9891,U+9897,U+98A7,U+98D8,U+98DF,U+9970,U+9976,U+997A,U+9981,U+9988,U+998F,U+9992,U+9996,U+9A6C,U+9A71,U+9A74,U+9A76,U+9A79,U+9A82,U+9A86,U+9A8F,U+9A91,U+9A97,U+9A9A,U+9AA1,U+9B41,U+9B44,U+9B54,U+9C81,U+9CA4,U+9CB8,U+9CC3,U+9CDE,U+9E1F,U+9E23,U+9E25,U+9E43,U+9E4A,U+9E4F,U+9E7F,U+9E93,U+9EA6,U+9EBB,U+9ECE,U+9ED4,U+9ED8,U+9F50,U+9F84,U+9F8B,U+9F99"
806
+ }),
807
+ Object.freeze({
808
+ family: "Aha Hand",
809
+ file: "wenkai-gb-3-6949c050.woff2",
810
+ bytes: 311704,
811
+ unicodeRange: "U+4E00,U+4E07-4E08,U+4E0B,U+4E0E,U+4E13,U+4E1A,U+4E1D,U+4E25,U+4E2B,U+4E2D,U+4E38,U+4E3A-4E3B,U+4E49,U+4E4B-4E4D,U+4E59,U+4E5F-4E61,U+4E66,U+4E88-4E89,U+4E8E,U+4E91,U+4E94,U+4E9A-4E9B,U+4EA1,U+4EA6,U+4EAB,U+4EAD,U+4EBF,U+4ED4,U+4ED6-4ED7,U+4ED9,U+4EE5,U+4EEA,U+4EF0,U+4EF2,U+4F0A,U+4F0D,U+4F11,U+4F17-4F18,U+4F1F,U+4F2A,U+4F3A,U+4F3C,U+4F4D,U+4F4F-4F51,U+4F53,U+4F59,U+4F5C,U+4F63,U+4F6F,U+4F84,U+4F9D,U+4FA0,U+4FA6,U+4FAE,U+4FD7,U+4FDE,U+4FE1,U+4FEE,U+5018,U+501A,U+503A,U+503C,U+505A,U+505C,U+5077,U+50CF,U+50F3,U+5141,U+5143-5144,U+5146,U+5148,U+5154,U+5174,U+5179,U+517B,U+517D,U+518D,U+5199,U+51A4,U+51B6,U+51C6,U+51F6,U+51F8,U+51FF,U+5211,U+5219,U+5236-5237,U+5243,U+524A,U+5254,U+52A1,U+52A9,U+52C7,U+52CB,U+52FF-5300,U+5308,U+531D,U+5323,U+533B,U+5348,U+534F,U+5352-5353,U+5360,U+5367,U+536B,U+5370-5371,U+5378,U+5385,U+538B-538C,U+539F,U+53A2,U+53A6,U+53BF,U+53C8,U+53CB-53CC,U+53D4,U+53D9,U+53EA,U+53EC,U+53F0,U+53F3,U+53F6,U+53F8-53F9,U+5401,U+540C,U+5410-5411,U+5413,U+541E-541F,U+542C,U+542E,U+5431,U+5434,U+5438,U+543B,U+543E,U+5440,U+5458,U+545C,U+5468,U+5473,U+548B,U+548F,U+5492,U+54A8,U+54AC,U+54B1,U+54B8,U+54BD,U+54C7,U+54C9,U+54CD,U+54D1,U+54DF,U+54EE,U+54F2,U+5501,U+5506,U+5510,U+552F,U+553E,U+5544,U+5578,U+557C,U+5582,U+559C,U+55A7,U+55B3,U+55BB,U+55C5,U+55E1,U+55E3,U+55FD,U+5618,U+5631,U+5634,U+5636,U+563B,U+564E,U+566A,U+568F,U+56A3,U+56DB,U+56E0,U+56E2,U+56ED,U+56F4,U+56FE,U+5706,U+571F,U+5728,U+5740,U+574D,U+5750,U+575B,U+575E,U+5760,U+5766,U+578B,U+57A3,U+57DF,U+5802,U+5830,U+584C,U+5851,U+5854,U+5858,U+586B,U+5885,U+589E-589F,U+58EE,U+58F9,U+590F,U+5915-5916,U+591C,U+5929-592A,U+592E,U+5934,U+5937,U+5944,U+594F,U+5957,U+5979,U+5984,U+5986,U+5996,U+59A5,U+59D3-59D4,U+59DA,U+59E8,U+59FB,U+59FF,U+5A01,U+5A03,U+5A31,U+5A49,U+5A74,U+5A7F,U+5AB3,U+5ACC,U+5B50,U+5B55,U+5B57,U+5B59,U+5B5C-5B5D,U+5B66,U+5B70,U+5B83,U+5B85,U+5B87,U+5B8B-5B8C,U+5B97,U+5B99,U+5B9B-5B9C,U+5BA3,U+5BAA,U+5BB0,U+5BB4-5BB5,U+5BBF,U+5BC5,U+5BD3,U+5BE8,U+5BFA-5BFB,U+5C09-5C0A,U+5C0F,U+5C24,U+5C27,U+5C39,U+5C3E,U+5C49,U+5C4B,U+5C51,U+5C55,U+5C5E,U+5C60,U+5C6F,U+5C79,U+5C7F,U+5C81,U+5CA9,U+5CB3,U+5CD9,U+5CE1,U+5CEA,U+5D16,U+5D2D,U+5DCD,U+5DDE,U+5DE1,U+5DE6,U+5DEB,U+5DF2-5DF3,U+5DF7,U+5E05,U+5E0C,U+5E10,U+5E16,U+5E1A,U+5E1C,U+5E27,U+5E2D,U+5E78,U+5E7C-5E7D,U+5E84,U+5E8F,U+5E94,U+5EA7,U+5EAD,U+5EB6,U+5EB8,U+5EF6-5EF7,U+5F02,U+5F15,U+5F20,U+5F26,U+5F2F,U+5F5D,U+5F62,U+5F64,U+5F66,U+5F70-5F71,U+5F79,U+5F80-5F81,U+5F90,U+5F92,U+5FA1,U+5FAA,U+5FAE,U+5FC3,U+5FC6,U+5FD7-5FD8,U+5FE0,U+5FE7,U+5FFB,U+6001-6002,U+600E,U+6014,U+601D,U+6027-6028,U+603B,U+6055,U+6064,U+606C,U+606F,U+607F,U+6089,U+609F-60A0,U+60A6,U+60AC,U+60CB,U+60D5,U+60DC,U+60DF,U+60F3,U+60FA,U+6108-6109,U+610F,U+611A,U+613F,U+6170,U+618E,U+61C8,U+620A,U+620C-620D,U+620F,U+6211,U+6218,U+6240,U+624E,U+6258,U+6267,U+626C,U+627E,U+6291-6293,U+6295,U+6298,U+62AC,U+62BC,U+62C4,U+62D3,U+62D6,U+62D9,U+62DB,U+62E5,U+62E9,U+62EF,U+62F4,U+62FD,U+6307,U+6311,U+6316,U+631A,U+631D-631F,U+6323,U+632F,U+633A,U+633D,U+6342,U+6345,U+6349,U+635F,U+6380,U+638C,U+638F,U+6396,U+63A2,U+63A8-63A9,U+63B7,U+63CD,U+63D0,U+63D6,U+63E1,U+63F4,U+641C,U+642A,U+643A,U+6447,U+644A,U+6454,U+6458,U+6495,U+649E,U+64B0,U+64DE,U+6512,U+652F,U+653F,U+6548,U+6570,U+6574,U+6587,U+658B,U+659C,U+659F,U+65A1,U+65A9,U+65AF-65B0,U+65CB,U+65CF,U+65E0,U+65E8-65E9,U+65EC-65ED,U+65FA,U+6613-6614,U+661F-6620,U+6628,U+662D,U+663C,U+663E,U+6653,U+6655,U+665A,U+6664,U+6670,U+667A,U+6682,U+6687,U+6691,U+66D9,U+66F0,U+66F3,U+66FE-6700,U+6708-6709,U+6714,U+671B,U+672A,U+672D,U+672F,U+6731,U+673D,U+6742,U+674F,U+6756,U+675F,U+6761,U+6768,U+677E,U+6789,U+6790,U+6795,U+679D,U+67A2-67A3,U+67DE,U+67F1,U+6805,U+6808,U+6811,U+6813,U+6821,U+682A,U+6837,U+683D,U+6843,U+6845,U+684C,U+6850,U+6869,U+6876,U+68A7,U+68AD,U+68AF-68B0,U+68B3,U+68D5,U+68E0,U+6905,U+690D-690E,U+692D,U+6930,U+6954,U+6986,U+69A8,U+6A1F,U+6A31,U+6A61,U+6A80,U+6A84,U+6B23,U+6B32,U+6B47,U+6B62-6B63,U+6B66,U+6B6A,U+6B7B,U+6B83,U+6B89-6B8A,U+6B96,U+6BB7,U+6BC5,U+6BCB,U+6BE1,U+6BEF,U+6C27,U+6C34,U+6C38,U+6C40-6C41,U+6C50,U+6C5B,U+6C61,U+6C64,U+6C6A,U+6C70,U+6C79,U+6C82-6C83,U+6CB9,U+6CBB-6CBC,U+6CBE-6CBF,U+6CC4,U+6CE8,U+6CF0,U+6CF3,U+6CFB,U+6CFD,U+6D0B,U+6D17,U+6D32,U+6D3C,U+6D4A,U+6D59,U+6D74,U+6D82,U+6D88,U+6D8C,U+6D8E,U+6D95,U+6D9B,U+6DA1,U+6DA8,U+6DAF,U+6DB2,U+6DC4,U+6DC6,U+6DCC,U+6DD1,U+6DD8,U+6DE4,U+6DEB,U+6DF9,U+6DFB,U+6E0A,U+6E0D,U+6E14,U+6E1D,U+6E23,U+6E29,U+6E2D,U+6E38,U+6E4D,U+6E58,U+6E5B,U+6E7E,U+6E90,U+6EA2,U+6EAA,U+6EAF,U+6ECB,U+6ED3-6ED4,U+6EDE,U+6EE9,U+6F14,U+6F31,U+6F33,U+6F3E,U+6F4D,U+6F6D,U+6FA1,U+7076,U+707C,U+707E,U+708E,U+7099,U+70AD,U+70B8,U+70C1,U+70C3,U+70DB,U+70DF,U+70EB,U+70EF,U+70F7,U+7109,U+7130,U+7167,U+716E,U+7184,U+718A,U+718F,U+7199,U+719F,U+71D5,U+71E5,U+722A,U+7237,U+723D,U+7259,U+7269,U+7279-727A,U+7280,U+72B6,U+72B9,U+72ED,U+72F0-72F1,U+7329-732A,U+732E,U+733F,U+736D,U+7384,U+7389,U+738B,U+73A9,U+73B0,U+73CD,U+73E0,U+7410,U+7422,U+7476,U+74E6,U+74EE,U+7504,U+751C,U+7528-7529,U+7530-7531,U+754F,U+755C,U+758F,U+7591,U+75A1,U+75AB,U+75B9,U+75BC,U+75C7-75C8,U+75D2,U+75D4,U+75DB,U+75F0,U+761F,U+7626,U+762B,U+7634,U+7663,U+7682,U+7696,U+76B1,U+76C2,U+76C5,U+76C8,U+76CA,U+76CF-76D0,U+76F4,U+76F8,U+771F,U+7728-7729,U+773A,U+773C,U+7740-7741,U+7761,U+778E,U+77A9,U+77AC,U+77B3,U+77BB,U+77E3,U+77E5,U+77FD,U+7814,U+7816,U+781A,U+7827,U+7838,U+7852,U+7855,U+785D,U+786C,U+788E,U+7897,U+78B3,U+7956,U+795D,U+795F,U+7965,U+79B9,U+79C0-79C1,U+79C3,U+79CD,U+79DF,U+79E7,U+79E9,U+79FB,U+7A00,U+7A0E,U+7A1A,U+7A33,U+7A57,U+7A74,U+7A81,U+7A84,U+7A91-7A92,U+7A9D,U+7AD6,U+7AD9,U+7AE0,U+7AE5,U+7AF9,U+7B0B,U+7B11,U+7B51-7B52,U+7B97,U+7BB1,U+7BC6,U+7C7D,U+7C98,U+7C9F,U+7CA4-7CA5,U+7CD6,U+7CDF,U+7CFB,U+7D0A,U+7D20,U+7D22,U+7D2B,U+7D6E,U+7E82,U+7EA4,U+7EA6,U+7EAC,U+7EB5,U+7EB8-7EB9,U+7EBF,U+7EC4,U+7EC6-7EC8,U+7ECE,U+7EDA,U+7EDF,U+7EE3,U+7EE5-7EE6,U+7EEA,U+7EED,U+7EF4,U+7EFC-7EFD,U+7F00,U+7F18,U+7F28-7F29,U+7F51,U+7F69-7F6A,U+7F6E,U+7F72,U+7F8A,U+7F9E,U+7FA1,U+7FBD,U+7FC1,U+7FCC,U+7FD4,U+7FFC,U+8000,U+8005,U+800D,U+8018,U+8036,U+8038,U+804C,U+8083-8084,U+8086-8087,U+8096,U+8098,U+80A2,U+80B2,U+80BF-80C1,U+80C3,U+80CE,U+80F0,U+80F8,U+8102,U+810F,U+8131,U+8146,U+814B,U+8155,U+8165,U+8170,U+817A,U+817E-817F,U+819B,U+819D,U+81C0,U+81C3,U+81C6,U+81EA,U+81F3-81F4,U+81FB,U+8200,U+8206,U+8212,U+8214,U+821C,U+821E-821F,U+8237,U+8247,U+8258,U+8273,U+827A,U+828B,U+829C-829D,U+82AF,U+82BD,U+82C7,U+82CF,U+82D1,U+82D4,U+82F1,U+8301,U+8335,U+8367,U+836B,U+836F,U+83B9,U+8404,U+840E,U+8424-8425,U+8427,U+8457,U+846C,U+849C,U+84B8,U+84C4,U+84D1,U+8517,U+851A,U+852C,U+8574,U+859B,U+85AA,U+85AF,U+85E4,U+85FB,U+8638,U+865A,U+865E,U+867D-867E,U+8681,U+868A,U+869C,U+86A4,U+86C0,U+86D9,U+86DB,U+86F0,U+86F9,U+8700,U+8712,U+8715,U+8717-8718,U+8747,U+874E,U+87F9,U+8840,U+8845,U+884C-884D,U+8854,U+8859,U+8863,U+8870,U+8877,U+8881,U+8892,U+8896,U+889C,U+88AD,U+88C5,U+88D4-88D5,U+892A,U+8944,U+897F,U+8981,U+8A00,U+8A79,U+8A89-8A8A,U+8BA8,U+8BAD-8BAF,U+8BB6,U+8BB8,U+8BBC,U+8BC1,U+8BC5,U+8BC8-8BCA,U+8BCC,U+8BD1,U+8BDB,U+8BE2-8BE3,U+8BE6,U+8BEC-8BED,U+8BEF,U+8BF1,U+8BF4-8BF5,U+8BF8,U+8C01,U+8C06,U+8C08,U+8C0A,U+8C10,U+8C13,U+8C1A,U+8C22-8C23,U+8C2D,U+8C4C,U+8C61,U+8C6B,U+8D1E,U+8D23-8D24,U+8D26,U+8D28,U+8D2A,U+8D2E,U+8D34,U+8D3C,U+8D43-8D44,U+8D4E,U+8D58,U+8D5A,U+8D5E,U+8D60,U+8D62,U+8D70,U+8D75,U+8D8A,U+8D9F,U+8DB3,U+8DBE,U+8DC3,U+8DF3,U+8E0A,U+8E0F,U+8E22,U+8E2A,U+8E44,U+8E4B,U+8E81,U+8EBA,U+8F67,U+8F69,U+8F6C,U+8F74,U+8F7D,U+8F93,U+8F95-8F97,U+8F99,U+8F9B,U+8FC2,U+8FC5,U+8FCE,U+8FD0,U+8FD9,U+8FDC-8FDD,U+8FE2,U+8FF0,U+8FFD,U+9000-9001,U+9003,U+9009-900A,U+900F-9010,U+9014,U+901A,U+901F-9020,U+9038,U+903E,U+9042,U+9047,U+9057,U+9065,U+906D-906E,U+9075,U+9080,U+9091,U+90A2,U+90AA,U+90AE,U+90B9,U+90C1,U+90D1,U+90E7,U+9149,U+914C,U+9157,U+915D-915E,U+9165,U+916E,U+9178,U+9189,U+9192,U+91C9,U+91CD-91CE,U+9488,U+949F,U+94A5,U+94A8,U+94BB,U+94C0-94C1,U+94DC,U+94E1,U+94E3,U+94F1,U+94F6,U+94F8,U+9500-9501,U+9508,U+950C,U+9511,U+9517,U+9521,U+9525,U+9528,U+9547,U+9576,U+95EE,U+95F2,U+95F8,U+95FB,U+9605,U+9609,U+960E,U+9633-9635,U+963B,U+9640,U+9650,U+9662,U+9668-9669,U+9676-9677,U+9685,U+968B,U+968F-9690,U+9699,U+969C,U+96A7,U+96C1,U+96C4-96C5,U+96CD,U+96E8,U+96EA,U+96FE,U+9700,U+9704,U+9707,U+971C,U+971E,U+9774,U+978B,U+97E6,U+97F3,U+97F5,U+9875,U+9879-987B,U+987D,U+9882,U+9884,U+9890,U+9893,U+9896,U+9898,U+989C,U+996E,U+9972,U+9985,U+9999,U+9A6D-9A6F,U+9A7B-9A7C,U+9A8C,U+9AA4,U+9AD3,U+9B03,U+9B4F,U+9C7C,U+9C9C,U+9E26,U+9E2D,U+9E2F,U+9E33,U+9E35,U+9E70,U+9ECD,U+9F20"
812
+ }),
813
+ Object.freeze({
814
+ family: "Aha Hand",
815
+ file: "wenkai-gb-4-375dc3b3.woff2",
816
+ bytes: 257596,
817
+ unicodeRange: "U+4E0C,U+4E10,U+4E15,U+4E1E,U+4E28,U+4E36,U+4E3F,U+4E47,U+4E5C,U+4E69,U+4E8D,U+4E93,U+4E98,U+4E9F-4EA0,U+4EB3,U+4EB5,U+4EBB,U+4EC2-4EC4,U+4EC9,U+4EDD-4EDE,U+4EE1,U+4EE8,U+4EEB,U+4EF3,U+4EF5,U+4F09,U+4F1B,U+4F22,U+4F25,U+4F27,U+4F2B,U+4F32,U+4F3D,U+4F57-4F58,U+4F5A,U+4F5D-4F5F,U+4F64-4F65,U+4F67,U+4F74,U+4F76,U+4F7B-4F7C,U+4F7E,U+4F83,U+4F89,U+4F8F,U+4F91,U+4F94,U+4FAA,U+4FAC,U+4FC5,U+4FCE,U+4FD1,U+4FDA,U+4FDC,U+4FDF,U+4FE3,U+4FE6,U+4FE8,U+4FEA,U+4FF3,U+4FF8,U+4FFE,U+500C,U+500F,U+501C,U+5025,U+5028-5029,U+502C-502E,U+5043,U+5048,U+504C,U+504E,U+5055,U+506C,U+507B,U+507E,U+50A5,U+50A7,U+50A9,U+50BA,U+50D6,U+50E6,U+50EC-50EE,U+5106-5107,U+510B,U+5140,U+5155-5156,U+516E,U+5181-5182,U+5196,U+51A2,U+51A5,U+51AB,U+51B1,U+51BC-51BD,U+51C7,U+51EB,U+51F5,U+51FC,U+5202,U+5208,U+520D-520E,U+522D,U+5233,U+523F-5240,U+524C,U+525C,U+525E,U+5261,U+527D,U+5281-5282,U+5290,U+5293,U+52A2,U+52AC-52AD,U+52BE,U+52D0,U+52D6,U+52F0,U+52F9,U+530D,U+530F-5310,U+5315,U+531A,U+5326,U+532E,U+533E,U+5345,U+535F,U+5363,U+5366,U+5369,U+536E,U+537A,U+538D,U+539D,U+53A3,U+53A5,U+53AE,U+53B6,U+53DF,U+53E8-53E9,U+53F1,U+53F5,U+53FB,U+53FD,U+5406,U+5412,U+5416,U+5421,U+5423,U+5432,U+5443,U+544B,U+5452-5454,U+5456-5457,U+5459,U+5464,U+5466,U+5471-5472,U+5476-5477,U+5482,U+5484,U+5494,U+549A-549B,U+549D,U+54A3-54A4,U+54A6-54A7,U+54A9-54AA,U+54AD,U+54B4,U+54BB,U+54BF,U+54C2,U+54CC,U+54CF-54D0,U+54D2-54D5,U+54D9-54DA,U+54DC-54DE,U+54E7,U+54F3,U+54FD,U+54FF,U+550F,U+5511,U+5514,U+551B,U+5520,U+5522-5523,U+5527,U+552A,U+5530,U+5533,U+5537,U+553C,U+553F,U+5541,U+5549,U+5550,U+5555-5556,U+555C,U+5567,U+556C-556D,U+5575-5577,U+557B,U+557E,U+5581,U+5583,U+5588,U+558B,U+558F,U+5591,U+5594,U+5599,U+559F,U+55B1,U+55B5,U+55B9,U+55BD-55BE,U+55C4,U+55C9,U+55CC-55CD,U+55D1-55D2,U+55D4,U+55D6,U+55DD,U+55DF,U+55E4-55E6,U+55E8,U+55EA-55EC,U+55EF,U+55F2-55F3,U+55F5,U+55F7,U+55FE,U+5600-5601,U+5608,U+560C,U+560F,U+561E-561F,U+5623-5624,U+5627,U+562C-562D,U+5639,U+564C-564D,U+5654,U+5657-5659,U+565C,U+5662,U+5664,U+5669,U+566B,U+5671,U+567B-567C,U+5685-5686,U+5693,U+56AF,U+56D4,U+56D7,U+56DD,U+56DF,U+56E1,U+56EB,U+56F5,U+56F9,U+56FF,U+5704,U+5709-570A,U+571C,U+5729-572A,U+572C,U+572E-572F,U+5733,U+5739,U+573B,U+5742,U+574C,U+575C,U+5768-5769,U+576B,U+576D,U+5773,U+5776,U+577B-577C,U+5785-5786,U+578C,U+5793,U+57A0-57A1,U+57A4,U+57A7,U+57A9,U+57AD,U+57B2,U+57B4,U+57B8,U+57CF,U+57D2,U+57D5,U+57D8-57DA,U+57DD,U+57E4,U+57ED,U+57EF,U+57F4,U+57F8,U+57FD,U+5800,U+5807,U+580B,U+580D,U+5819,U+581E,U+5820,U+5844,U+5865,U+586C,U+587E,U+5880-5881,U+5889,U+589A,U+58BC,U+58C5,U+58D1,U+5902,U+5914,U+5919,U+5924-5925,U+592D,U+593C,U+5941-5942,U+5955,U+5958,U+595A,U+5B34,U+5B5B,U+5B6C,U+5C22,U+5C25,U+5C2C,U+5C34,U+5C7A,U+5C88,U+5C8C-5C8D,U+5C90-5C91,U+5C96,U+5C98-5C9A,U+5C9C,U+5CA2-5CA3,U+5CAB-5CAC,U+5CB1,U+5CB5,U+5CB7,U+5CBD,U+5CC1,U+5CC4,U+5CCB,U+5CD2,U+5CE4-5CE5,U+5D02-5D03,U+5D06,U+5D1B,U+5D1E,U+5D24,U+5D26-5D27,U+5D2E,U+5D34,U+5D3D-5D3E,U+5D4A-5D4B,U+5D58,U+5D5B,U+5D5D,U+5D69,U+5D6B-5D6C,U+5D6F,U+5D74,U+5D82,U+5D99,U+5D9D,U+5DB7,U+5DC5,U+5DEF,U+5DFD,U+5E0F,U+5E11,U+5E14,U+5E19,U+5E31,U+5E37,U+5E3B-5E3C,U+5E44,U+5E54,U+5E5B,U+5E5E,U+5E61,U+5E80,U+5E8B,U+5E91,U+5E96,U+5EA0,U+5EA5,U+5EB3,U+5EB5,U+5EB9,U+5EBE,U+5ED1-5ED2,U+5EDB,U+5EE8,U+5EEA,U+5EF4,U+5EFE-5EFF,U+5F01,U+5F08,U+5F0B,U+5F11,U+5F61,U+5F73,U+5F77,U+5F82,U+5F87,U+5F89,U+5F8C,U+5F95,U+5F99,U+5F9C,U+5FA8,U+5FAD,U+5FB5,U+5FBC,U+5FD2,U+61FF,U+624C,U+626A,U+629F,U+62BB,U+62CA,U+62D7,U+62DA,U+62EE,U+62F6,U+6322,U+6339,U+6343,U+634B,U+6369,U+636D,U+6371,U+637A,U+638A,U+638E,U+63AC-63AE,U+63B4,U+63BC,U+63BE,U+63C4,U+63C6,U+63CE,U+63DE,U+63E0,U+63F2,U+63F6,U+63F8,U+63FF,U+640B-640C,U+641B,U+6420-6421,U+6426,U+6441,U+6445,U+6452,U+645E,U+646D,U+647A,U+6484,U+6496,U+6499,U+64B7-64B8,U+64BA,U+64C0,U+64D0,U+64D7,U+64E2,U+64E4,U+6509,U+6525,U+652E,U+6538,U+6BD3,U+6C10,U+6C3D,U+6C46,U+71EE,U+723B,U+72AD,U+72B0,U+72B4,U+72B7-72B8,U+72C1,U+72C3,U+72CD-72CE,U+72D2,U+72E8-72E9,U+72EF,U+72F2-72F4,U+72F7,U+72FA-72FB,U+7301,U+7303,U+730A,U+7313,U+7315,U+7317,U+731D-731E,U+7321-7322,U+7325,U+732C,U+7331,U+7338-7339,U+734D,U+7350,U+7357,U+7360,U+736C,U+736F,U+737E,U+750D,U+7519,U+755A,U+777E,U+77A2,U+77CD,U+7980,U+79BA,U+7C74,U+7F54,U+7FB8,U+8037,U+80E4,U+8114,U+81BA,U+821B,U+8279,U+827D,U+827F,U+8284,U+8288,U+828A,U+828E-828F,U+8291,U+8297-8299,U+829F,U+82A1,U+82A4,U+82A8-82AB,U+82AE,U+82B0,U+82B4,U+82B7-82B8,U+82BE,U+82C1,U+82C4,U+82C8,U+82CA-82CC,U+82CE,U+82D2-82D3,U+82D5,U+82D8,U+82DC,U+82E0-82E1,U+82E3-82E4,U+82F4,U+82F7,U+82FB,U+8306-8309,U+830C,U+830F,U+8311,U+8314-8315,U+8317,U+831A-831C,U+832D,U+832F,U+8331,U+8333-8334,U+833A,U+833C,U+8340,U+8343,U+8347,U+834F,U+8351,U+835B-835C,U+835E-8360,U+8365-8366,U+8368-836A,U+836C-836E,U+8378,U+837B-837D,U+8385,U+8392-8393,U+8398,U+839B-839C,U+839E,U+83A0,U+83A8-83AA,U+83B0,U+83B3-83B4,U+83B6,U+83B8,U+83BA,U+83BC,U+83C0-83C1,U+83C5,U+83D4,U+83D6,U+83D8,U+83DD,U+83DF,U+83E1,U+83E5,U+83EA,U+83F0,U+83F8-83F9,U+83FD,U+8401,U+8403,U+8406,U+840B,U+840F,U+8411,U+8418,U+841C,U+8426,U+8431,U+8438,U+843C,U+8446,U+8451,U+8459-845A,U+845C,U+8469,U+846D,U+8473,U+8476,U+8478,U+847A,U+8487-8489,U+848C,U+848E,U+8497,U+84A1,U+84AF,U+84B4,U+84B9-84BA,U+84BD,U+84BF,U+84C1,U+84CA,U+84CD,U+84D0,U+84D3,U+84E0,U+84E3,U+84E5-84E6,U+84F0,U+84FC,U+84FF,U+850C,U+851F,U+8538-853B,U+8543,U+8548,U+8556,U+8559,U+855E,U+8564,U+8568,U+8572,U+8579-857B,U+8585,U+8587,U+858F,U+859C,U+85A4,U+85A8,U+85AE,U+85B0,U+85B7,U+85B9,U+85C1,U+85D3,U+85DC,U+85FF,U+8605,U+8616,U+8627,U+8629,U+863C,U+8803,U+8862,U+886E,U+88A4,U+88D2,U+8A07,U+8BA0,U+8BA6-8BA7,U+8BAA,U+8BB4-8BB5,U+8BB7,U+8BC2-8BC3,U+8BCB,U+8BCE-8BCF,U+8BD2-8BD4,U+8BD6,U+8BD8-8BD9,U+8BDC,U+8BDF-8BE0,U+8BE4,U+8BE8-8BE9,U+8BEE,U+8BF0,U+8BF3,U+8BF6,U+8BF9,U+8BFC,U+8BFF-8C00,U+8C02,U+8C04,U+8C07,U+8C0C,U+8C0F,U+8C11-8C12,U+8C14-8C16,U+8C18-8C19,U+8C1B,U+8C1D,U+8C1F-8C21,U+8C25,U+8C27,U+8C2A-8C2B,U+8C2E-8C2F,U+8C32-8C33,U+8C35-8C36,U+8C73,U+8D53,U+8D5C-8D5D,U+8F94,U+9097,U+9099,U+909B,U+909D,U+90A1,U+90AC,U+90B0,U+90B3-90B4,U+90B6,U+90B8,U+90BA,U+90BE,U+90C4-90C5,U+90C7,U+90CF-90D0,U+90D3,U+90D7,U+90DB-90DC,U+90E2,U+90E6,U+90EB,U+90EF,U+90FE,U+9104,U+911E,U+9122-9123,U+912F,U+9131,U+9139,U+9143,U+9146,U+961D,U+9621-9622,U+962A,U+9631,U+963C-963D,U+9642,U+9649,U+9654,U+965F,U+9667,U+966C,U+9672,U+9674,U+9688,U+968D,U+9697,U+96B0,U+9765,U+98E7,U+9963,U+9967-996C,U+9974,U+9977,U+997D,U+9980,U+9984,U+9987,U+998A,U+998D,U+9990-9991,U+9993-9995,U+9997-9998,U+99A8,U+9B2F,U+9B32,U+9EC9,U+9F10,U+9F17,U+9F19,U+9FA0"
818
+ }),
819
+ Object.freeze({
820
+ family: "Aha Hand",
821
+ file: "wenkai-gb-5-d0359bbc.woff2",
822
+ bytes: 278908,
823
+ unicodeRange: "U+4E2C,U+5216,U+54AB,U+584D,U+5981,U+5983,U+598D,U+5997,U+599E,U+59A3-59A4,U+59A9-59AB,U+59AF,U+59B2,U+59BE,U+59CA,U+59D2,U+59D7-59D8,U+59DD,U+59E3,U+59F9,U+5A05-5A06,U+5A08-5A09,U+5A0C,U+5A11,U+5A13,U+5A23,U+5A32,U+5A34,U+5A3C,U+5A40,U+5A4A,U+5A55,U+5A62,U+5A67,U+5A75,U+5A77,U+5A7A,U+5A9B,U+5AAA,U+5AB2,U+5AB5,U+5AB8,U+5ABE,U+5AD2,U+5AD4,U+5AD6,U+5AD8,U+5ADC,U+5AE0,U+5AE3,U+5AE6,U+5AEB,U+5AF1,U+5B09,U+5B16-5B17,U+5B32,U+5B37,U+5B40,U+5B51,U+5B53,U+5B5A,U+5B62,U+5B65,U+5B71,U+5B73,U+5B80,U+5B84,U+5B93,U+5B95,U+5BA5,U+5BB8,U+5BE4,U+5BEE,U+5BF0,U+5C15,U+5C1C,U+5C3B,U+5C50,U+5C59,U+5C63,U+5C66,U+5C6E,U+5DDB,U+5E7A,U+5F29-5F2A,U+5F2D,U+5F3C,U+5F40,U+5F50,U+5F56-5F58,U+5FC4,U+5FC9,U+5FCF-5FD1,U+5FD6,U+5FDD,U+5FE1,U+5FE4,U+5FEA,U+5FED-5FEE,U+5FF8,U+5FFE,U+6003-6006,U+600A,U+600D,U+600F,U+6019,U+601B,U+6021,U+6026,U+6029,U+602B,U+6035,U+603C,U+603F,U+6041-6042,U+6059-605A,U+605D,U+6063,U+6067,U+606A,U+6078-607B,U+607D,U+6083,U+608C,U+6092,U+6096,U+609A-609B,U+609D,U+60AB,U+60AD,U+60B1,U+60B4,U+60BB,U+60C6,U+60D8,U+60DA,U+60DD,U+60EC,U+60F4,U+6100,U+6106,U+610D-610E,U+6115,U+6120,U+6123,U+6126,U+612B,U+614A,U+615D,U+6175,U+6194,U+619D,U+61A7,U+61A9,U+61AC,U+61B7,U+61CB,U+61D1,U+61D4,U+61F5,U+6206,U+620B,U+6215,U+6217,U+621B,U+621F,U+6221-6222,U+6224-6225,U+622C,U+623D-623E,U+6243,U+6248-6249,U+6308,U+6332,U+63B0,U+6434,U+643F,U+64D8,U+6534-6535,U+6555,U+656B,U+6590,U+6593,U+65AB,U+65BC,U+65C3-65C4,U+65C6,U+65CC,U+65CE,U+65D2,U+65D6,U+65EE-65F0,U+6600,U+6603,U+660A,U+6615,U+6619,U+661D,U+6631,U+6634-6636,U+6641,U+664F,U+6654,U+6656-6657,U+665F,U+6661,U+6677,U+6684,U+668C,U+669D,U+66A7,U+66B9,U+66BE,U+66DB-66DC,U+66E6,U+66E9,U+66F7,U+670A,U+6710,U+6715,U+6726,U+6748,U+674C,U+6753,U+675E,U+6769-676A,U+6772-6773,U+6775,U+6777,U+677C,U+6787,U+678B,U+6798,U+679E,U+67A5,U+67A7-67A8,U+67AD,U+67B0,U+67B3,U+67B5,U+67B8,U+67C1,U+67C3,U+67D8-67DA,U+67DD,U+67E2,U+67E9,U+67F0,U+67FD,U+6800,U+6809-680A,U+680C,U+680E,U+681D,U+6829,U+6832-6833,U+683E,U+6840-6841,U+6844,U+6849-684A,U+684E,U+6855,U+6860-6862,U+6864,U+6866-6867,U+686B,U+6874,U+6877,U+6883,U+688F,U+6893,U+68B5,U+68C2,U+68E3,U+68F0,U+68F9,U+68FC,U+6901,U+690B,U+6910,U+691F-6920,U+6924,U+6934,U+6939,U+6942,U+6957,U+695D,U+6960,U+6963,U+6966,U+696B,U+696E,U+6971,U+6978-6979,U+6980,U+6984,U+6987-6989,U+698D,U+6995,U+6998,U+699B,U+69A7,U+69AB,U+69AD,U+69B1,U+69BB,U+69C1,U+69CA,U+69CC,U+69CE,U+69D4,U+69DF-69E0,U+69ED,U+69F2,U+69FF,U+6A17-6A18,U+6A28,U+6A2F,U+6A35,U+6A3D-6A3E,U+6A44,U+6A50,U+6A58,U+6A5B,U+6A65,U+6A79,U+6A7C,U+6A8E,U+6A90-6A91,U+6A97,U+6AA0,U+6AA9,U+6AAB,U+6B24,U+6B37,U+6B39,U+6B43,U+6B46,U+6B59,U+6B81-6B82,U+6B84,U+6B87,U+6B8D,U+6B92-6B93,U+6B9A-6B9B,U+6BA1,U+6BAA,U+6BB3,U+6BC2,U+6BEA,U+6BF3,U+6BF5,U+6BF9,U+6BFD,U+6C05-6C07,U+6C0D,U+6C15,U+6C18-6C1A,U+6C21,U+6C24,U+6C29-6C2A,U+6C32,U+6C35,U+6C4A,U+6C54,U+6C5C,U+6C68-6C69,U+6C74,U+6C76,U+6C85-6C86,U+6C8C,U+6C90,U+6C93-6C94,U+6CA3,U+6CA9,U+6CAD,U+6CB1-6CB2,U+6CD0,U+6CD3-6CD4,U+6CD6-6CD7,U+6CE0,U+6CEB,U+6CEE-6CEF,U+6CF1,U+6CF6-6CF8,U+6CFA,U+6CFE,U+6D04,U+6D07,U+6D0C,U+6D0E,U+6D19-6D1A,U+6D27,U+6D2B,U+6D2E,U+6D33,U+6D35,U+6D39,U+6D43,U+6D48,U+6D4D,U+6D4F,U+6D52,U+6D54,U+6D5C,U+6D5E,U+6D60,U+6D63,U+6D6F,U+6D7C,U+6D91,U+6D93-6D94,U+6D9E,U+6DA0,U+6DAB,U+6DAE,U+6DBF,U+6DC5,U+6DC7,U+6DD9,U+6DDD-6DDE,U+6DE0,U+6DE6,U+6DFC,U+6E0C,U+6E0E,U+6E11,U+6E16,U+6E1A,U+6E25,U+6E2B,U+6E32,U+6E44,U+6E4E,U+6E53-6E54,U+6E5F,U+6E6B,U+6E6E,U+6E86,U+6E8F,U+6E98,U+6E9F,U+6EA5,U+6EA7,U+6EB1-6EB2,U+6EB4,U+6EB7,U+6EBB,U+6EBD,U+6EC2,U+6ECF,U+6ED5,U+6ED7,U+6EDF-6EE0,U+6EE2,U+6EF9,U+6F09,U+6F15,U+6F24,U+6F29-6F2A,U+6F2D,U+6F2F,U+6F36,U+6F46-6F47,U+6F4B,U+6F62,U+6F72,U+6F74,U+6F78,U+6F7A,U+6F7C,U+6F89,U+6F8C-6F8D,U+6FA7,U+6FB6,U+6FB9,U+6FC2,U+6FC9,U+6FD1,U+6FDE,U+6FE0-6FE1,U+6FEE-6FEF,U+701A-701B,U+7023,U+7035,U+7039,U+704F,U+705E,U+706C,U+7080,U+7085,U+7096,U+709C-709D,U+70AB,U+70B1,U+70B7,U+70BB,U+70C0,U+70CA,U+70E8,U+7110,U+7113,U+7116,U+7118,U+712F,U+7131,U+7145,U+714A,U+715C,U+7166,U+7168,U+7172-7173,U+7178,U+717A,U+7198,U+71A0,U+71A8,U+71B3,U+71B5,U+71B9,U+71D4,U+71E0,U+71E7,U+71F9,U+721D,U+7228,U+7230,U+723F,U+724D,U+7252,U+7256,U+725D,U+7266,U+726E-726F,U+727E-727F,U+7284,U+728B,U+728D,U+728F,U+7292,U+729F,U+7337,U+7352,U+738E,U+7391,U+739F,U+73A2,U+73AE,U+73B3,U+73B7,U+73BA,U+73C0,U+73C2,U+73C8-73C9,U+73CF,U+73D1,U+73D9,U+73DE,U+73E5,U+73E7,U+73E9,U+73F2,U+740A,U+740F,U+741A-741B,U+7425-7426,U+7428,U+742A,U+742C,U+742E,U+7430,U+7441,U+7455,U+7457,U+7459,U+745B-745C,U+746D,U+7477,U+747E,U+7480-7481,U+7487,U+748B,U+748E,U+7490,U+749C,U+749E,U+74A7-74A9,U+74BA,U+74D2,U+74EF,U+74F4,U+74FF,U+750F,U+7511,U+7513,U+752F,U+753E,U+757F,U+77F6,U+77F8,U+7800,U+7809,U+7811,U+7817-7818,U+781C-781D,U+782D,U+793B,U+7940,U+7946,U+7949,U+7953,U+7957,U+795A-795C,U+7960,U+7962,U+7967,U+796F,U+797A,U+7985,U+798A,U+799A,U+79A7,U+79B3,U+7E9F,U+7EA1,U+7EA3,U+7EA5,U+7EA8-7EA9,U+7EAD,U+7EB0,U+7EBE,U+7EC0-7EC2,U+7EC9,U+7ECB-7ECC,U+7ED0,U+7ED4,U+7ED7,U+7EDB,U+7EE0-7EE1,U+7EE8,U+7EEB,U+7EEE-7EEF,U+7EF1-7EF2,U+7EF6,U+7EFA-7EFB,U+7EFE,U+7F01-7F03,U+7F07-7F08,U+7F0B-7F0D,U+7F0F,U+7F11-7F12,U+7F17,U+7F19,U+7F1B-7F1C,U+7F1F,U+7F21-7F27,U+7F2A-7F2D,U+7F2F-7F33,U+7F35,U+7FBC,U+8004,U+8006,U+807F-8080,U+8093,U+809C,U+809F,U+80AB,U+80AD,U+80B1,U+80B4,U+80B7,U+80BC-80BD,U+80C2,U+80C4,U+80CD,U+80D7,U+80D9,U+80DB,U+80DD,U+80E7-80ED,U+80F1-80F2,U+80F4,U+80FC,U+810D-810E,U+8112,U+8118,U+811E,U+812C,U+8132,U+8136,U+8148,U+814C,U+8153,U+8159-815A,U+8160,U+8167,U+8169,U+816D,U+8171,U+8174,U+817C-817D,U+8182,U+8188,U+8191,U+81A3,U+81A6,U+81AA,U+81BB,U+81C1,U+81CA,U+81CC,U+81E7,U+8274,U+8662,U+8930,U+89C7,U+89CA-89CC,U+89CE-89D1,U+89F3,U+8B07,U+8C5A,U+8D32-8D33,U+8D36,U+8D3B,U+8D3D,U+8D40,U+8D45-8D49,U+8D4D,U+8D55,U+8D59,U+8E47,U+8ECE,U+8F6B,U+8F6D,U+8F71-8F73,U+8F75-8F7A,U+8F7C,U+8F7E,U+8F81-8F82,U+8F84,U+8F87,U+8F8B,U+8F8D-8F8F,U+8F98,U+8F9A,U+8FB6,U+8FD3,U+8FD5,U+8FE4-8FE6,U+8FE8-8FE9,U+8FEE,U+8FF3,U+9004-9005,U+900B,U+900D,U+9011,U+9016,U+9021,U+9026,U+902D,U+902F,U+9035-9036,U+9044,U+9050-9052,U+9058,U+905B,U+9062,U+9068,U+9074,U+907D,U+9082-9083,U+9088,U+908B,U+9095,U+95E9,U+95EB,U+95F1,U+95F3,U+95F5-95F6,U+95FC,U+95FE,U+9603-9604,U+9606,U+9608,U+960A-960D,U+960F,U+9612,U+9615-9617,U+9619-961A,U+96B3,U+97EA-97EC,U+987C,U+98D1-98D3,U+98D5,U+98D9-98DA,U+9A75,U+9A77-9A78,U+9A7A,U+9A7D,U+9A7F-9A81,U+9A85,U+9A88,U+9A8A,U+9A90,U+9A92-9A93,U+9A96,U+9A98,U+9A9B-9AA0,U+9AA2-9AA3,U+9AA5,U+9AA7,U+9B3B,U+9F51"
824
+ }),
825
+ Object.freeze({
826
+ family: "Aha Hand",
827
+ file: "wenkai-gb-6-a238b41c.woff2",
828
+ bytes: 307604,
829
+ unicodeRange: "U+5D47,U+6549,U+659B,U+66A8,U+74DE,U+74E0,U+752C,U+753A,U+7540,U+7548,U+754B,U+754E,U+755B,U+7572,U+7579,U+7583,U+758B,U+7592,U+7594,U+7596,U+759D,U+75A0,U+75A3,U+75AC,U+75B0-75B1,U+75B3-75B4,U+75B8,U+75C2-75C4,U+75CD,U+75D6,U+75E3-75E4,U+75E6-75E8,U+75EB,U+75F1,U+75FC,U+75FF-7600,U+7603,U+7605,U+760A,U+760C,U+7610,U+7615,U+7617-7619,U+761B,U+7620,U+7622,U+7625,U+762D,U+7630,U+7633,U+7635,U+763C,U+763E-7640,U+7643,U+764D,U+7654,U+7656,U+765C,U+765E,U+766B,U+766F,U+7688,U+768E,U+7693,U+7699,U+76A4,U+76B2,U+76B4,U+76CD,U+76E5,U+76F1,U+76F9,U+7704,U+7707-7708,U+770D,U+7719-771A,U+7722,U+7726,U+772D,U+7735,U+7738,U+7743,U+7747,U+7750-7751,U+775A,U+7762,U+7765,U+7768,U+777D,U+777F-7780,U+778C-778D,U+7791,U+779F-77A0,U+77B0,U+77B5,U+77BD,U+77BF,U+77DC,U+77E7,U+77EC,U+781F,U+7823,U+7825-7826,U+7829,U+782C,U+7839-783C,U+7847,U+784C,U+784E,U+7850,U+7856-7857,U+786A,U+786D,U+7887,U+7893,U+789A-789C,U+78A1,U+78A3,U+78A5,U+78B2,U+78B9,U+78C9,U+78D4,U+78D9,U+78EC,U+78F2,U+78F4,U+7905,U+7913,U+791E,U+7924,U+7934,U+79D5,U+79E3,U+79EB,U+79ED,U+7A02-7A03,U+7A06,U+7A14,U+7A1E,U+7A23,U+7A37,U+7A39,U+7A51,U+7A70,U+7A78-7A79,U+7A80,U+7A86,U+7A88,U+7A95,U+7AA0,U+7AA6,U+7AA8,U+7AAC-7AAD,U+7AB3,U+7AE6,U+7AFA,U+7AFD,U+7B03-7B04,U+7B08,U+7B0A,U+7B0F,U+7B15,U+7B19,U+7B1E,U+7B20,U+7B24-7B25,U+7B2A-7B2B,U+7B2E,U+7B31,U+7B33,U+7B38,U+7B3E,U+7B45,U+7B47,U+7B4C,U+7B58,U+7B5A,U+7B5D,U+7B60,U+7B62,U+7B6E,U+7B71-7B72,U+7B75,U+7B7B,U+7B85,U+7B90,U+7B9C-7B9D,U+7BA2,U+7BA6-7BA8,U+7BAA-7BAC,U+7BB4,U+7BB8,U+7BC1,U+7BCC,U+7BD1,U+7BDA,U+7BDD,U+7BE5-7BE6,U+7BEA,U+7BFC,U+7BFE,U+7C0B-7C0C,U+7C0F,U+7C16,U+7C1F,U+7C26,U+7C2A,U+7C38,U+7C40-7C41,U+7C7C,U+7C91,U+7C9C-7C9E,U+7CA2,U+7CB2,U+7CBC-7CBD,U+7CC1,U+7CC5,U+7CC7-7CC8,U+7CCC-7CCD,U+7CD7,U+7CE8,U+7CF8,U+7D77,U+7DA6,U+7DAE,U+7E3B,U+7E47,U+7E9B,U+7F36,U+7F42,U+7F44-7F45,U+7F58,U+7F5F,U+7F61,U+7F68,U+7F71,U+7F74,U+7F79,U+7F7E,U+7F81,U+7F9D,U+7F9F,U+7FA7,U+7FAF-7FB0,U+7FB2,U+7FBF,U+7FCA,U+7FCE,U+7FD5,U+7FE1,U+7FE5-7FE6,U+7FE9,U+7FEE,U+7FF3,U+800B,U+8012,U+8014,U+8016,U+801C,U+8020,U+8022,U+8025-8029,U+8031,U+8035,U+8043,U+8046,U+804D,U+8052,U+8069,U+8071,U+80E5,U+81EC,U+81FE,U+8201-8202,U+8204,U+8210,U+8221-8223,U+8228,U+822B,U+822D,U+822F,U+8233-8234,U+8238,U+823B,U+823E,U+8244,U+8249,U+824B,U+824F,U+825A,U+825F,U+8268,U+826E,U+864D,U+8654,U+866C,U+866E,U+867A-867C,U+867F,U+868B,U+868D,U+8693,U+869D,U+86A3,U+86A7-86AA,U+86AC,U+86AF-86B1,U+86B4-86B6,U+86BA,U+86C4,U+86C9,U+86CE-86D1,U+86D8,U+86DE-86DF,U+86E9,U+86ED,U+86F1-86F4,U+86F8,U+8703,U+8707-870A,U+870D,U+8713,U+871A,U+871E,U+8722-8723,U+8725,U+8729,U+872E,U+8731,U+8734,U+8737,U+873B,U+873E-873F,U+8748,U+874C,U+8753,U+8759,U+8760,U+8763-8765,U+876E,U+8770,U+877B-877E,U+8782-8783,U+8785,U+8788,U+878B,U+8793,U+8797,U+87A8,U+87AB-87AD,U+87AF,U+87B3,U+87B5,U+87BD,U+87C0,U+87C6,U+87CA-87CB,U+87D1-87D3,U+87DB,U+87E0,U+87E5,U+87EA,U+87EE,U+87FE,U+880A,U+8813,U+8816,U+881B,U+8821,U+8832,U+8839,U+883C,U+8844,U+8864,U+8869,U+8872,U+887D-887F,U+8882,U+8885,U+8888,U+88A2,U+88B7,U+88BC,U+88C6,U+88C9,U+88CE,U+88D8,U+88DF,U+88E2-88E3,U+88E5,U+88E8,U+88F0-88F1,U+88FC,U+88FE,U+890A,U+8913,U+8919-891B,U+8921,U+892B,U+8934,U+8936,U+8941,U+895E,U+8966,U+897B,U+8983,U+89D6,U+89DA,U+89DC,U+89DE,U+89E5,U+89EB,U+89EF,U+8A3E,U+8A48,U+8B26,U+8C47,U+8C49,U+8C55,U+8C78,U+8C82,U+8C85,U+8C8A,U+8C94,U+8C98,U+8D67,U+8D6D,U+8D73,U+8D84,U+8D91,U+8D94,U+8DB1,U+8DB5,U+8DB8,U+8DBA,U+8DBC,U+8DBF,U+8DC4,U+8DC6,U+8DCE-8DCF,U+8DD6-8DD7,U+8DDA-8DDB,U+8DDE,U+8DE3-8DE4,U+8DEB-8DEC,U+8DF7-8DF9,U+8DFB,U+8DFD,U+8E05,U+8E09,U+8E14,U+8E1D,U+8E1F,U+8E23,U+8E2C,U+8E2E-8E2F,U+8E31,U+8E35,U+8E39-8E3A,U+8E3D,U+8E40-8E42,U+8E49-8E4A,U+8E51-8E52,U+8E59,U+8E69,U+8E6F-8E70,U+8E74,U+8E76,U+8E7C,U+8E85,U+8E8F-8E90,U+8E94,U+8E9C,U+8E9E,U+914A,U+914E-9150,U+9161-9162,U+9164,U+9169,U+916F-9170,U+9172,U+9174,U+9179,U+917D-917E,U+9185,U+918C-918D,U+9190-9191,U+91A2-91A3,U+91AA,U+91AD-91AF,U+91B4-91B5,U+91BA,U+928E,U+92AE,U+92C8,U+933E,U+936A,U+938F,U+93CA,U+93D6,U+943E,U+946B,U+9485-9487,U+948A-948D,U+948F-9490,U+9494-9495,U+9497,U+949A-949C,U+94A3-94A4,U+94AA-94AD,U+94AF-94B0,U+94B2,U+94B4,U+94B6-94BA,U+94BC-94BD,U+94BF,U+94C4,U+94C8-94CE,U+94D0-94D2,U+94D5-94D9,U+94DB,U+94DE-94E0,U+94E2,U+94E4-94E5,U+94E7-94EB,U+94EE-94EF,U+94F3-94F5,U+94F7,U+94F9,U+94FC-94FD,U+94FF,U+9502-9503,U+9506-9507,U+9509-950A,U+950D-950F,U+9512-9516,U+9518,U+951B,U+951D-951F,U+9522,U+9529-952C,U+9531-9532,U+9534-9538,U+953A,U+953C,U+953E-953F,U+9542,U+9544-9546,U+9549,U+954C,U+954E-954F,U+9552-9554,U+9556-9559,U+955B,U+955D-955F,U+9561-9562,U+9564-956C,U+956F,U+9571-9573,U+96B9,U+96BC-96BD,U+96C9,U+96CE,U+96D2,U+96E0,U+96E9,U+96EF,U+96F3,U+9701,U+9706,U+9708,U+970E-970F,U+972A,U+972D,U+9730,U+973E,U+9753,U+977C,U+9785,U+9791-9792,U+9794,U+97A3,U+97AB,U+97AF,U+97B2,U+97B4,U+9878,U+9880,U+9883,U+9889,U+988C-988D,U+988F,U+9894,U+989A-989B,U+989E-989F,U+98A1-98A2,U+98A5-98A6,U+98E8,U+990D,U+992E,U+9954-9955,U+99A5,U+9AB0-9AB1,U+9AB6-9AB7,U+9ABA,U+9ABC,U+9AC0-9AC2,U+9AC5,U+9ACB-9ACC,U+9AD1,U+9ADF,U+9AE1,U+9AE6,U+9AEB,U+9AED,U+9AEF,U+9AF9,U+9AFB,U+9B08,U+9B0F,U+9B13,U+9B1F,U+9B23,U+9B43,U+9B45,U+9B47-9B49,U+9B4D,U+9B51,U+9C7F,U+9C82,U+9C85-9C88,U+9C8B,U+9C8E,U+9C90-9C92,U+9C94-9C95,U+9C9A-9C9B,U+9C9E-9CA3,U+9CA5-9CA9,U+9CAB,U+9CAD-9CAE,U+9CB0-9CB7,U+9CBA-9CBD,U+9CC4-9CC7,U+9CCA-9CD0,U+9CD3-9CD5,U+9CD7-9CD9,U+9CDC-9CDD,U+9CDF,U+9CE2,U+9E20,U+9E22,U+9E28-9E2C,U+9E31-9E32,U+9E36-9E3A,U+9E3E,U+9E41-9E42,U+9E44,U+9E46-9E49,U+9E4B-9E4C,U+9E4E,U+9E51,U+9E55,U+9E57-9E58,U+9E5A-9E5C,U+9E5E,U+9E63,U+9E66-9E6D,U+9E71,U+9E73,U+9E7E,U+9E82,U+9E87-9E88,U+9E8B,U+9E92,U+9E9D,U+9E9F,U+9EB4,U+9EB8,U+9EBD-9EBE,U+9ECF,U+9EDB-9EDD,U+9EDF-9EE0,U+9EE2,U+9EE5,U+9EE7,U+9EE9-9EEA,U+9EEF,U+9EF9,U+9EFB-9EFC,U+9EFE,U+9F0B,U+9F0D,U+9F22,U+9F2C,U+9F2F,U+9F37,U+9F39,U+9F3D-9F3E,U+9F44,U+9F80,U+9F83,U+9F85-9F8A,U+9F8C,U+9F9B"
830
+ })
831
+ ]);
832
+ //#endregion
833
+ //#region src/host/web-fetch.js
834
+ const LOCAL_ADDRESS_MESSAGE = "Local network addresses are not supported";
835
+ var AddressError = class extends Error {
836
+ constructor(message = LOCAL_ADDRESS_MESSAGE) {
837
+ super(message);
838
+ this.name = "AddressError";
839
+ this.code = "bad-url";
840
+ }
841
+ };
842
+ const V4_BLOCKED = [
843
+ ["0.0.0.0", 8],
844
+ ["10.0.0.0", 8],
845
+ ["100.64.0.0", 10],
846
+ ["127.0.0.0", 8],
847
+ ["169.254.0.0", 16],
848
+ ["172.16.0.0", 12],
849
+ ["192.0.0.0", 24],
850
+ ["192.0.2.0", 24],
851
+ ["192.88.99.0", 24],
852
+ ["192.168.0.0", 16],
853
+ ["198.18.0.0", 15],
854
+ ["198.51.100.0", 24],
855
+ ["203.0.113.0", 24],
856
+ ["224.0.0.0", 4],
857
+ ["240.0.0.0", 4]
858
+ ].map(([base, bits]) => [v4Number(base), bits]);
859
+ const V6_BLOCKED = [
860
+ ["fc00::", 7],
861
+ ["fe80::", 10],
862
+ ["fec0::", 10],
863
+ ["ff00::", 8],
864
+ ["100::", 64],
865
+ ["64:ff9b:1::", 48],
866
+ ["2001::", 32],
867
+ ["2001:2::", 48],
868
+ ["2001:10::", 28],
869
+ ["2001:20::", 28],
870
+ ["2001:db8::", 32]
871
+ ].map(([base, bits]) => [v6Bytes(base), bits]);
872
+ function v4Number(address) {
873
+ const parts = address.split(".").map(Number);
874
+ return (parts[0] << 24 >>> 0) + (parts[1] << 16) + (parts[2] << 8) + parts[3] >>> 0;
875
+ }
876
+ function v4Public(address) {
877
+ const value = v4Number(address);
878
+ return !V4_BLOCKED.some(([base, bits]) => bits === 0 ? true : value >>> 32 - bits === base >>> 32 - bits);
879
+ }
880
+ function v6Bytes(address) {
881
+ let text = String(address).toLowerCase();
882
+ const zone = text.indexOf("%");
883
+ if (zone >= 0) text = text.slice(0, zone);
884
+ const groups = [];
885
+ const dotted = text.match(/(\d+\.\d+\.\d+\.\d+)$/u);
886
+ if (dotted) {
887
+ const value = v4Number(dotted[1]);
888
+ text = `${text.slice(0, -dotted[1].length)}${(value >>> 16).toString(16)}:${(value & 65535).toString(16)}`;
889
+ }
890
+ const [head, tail] = text.includes("::") ? text.split("::") : [text, null];
891
+ const left = head ? head.split(":") : [];
892
+ const right = tail ? tail.split(":") : [];
893
+ const fill = tail === null ? 0 : 8 - left.length - right.length;
894
+ for (const group of [
895
+ ...left,
896
+ ...Array(Math.max(0, fill)).fill("0"),
897
+ ...right
898
+ ]) groups.push(Number.parseInt(group || "0", 16));
899
+ const bytes = /* @__PURE__ */ new Uint8Array(16);
900
+ groups.slice(0, 8).forEach((group, index) => {
901
+ bytes[index * 2] = group >> 8 & 255;
902
+ bytes[index * 2 + 1] = group & 255;
903
+ });
904
+ return bytes;
905
+ }
906
+ function prefixMatches(bytes, base, bits) {
907
+ for (let bit = 0; bit < bits; bit += 8) {
908
+ const mask = 255 << 8 - Math.min(8, bits - bit) & 255;
909
+ if ((bytes[bit / 8] & mask) !== (base[bit / 8] & mask)) return false;
910
+ }
911
+ return true;
912
+ }
913
+ const embeddedV4 = (bytes, at) => `${bytes[at]}.${bytes[at + 1]}.${bytes[at + 2]}.${bytes[at + 3]}`;
914
+ function v6Public(address) {
915
+ const bytes = v6Bytes(address);
916
+ const zeros = (count) => bytes.subarray(0, count).every((byte) => byte === 0);
917
+ if (zeros(12) || zeros(10) && bytes[10] === 255 && bytes[11] === 255) return v4Public(embeddedV4(bytes, 12));
918
+ if (prefixMatches(bytes, v6Bytes("64:ff9b::"), 96)) return v4Public(embeddedV4(bytes, 12));
919
+ if (bytes[0] === 32 && bytes[1] === 2) return v4Public(embeddedV4(bytes, 2));
920
+ return !V6_BLOCKED.some(([base, bits]) => prefixMatches(bytes, base, bits));
921
+ }
922
+ function isPublicAddress(address) {
923
+ const value = String(address ?? "").replace(/^\[|\]$/gu, "");
924
+ const family = isIP(value.split("%")[0]);
925
+ if (family === 4) return v4Public(value);
926
+ if (family === 6) return v6Public(value);
927
+ return false;
928
+ }
929
+ const LOCAL_NAME = /(^|\.)(localhost|local|internal|home\.arpa)$/u;
930
+ function assertPublicHostname(hostname) {
931
+ const host = String(hostname ?? "").replace(/^\[|\]$/gu, "").replace(/\.+$/u, "").toLowerCase();
932
+ if (!host) throw new AddressError("This is not a valid web address");
933
+ if (isIP(host)) {
934
+ if (!isPublicAddress(host)) throw new AddressError();
935
+ return true;
936
+ }
937
+ if (LOCAL_NAME.test(host) || !host.includes(".")) throw new AddressError();
938
+ return false;
939
+ }
940
+ function createGuardedLookup({ resolve = lookup, allowed = isPublicAddress } = {}) {
941
+ return function guardedLookup(hostname, options, callback) {
942
+ const done = typeof options === "function" ? options : callback;
943
+ const wanted = typeof options === "object" && options ? options : {};
944
+ resolve(hostname, { all: true }, (error, addresses) => {
945
+ if (error) return done(error);
946
+ const list = (Array.isArray(addresses) ? addresses : []).map((entry) => typeof entry === "string" ? {
947
+ address: entry,
948
+ family: isIP(entry)
949
+ } : entry).filter((entry) => entry?.address);
950
+ if (!list.length) return done(Object.assign(/* @__PURE__ */ new Error(`No address for ${hostname}`), { code: "ENOTFOUND" }));
951
+ if (list.some((entry) => !allowed(entry.address))) return done(new AddressError());
952
+ const family = wanted.family === 4 || wanted.family === 6 ? wanted.family : 0;
953
+ const usable = family ? list.filter((entry) => entry.family === family) : list;
954
+ const chosen = usable.length ? usable : list;
955
+ if (wanted.all) return done(null, chosen.map((entry) => ({
956
+ address: entry.address,
957
+ family: entry.family
958
+ })));
959
+ return done(null, chosen[0].address, chosen[0].family);
960
+ });
961
+ };
962
+ }
963
+ const EMPTY_BODY_STATUS = /* @__PURE__ */ new Set([
964
+ 101,
965
+ 204,
966
+ 205,
967
+ 304
968
+ ]);
969
+ function decoded(response) {
970
+ const encoding = String(response.headers["content-encoding"] ?? "").trim().toLowerCase();
971
+ const decoder = encoding === "gzip" || encoding === "x-gzip" ? zlib.createGunzip() : encoding === "deflate" ? zlib.createInflate() : encoding === "br" ? zlib.createBrotliDecompress() : null;
972
+ if (!decoder) return response;
973
+ return pipeline(response, decoder, () => void 0);
974
+ }
975
+ function toHeaders(raw) {
976
+ const headers = new Headers();
977
+ for (const [name, value] of Object.entries(raw ?? {})) {
978
+ if (name === "content-encoding" || name === "content-length" || value === void 0) continue;
979
+ for (const entry of Array.isArray(value) ? value : [value]) try {
980
+ headers.append(name, String(entry));
981
+ } catch {}
982
+ }
983
+ return headers;
984
+ }
985
+ function createGuardedFetch({ resolve, allowed = isPublicAddress, maxBytes = 64 * 1024 * 1024 } = {}) {
986
+ const lookup = createGuardedLookup({
987
+ resolve,
988
+ allowed
989
+ });
990
+ return function guardedFetch(input, { headers = {}, signal } = {}) {
991
+ return new Promise((fulfil, reject) => {
992
+ let target;
993
+ try {
994
+ target = input instanceof URL ? input : new URL(String(input));
995
+ if (target.protocol !== "http:" && target.protocol !== "https:") throw new AddressError("Only http and https addresses are supported");
996
+ const literal = target.hostname.replace(/^\[|\]$/gu, "");
997
+ if (isIP(literal) && !allowed(literal)) throw new AddressError();
998
+ } catch (error) {
999
+ reject(error);
1000
+ return;
1001
+ }
1002
+ const transport = target.protocol === "https:" ? https : http;
1003
+ let settled = false;
1004
+ const fail = (error) => {
1005
+ if (settled) return;
1006
+ settled = true;
1007
+ reject(error);
1008
+ };
1009
+ const request = transport.request(target, {
1010
+ method: "GET",
1011
+ agent: false,
1012
+ lookup,
1013
+ signal,
1014
+ headers: {
1015
+ ...headers,
1016
+ "accept-encoding": "gzip, deflate, br"
1017
+ }
1018
+ }, (response) => {
1019
+ const status = response.statusCode ?? 0;
1020
+ if (status < 200 || status > 599) {
1021
+ response.resume();
1022
+ fail(Object.assign(/* @__PURE__ */ new Error(`Unexpected status ${status}`), { code: "EBADSTATUS" }));
1023
+ return;
1024
+ }
1025
+ if (status >= 300 && status < 400 || EMPTY_BODY_STATUS.has(status)) {
1026
+ response.resume();
1027
+ settled = true;
1028
+ fulfil(new Response(null, {
1029
+ status,
1030
+ headers: toHeaders(response.headers)
1031
+ }));
1032
+ return;
1033
+ }
1034
+ const body = decoded(response);
1035
+ const parts = [];
1036
+ let size = 0;
1037
+ body.on("data", (chunk) => {
1038
+ if (settled) return;
1039
+ size += chunk.length;
1040
+ if (size > maxBytes) {
1041
+ request.destroy();
1042
+ body.destroy?.();
1043
+ fail(Object.assign(/* @__PURE__ */ new Error("Response too large"), { code: "ETOOLARGE" }));
1044
+ return;
1045
+ }
1046
+ parts.push(chunk);
1047
+ });
1048
+ body.on("error", fail);
1049
+ body.on("end", () => {
1050
+ if (settled) return;
1051
+ settled = true;
1052
+ fulfil(new Response(Buffer.concat(parts), {
1053
+ status,
1054
+ headers: toHeaders(response.headers)
1055
+ }));
1056
+ });
1057
+ });
1058
+ request.on("error", fail);
1059
+ request.end();
1060
+ });
1061
+ };
1062
+ }
1063
+ createGuardedFetch();
1064
+ //#endregion
666
1065
  //#region src/host/materials.js
667
1066
  const MAX_UPLOAD_BYTES = 60 * 1024 * 1024;
668
1067
  const MAX_TEXT_CHARS = 2e6;
669
1068
  const CHUNK_CHARS = 1600;
670
1069
  const FETCH_TIMEOUT_MS = 2e4;
671
1070
  const MAX_PAGE_BYTES = 6 * 1024 * 1024;
1071
+ const pageFetch = createGuardedFetch({ maxBytes: MAX_UPLOAD_BYTES });
672
1072
  var MaterialError = class extends Error {
673
1073
  constructor(code, message) {
674
1074
  super(message);
@@ -984,7 +1384,6 @@ async function ingestFile({ name, bytes, signal }) {
984
1384
  const result = extractPlain(bytes, kind);
985
1385
  return finish(kind, result.title || baseTitle, result.chunks);
986
1386
  }
987
- const PRIVATE_HOST = /^(localhost|.*\.local|127\.\d+\.\d+\.\d+|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+|169\.254\.\d+\.\d+|0\.0\.0\.0|\[?::1\]?|\[?f[cd][0-9a-f]{2}:.*)$/iu;
988
1387
  function checkPublicUrl(value) {
989
1388
  let url;
990
1389
  try {
@@ -994,10 +1393,14 @@ function checkPublicUrl(value) {
994
1393
  }
995
1394
  if (url.protocol !== "https:" && url.protocol !== "http:") throw new MaterialError("bad-url", "Only http and https addresses are supported");
996
1395
  if (url.username || url.password) throw new MaterialError("bad-url", "Addresses with credentials are not supported");
997
- if (PRIVATE_HOST.test(url.hostname)) throw new MaterialError("bad-url", "Local network addresses are not supported");
1396
+ try {
1397
+ assertPublicHostname(url.hostname);
1398
+ } catch (error) {
1399
+ throw new MaterialError("bad-url", error.message);
1400
+ }
998
1401
  return url;
999
1402
  }
1000
- async function ingestUrl(address, { fetchImpl = globalThis.fetch, signal } = {}) {
1403
+ async function ingestUrl(address, { fetchImpl = pageFetch, signal } = {}) {
1001
1404
  let url = checkPublicUrl(address);
1002
1405
  let response;
1003
1406
  for (let hop = 0; hop < 6; hop += 1) {
@@ -1013,6 +1416,8 @@ async function ingestUrl(address, { fetchImpl = globalThis.fetch, signal } = {})
1013
1416
  });
1014
1417
  } catch (error) {
1015
1418
  if (signal?.aborted) throw signal.reason ?? error;
1419
+ if (error?.name === "AddressError") throw new MaterialError("bad-url", error.message);
1420
+ if (error?.code === "ETOOLARGE") throw new MaterialError("too-large", "The page is too large");
1016
1421
  throw new MaterialError("network", timeout.aborted ? "The page took too long to load" : "The page could not be loaded");
1017
1422
  }
1018
1423
  if (response.status >= 300 && response.status < 400 && response.headers.get("location")) {
@@ -1057,6 +1462,7 @@ async function ingestUrl(address, { fetchImpl = globalThis.fetch, signal } = {})
1057
1462
  //#region src/host/http.js
1058
1463
  const UPLOAD_PATH = "/api/aha-tutor/upload";
1059
1464
  const TTS_PATH = "/api/aha-tutor/tts";
1465
+ const FONT_PATH = "/api/aha-tutor/font";
1060
1466
  async function readCapped(stream, max, signal) {
1061
1467
  const parts = [];
1062
1468
  let size = 0;
@@ -1140,6 +1546,34 @@ function ttsRoute({ speak }) {
1140
1546
  }
1141
1547
  };
1142
1548
  }
1549
+ function fontsDir() {
1550
+ return join(dirname(createRequire(import.meta.url).resolve("dsh-aha-tutor/package.json")), "assets", "fonts");
1551
+ }
1552
+ const FONT_FILES = new Set(BOARD_FONT_FILES.map((entry) => entry.file));
1553
+ function fontRoute({ dir = fontsDir() } = {}) {
1554
+ const loaded = /* @__PURE__ */ new Map();
1555
+ return {
1556
+ path: FONT_PATH,
1557
+ methods: ["GET"],
1558
+ requestBody: "buffered",
1559
+ fetch: async (request) => {
1560
+ const name = new URL(request.url).searchParams.get("f") ?? "";
1561
+ if (!FONT_FILES.has(name)) return new Response("Not found", { status: 404 });
1562
+ try {
1563
+ if (!loaded.has(name)) loaded.set(name, await readFile(join(dir, name)));
1564
+ } catch {
1565
+ return new Response("Not found", { status: 404 });
1566
+ }
1567
+ return new Response(loaded.get(name), {
1568
+ status: 200,
1569
+ headers: {
1570
+ "content-type": "font/woff2",
1571
+ "cache-control": "public, max-age=31536000, immutable"
1572
+ }
1573
+ });
1574
+ }
1575
+ };
1576
+ }
1143
1577
  //#endregion
1144
1578
  //#region src/host/jobs.js
1145
1579
  const KEEP_FINISHED_MS = 10 * 6e4;
@@ -1162,7 +1596,7 @@ function createJobs({ logger, now = Date.now } = {}) {
1162
1596
  sweep();
1163
1597
  if (key && byKey.has(key)) {
1164
1598
  const existing = jobs.get(byKey.get(key));
1165
- if (existing && !existing.finishedAt) return snapshot(existing);
1599
+ if (existing && !existing.finishedAt && !existing.controller.signal.aborted) return snapshot(existing);
1166
1600
  }
1167
1601
  const controller = new AbortController();
1168
1602
  const job = {
@@ -1177,6 +1611,7 @@ function createJobs({ logger, now = Date.now } = {}) {
1177
1611
  label: ""
1178
1612
  },
1179
1613
  partial: [],
1614
+ preview: null,
1180
1615
  result: void 0,
1181
1616
  error: void 0,
1182
1617
  startedAt: now(),
@@ -1196,6 +1631,9 @@ function createJobs({ logger, now = Date.now } = {}) {
1196
1631
  },
1197
1632
  push(item) {
1198
1633
  job.partial.push(item);
1634
+ },
1635
+ preview(value) {
1636
+ job.preview = value ?? null;
1199
1637
  }
1200
1638
  };
1201
1639
  const aborted = new Promise((_, reject) => {
@@ -1206,7 +1644,10 @@ function createJobs({ logger, now = Date.now } = {}) {
1206
1644
  job.result = result;
1207
1645
  }).catch((error) => {
1208
1646
  job.status = controller.signal.aborted ? "cancelled" : "failed";
1209
- job.error = {
1647
+ job.error = controller.signal.aborted ? {
1648
+ code: "cancelled",
1649
+ message: "Cancelled"
1650
+ } : {
1210
1651
  code: error?.code ?? "failed",
1211
1652
  message: publicMessage(error)
1212
1653
  };
@@ -1225,6 +1666,7 @@ function createJobs({ logger, now = Date.now } = {}) {
1225
1666
  progress: job.progress,
1226
1667
  partial: job.partial.slice(since),
1227
1668
  partialCount: job.partial.length,
1669
+ preview: job.preview ?? void 0,
1228
1670
  result: job.status === "done" ? job.result : void 0,
1229
1671
  error: job.error
1230
1672
  };
@@ -1241,7 +1683,11 @@ function createJobs({ logger, now = Date.now } = {}) {
1241
1683
  function running(key) {
1242
1684
  const id = byKey.get(key);
1243
1685
  const job = id ? jobs.get(id) : void 0;
1244
- return job && !job.finishedAt ? snapshot(job) : null;
1686
+ return job && !job.finishedAt && !job.controller.signal.aborted ? snapshot(job) : null;
1687
+ }
1688
+ function cancelKey(key) {
1689
+ const job = running(key);
1690
+ return job ? cancel(job.id) : null;
1245
1691
  }
1246
1692
  function dispose() {
1247
1693
  for (const job of jobs.values()) if (!job.finishedAt) job.controller.abort(/* @__PURE__ */ new Error("plugin stopped"));
@@ -1252,6 +1698,7 @@ function createJobs({ logger, now = Date.now } = {}) {
1252
1698
  start,
1253
1699
  get,
1254
1700
  cancel,
1701
+ cancelKey,
1255
1702
  running,
1256
1703
  dispose
1257
1704
  });
@@ -1315,6 +1762,7 @@ function buildPlan({ today, settings, courses = [], reviews = [], exams = [] })
1315
1762
  const queues = courses.map((course) => ({
1316
1763
  id: course.id,
1317
1764
  title: course.title,
1765
+ emoji: course.emoji || "",
1318
1766
  deadline: course.deadline || null,
1319
1767
  priority: Number(course.priority) || 0,
1320
1768
  remaining: (course.lessons || []).filter((lesson) => !lesson.done).map((lesson) => ({
@@ -1375,6 +1823,7 @@ function buildPlan({ today, settings, courses = [], reviews = [], exams = [] })
1375
1823
  courseId: queue.id,
1376
1824
  title: lesson.title,
1377
1825
  courseTitle: queue.title,
1826
+ courseEmoji: queue.emoji,
1378
1827
  minutes: lesson.minutes
1379
1828
  });
1380
1829
  if (queue.remaining.length === 0) queue.finishDay = day;
@@ -1583,6 +2032,7 @@ function createLearner({ store, llm, now = () => /* @__PURE__ */ new Date(), log
1583
2032
  courses: active.map(({ course, progress }) => ({
1584
2033
  id: course.id,
1585
2034
  title: course.title,
2035
+ emoji: course.emoji ?? "",
1586
2036
  deadline: course.settings?.deadline ?? null,
1587
2037
  lessons: flattenLessons(course).map(({ lesson }) => ({
1588
2038
  id: lesson.id,
@@ -1637,10 +2087,11 @@ function createLearner({ store, llm, now = () => /* @__PURE__ */ new Date(), log
1637
2087
  }));
1638
2088
  return plan();
1639
2089
  }
1640
- async function reviewSession({ limit = 8 } = {}) {
2090
+ async function reviewSession({ limit = 8, courseId } = {}) {
1641
2091
  const at = now().getTime();
1642
2092
  const items = [];
1643
2093
  for (const { course, progress } of await activeCourses()) {
2094
+ if (courseId && course.id !== courseId) continue;
1644
2095
  const index = conceptIndex(course);
1645
2096
  const due = Object.entries(progress.reviews ?? {}).filter(([id, card]) => index.has(id) && Date.parse(card.dueAt) <= at).sort((a, b) => Date.parse(a[1].dueAt) - Date.parse(b[1].dueAt));
1646
2097
  for (const [conceptId] of due) {
@@ -1649,7 +2100,7 @@ function createLearner({ store, llm, now = () => /* @__PURE__ */ new Date(), log
1649
2100
  const lesson = findLesson(course, concept.lessonId)?.lesson;
1650
2101
  const set = await store.readJson(store.paths.practice(course.id, concept.lessonId), null);
1651
2102
  const board = await store.readJson(store.paths.board(course.id, concept.lessonId), null);
1652
- const candidates = [...set?.items ?? [], ...(board?.steps ?? []).map((step) => step.check).filter(Boolean)].filter((item) => item.conceptIds?.includes(conceptId) && item.kind !== "short");
2103
+ const candidates = [...set?.items ?? [], ...(board?.steps ?? []).map((step) => step.check).filter(Boolean)].filter((item) => item.conceptIds?.includes(conceptId) && item.kind !== "short" && item.kind !== "work");
1653
2104
  const item = candidates[Math.floor(Math.random() * candidates.length)];
1654
2105
  if (item) items.push({
1655
2106
  courseId: course.id,
@@ -1768,6 +2219,7 @@ function round2(value) {
1768
2219
  //#endregion
1769
2220
  //#region src/host/lessons.js
1770
2221
  const EVENTS_KEPT = 200;
2222
+ const STEP_ID = /^s\d{1,4}$/u;
1771
2223
  function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__PURE__ */ new Date() }) {
1772
2224
  const stamp = () => now().toISOString();
1773
2225
  async function context(courseId, lessonId) {
@@ -1781,10 +2233,11 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
1781
2233
  }
1782
2234
  async function get({ courseId, lessonId }) {
1783
2235
  const { course, row } = await context(courseId, lessonId);
1784
- const [board, practice, progress] = await Promise.all([
2236
+ const [board, practice, progress, asides] = await Promise.all([
1785
2237
  store.readJson(store.paths.board(courseId, lessonId), null),
1786
2238
  store.readJson(store.paths.practice(courseId, lessonId), null),
1787
- readProgress(store, courseId)
2239
+ readProgress(store, courseId),
2240
+ store.readJson(store.paths.asides(courseId, lessonId), null)
1788
2241
  ]);
1789
2242
  return {
1790
2243
  courseId,
@@ -1800,6 +2253,7 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
1800
2253
  lesson: row.lesson,
1801
2254
  board,
1802
2255
  practice,
2256
+ asides: Array.isArray(asides?.items) ? asides.items : [],
1803
2257
  state: progress.lessons?.[lessonId] ?? {
1804
2258
  status: "new",
1805
2259
  stepIndex: 0
@@ -1821,6 +2275,7 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
1821
2275
  const kindDirective = row.lesson.kind === "remedial" ? "remediate" : row.lesson.kind === "review" ? "review" : row.lesson.kind === "challenge" ? "challenge" : void 0;
1822
2276
  return generateSteps(llm, {
1823
2277
  purpose: "teach",
2278
+ effort: "low",
1824
2279
  prompt: lessonPrompt({
1825
2280
  course,
1826
2281
  unit: row.unit,
@@ -1845,6 +2300,11 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
1845
2300
  }
1846
2301
  async function prepare({ courseId, lessonId, force = false, style }) {
1847
2302
  const { course, row } = await context(courseId, lessonId);
2303
+ ensurePractice(course, row);
2304
+ if (force) {
2305
+ jobs.cancelKey(`board:${courseId}:${lessonId}`);
2306
+ await store.writeJson(store.paths.asides(courseId, lessonId), { items: [] });
2307
+ }
1848
2308
  if (!force) {
1849
2309
  const existing = await store.readJson(store.paths.board(courseId, lessonId), null);
1850
2310
  if (existing) {
@@ -1939,7 +2399,18 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
1939
2399
  }
1940
2400
  throw new TutorError("invalid", "Unknown board");
1941
2401
  }
1942
- function ask({ scope, stepIndex = 0, question }) {
2402
+ async function keepAside(scope, aside) {
2403
+ if (scope?.kind !== "lesson") return null;
2404
+ const saved = {
2405
+ id: newId("asd"),
2406
+ ...aside,
2407
+ at: stamp()
2408
+ };
2409
+ await store.updateJson(store.paths.asides(scope.courseId, scope.lessonId), null, (value) => ({ items: [...Array.isArray(value?.items) ? value.items : [], saved].slice(-30) }));
2410
+ return saved;
2411
+ }
2412
+ const anchorFor = (board, stepIndex, anchorStepId) => STEP_ID.test(String(anchorStepId)) ? anchorStepId : board?.steps?.[Math.max(0, Number(stepIndex) || 0)]?.id ?? "s1";
2413
+ function ask({ scope, stepIndex = 0, question, anchorStepId }) {
1943
2414
  const request = text(question, 1500);
1944
2415
  if (!request) throw new TutorError("invalid", "Type your question");
1945
2416
  return jobs.start("lesson/ask", async (api) => {
@@ -1963,21 +2434,31 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
1963
2434
  conceptIds: lesson?.concepts?.map((concept) => concept.id) ?? [],
1964
2435
  onStep: (step) => api.push(step)
1965
2436
  });
2437
+ const aside = await keepAside(scope, {
2438
+ kind: "question",
2439
+ question: request,
2440
+ anchorStepId: anchorFor(board, stepIndex, anchorStepId),
2441
+ steps: answer.steps
2442
+ });
1966
2443
  if (scope.kind === "lesson") await logEvent(scope.courseId, {
1967
2444
  type: "question",
1968
2445
  lessonId: scope.lessonId,
1969
2446
  text: request.slice(0, 200)
1970
2447
  });
1971
- return { steps: answer.steps };
2448
+ return {
2449
+ steps: answer.steps,
2450
+ aside
2451
+ };
1972
2452
  });
1973
2453
  }
1974
- function reexplain({ scope, stepIndex = 0, style = "example" }) {
2454
+ function reexplain({ scope, stepIndex = 0, style = "example", anchorStepId }) {
1975
2455
  return jobs.start("lesson/reexplain", async (api) => {
1976
2456
  const { course, lesson, board, language } = await scopeBoard(scope);
1977
2457
  if (!board?.steps?.length) throw new TutorError("invalid", "Nothing to re-explain yet");
1978
2458
  const index = Math.min(board.steps.length - 1, Math.max(0, Number(stepIndex) || 0));
1979
2459
  const answer = await generateSteps(llm, {
1980
2460
  purpose: "teach",
2461
+ effort: "low",
1981
2462
  prompt: reexplainPrompt({
1982
2463
  lesson,
1983
2464
  language,
@@ -1993,6 +2474,12 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
1993
2474
  conceptIds: lesson?.concepts?.map((concept) => concept.id) ?? [],
1994
2475
  onStep: (next) => api.push(next)
1995
2476
  });
2477
+ const aside = await keepAside(scope, {
2478
+ kind: "reexplain",
2479
+ style,
2480
+ anchorStepId: anchorFor(board, index, anchorStepId),
2481
+ steps: answer.steps
2482
+ });
1996
2483
  if (scope.kind === "lesson" && course) {
1997
2484
  const conceptIds = lesson.concepts.map((concept) => concept.id);
1998
2485
  await record({
@@ -2002,7 +2489,10 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
2002
2489
  evidence: { kind: "reexplain" }
2003
2490
  });
2004
2491
  }
2005
- return { steps: answer.steps };
2492
+ return {
2493
+ steps: answer.steps,
2494
+ aside
2495
+ };
2006
2496
  });
2007
2497
  }
2008
2498
  async function record({ courseId, lessonId, conceptIds = [], evidence = {} }) {
@@ -2049,18 +2539,25 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
2049
2539
  }
2050
2540
  async function grade({ item, response, language = "zh-CN" }) {
2051
2541
  if (!item?.prompt) throw new TutorError("invalid", "Missing question");
2542
+ const work = item.kind === "work";
2052
2543
  const answer = text(response, 4e3);
2053
2544
  if (!answer) return {
2054
2545
  score: 0,
2055
2546
  verdict: "incorrect",
2056
2547
  feedback: "",
2057
2548
  errorTag: "",
2058
- misconception: false
2549
+ misconception: false,
2550
+ ...work ? {
2551
+ wrongStep: "",
2552
+ why: "",
2553
+ fix: ""
2554
+ } : {}
2059
2555
  };
2060
2556
  return await generateJson(llm, {
2061
2557
  purpose: "quick",
2062
2558
  prompt: gradePrompt({
2063
2559
  item: {
2560
+ kind: work ? "work" : "short",
2064
2561
  prompt: text(item.prompt, 2e3),
2065
2562
  answer: text(item.answer, 3e3),
2066
2563
  rubric: Array.isArray(item.rubric) ? item.rubric.map((entry) => text(entry, 300)) : []
@@ -2068,7 +2565,7 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
2068
2565
  response: answer,
2069
2566
  language
2070
2567
  }),
2071
- maxTokens: 1200,
2568
+ maxTokens: work ? 1800 : 1200,
2072
2569
  normalize: (raw) => ({
2073
2570
  score: Math.min(1, Math.max(0, Number(raw?.score) || 0)),
2074
2571
  verdict: [
@@ -2078,10 +2575,60 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
2078
2575
  ].includes(raw?.verdict) ? raw.verdict : Number(raw?.score) >= .8 ? "correct" : Number(raw?.score) >= .4 ? "partial" : "incorrect",
2079
2576
  feedback: text(raw?.feedback, 1200),
2080
2577
  errorTag: text(raw?.errorTag, 80),
2081
- misconception: raw?.misconception === true
2578
+ misconception: raw?.misconception === true,
2579
+ ...work ? {
2580
+ wrongStep: text(raw?.wrongStep, 400),
2581
+ why: text(raw?.why, 400),
2582
+ fix: text(raw?.fix, 400)
2583
+ } : {}
2082
2584
  })
2083
2585
  });
2084
2586
  }
2587
+ function correct({ scope, stepIndex = 0, anchorStepId, item, response, grade: graded }) {
2588
+ const working = text(response, 4e3);
2589
+ if (!item?.prompt || !working) throw new TutorError("invalid", "Nothing to correct");
2590
+ const notes = graded && typeof graded === "object" ? graded : {};
2591
+ return jobs.start("lesson/correct", async (api) => {
2592
+ const { course, lesson, board, language } = await scopeBoard(scope);
2593
+ const answer = await generateSteps(llm, {
2594
+ purpose: "quick",
2595
+ prompt: correctionPrompt({
2596
+ course,
2597
+ lesson,
2598
+ language,
2599
+ boardSoFar: board ? boardToPlainText(board, {
2600
+ uptoStep: Number(stepIndex) || 0,
2601
+ maxChars: 5e3
2602
+ }) : "",
2603
+ item: {
2604
+ prompt: text(item.prompt, 2e3),
2605
+ answer: text(item.answer, 3e3)
2606
+ },
2607
+ response: working,
2608
+ grade: {
2609
+ feedback: text(notes.feedback, 1200),
2610
+ wrongStep: text(notes.wrongStep, 400),
2611
+ why: text(notes.why, 400),
2612
+ fix: text(notes.fix, 400)
2613
+ }
2614
+ }),
2615
+ maxTokens: 4e3,
2616
+ signal: api.signal,
2617
+ conceptIds: lesson?.concepts?.map((concept) => concept.id) ?? [],
2618
+ onStep: (step) => api.push(step)
2619
+ });
2620
+ const aside = await keepAside(scope, {
2621
+ kind: "correction",
2622
+ errorTag: text(notes.errorTag, 80),
2623
+ anchorStepId: anchorFor(board, stepIndex, anchorStepId),
2624
+ steps: answer.steps
2625
+ });
2626
+ return {
2627
+ steps: answer.steps,
2628
+ aside
2629
+ };
2630
+ });
2631
+ }
2085
2632
  async function savePosition({ courseId, lessonId, stepIndex = 0 }) {
2086
2633
  assertId(lessonId, "les");
2087
2634
  await store.updateJson(store.paths.progress(assertId(courseId, "crs")), null, (progress) => {
@@ -2169,37 +2716,34 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
2169
2716
  const { course, row } = await context(courseId, lessonId);
2170
2717
  const existing = await store.readJson(store.paths.practice(courseId, lessonId), null);
2171
2718
  if (existing) return { practice: existing };
2172
- return { job: jobs.start("lesson/practice", async (api) => {
2173
- const materials = await readMaterials(store, course.materialIds ?? []);
2174
- const sets = await generateJson(llm, {
2175
- purpose: "teach",
2176
- prompt: practicePrompt({
2177
- course,
2178
- lecture: row.lecture,
2179
- lessons: [row.lesson],
2180
- language: course.language,
2181
- passages: passagesFor(materials, row.lesson.title, 4e3)
2182
- }),
2183
- maxTokens: 5e3,
2184
- signal: api.signal,
2185
- normalize: (raw) => raw?.lessons ?? raw
2186
- });
2187
- const set = normalizePractice(sets?.[lessonId] ?? Object.values(sets ?? {})[0] ?? { items: [] }, row.lesson.concepts.map((concept) => concept.id));
2188
- if (!set.items.length) throw new TutorError("bad-output", "No practice items came back. Try again.");
2189
- const saved = {
2190
- lessonId,
2191
- ...set,
2192
- createdAt: stamp()
2193
- };
2194
- await store.writeJson(store.paths.practice(courseId, lessonId), saved);
2195
- return { practice: saved };
2719
+ return { job: lecturePractice(course, row) };
2720
+ }
2721
+ function lecturePractice(course, row) {
2722
+ return jobs.start("lesson/practice", async (api) => {
2723
+ const missing = [];
2724
+ for (const lesson of row.lecture.lessons) if (!await store.exists(store.paths.practice(course.id, lesson.id))) missing.push(lesson);
2725
+ const written = missing.length ? await courses.writeLecturePractice({
2726
+ course,
2727
+ lecture: row.lecture,
2728
+ lessons: missing,
2729
+ signal: api.signal
2730
+ }) : {};
2731
+ const sets = {};
2732
+ for (const lesson of row.lecture.lessons) sets[lesson.id] = written[lesson.id] ?? await store.readJson(store.paths.practice(course.id, lesson.id), null);
2733
+ if (!Object.values(sets).some(Boolean)) throw new TutorError("bad-output", "No practice items came back. Try again.");
2734
+ return { sets };
2196
2735
  }, {
2197
- key: `practice:${courseId}:${lessonId}`,
2736
+ key: `practice:${course.id}:${row.lecture.id}`,
2198
2737
  meta: {
2199
- courseId,
2200
- lessonId
2738
+ courseId: course.id,
2739
+ lectureId: row.lecture.id
2201
2740
  }
2202
- }) };
2741
+ });
2742
+ }
2743
+ function ensurePractice(course, row) {
2744
+ store.exists(store.paths.practice(course.id, row.lesson.id)).then((exists) => {
2745
+ if (!exists && !jobs.running(`practice:${course.id}:${row.lecture.id}`)) lecturePractice(course, row);
2746
+ }).catch(() => void 0);
2203
2747
  }
2204
2748
  async function unitSheet({ courseId, unitId, force = false }) {
2205
2749
  const course = await courses.readCourse(courseId);
@@ -2357,6 +2901,7 @@ function createLessons({ store, llm, jobs, courses, learner, now = () => /* @__P
2357
2901
  reexplain,
2358
2902
  record,
2359
2903
  grade,
2904
+ correct,
2360
2905
  savePosition,
2361
2906
  complete,
2362
2907
  practice,
@@ -2394,6 +2939,17 @@ const RETRYABLE$1 = /* @__PURE__ */ new Set([
2394
2939
  "TIMEOUT",
2395
2940
  "TRANSPORT"
2396
2941
  ]);
2942
+ const REASONING_ROOM = Object.freeze({
2943
+ off: 0,
2944
+ none: 0,
2945
+ minimal: 4e3,
2946
+ low: 8e3,
2947
+ medium: 16e3,
2948
+ high: 24e3,
2949
+ xhigh: 48e3,
2950
+ max: 48e3
2951
+ });
2952
+ const STOPPED_AT_LIMIT = /* @__PURE__ */ new Set(["max-tokens", "length"]);
2397
2953
  const CALL_TIMEOUT_MS = 6 * 6e4;
2398
2954
  const MAX_RETRIES = 2;
2399
2955
  const USER_ERRORS = Object.freeze({
@@ -2407,6 +2963,7 @@ const USER_ERRORS = Object.freeze({
2407
2963
  CONTEXT_WINDOW_EXCEEDED: ["llm-context", "The material is too long for this model. Pick fewer materials or a model with a larger context."],
2408
2964
  NO_ADAPTER: ["llm-unavailable", "The selected model is not available. Pick another model in Aha settings."],
2409
2965
  TIMEOUT: ["llm-timeout", "The model took too long to respond. Try again."],
2966
+ EMPTY_RESPONSE: ["llm-empty", "The model returned nothing this time. Try again."],
2410
2967
  ABORTED: ["cancelled", "Cancelled"]
2411
2968
  });
2412
2969
  function toTutorError(code, fallback) {
@@ -2414,6 +2971,17 @@ function toTutorError(code, fallback) {
2414
2971
  if (known) return new TutorError(known[0], known[1]);
2415
2972
  return new TutorError("llm-failed", `The model call failed${code ? ` (${code})` : ""}${fallback ? `: ${fallback}` : ""}`);
2416
2973
  }
2974
+ function pickEffort(efforts, wanted) {
2975
+ if (!efforts?.length || !wanted) return void 0;
2976
+ if (wanted === "lowest") return efforts[0].id;
2977
+ return (efforts.find((effort) => effort.id === wanted) ?? efforts.find((effort) => effort.id !== "off" && effort.id !== "none") ?? efforts[0]).id;
2978
+ }
2979
+ function outputBudget(maxTokens, effort, model) {
2980
+ const cap = Number(model?.defaultMaxTokens) || 0;
2981
+ if (!cap || !model?.reasoning) return maxTokens;
2982
+ const room = REASONING_ROOM[effort ?? model.reasoning.defaultEffort] ?? REASONING_ROOM.high;
2983
+ return Math.max(Math.min(maxTokens, cap), Math.min(maxTokens + room, cap));
2984
+ }
2417
2985
  const sleep$1 = (ms, signal) => new Promise((resolve, reject) => {
2418
2986
  const timer = setTimeout(resolve, ms);
2419
2987
  signal?.addEventListener("abort", () => {
@@ -2473,7 +3041,7 @@ function createLlmPort({ getLlm, getDefault, getAttachments, prefs, logger }) {
2473
3041
  }
2474
3042
  return blocks;
2475
3043
  }
2476
- async function attempt({ purpose, system, user, images, maxTokens, signal, onText }) {
3044
+ async function attempt({ purpose, effort, system, user, images, maxTokens, signal, onText }) {
2477
3045
  const llm = getLlm();
2478
3046
  if (!llm) throw new TutorError("llm-unavailable", "DSH has no model service in this profile.");
2479
3047
  const target = route(purpose);
@@ -2484,13 +3052,14 @@ function createLlmPort({ getLlm, getDefault, getAttachments, prefs, logger }) {
2484
3052
  prepared = await llm.prepareCall({
2485
3053
  ...target,
2486
3054
  maxTokens
2487
- }, fused, purpose === "quick" ? (controls, model) => {
2488
- const lowest = model?.reasoning?.efforts?.[0];
2489
- return lowest ? {
3055
+ }, fused, (controls, model) => {
3056
+ const chosen = pickEffort(model?.reasoning?.efforts, purpose === "quick" ? "lowest" : effort);
3057
+ return {
2490
3058
  ...controls,
2491
- reasoningEffort: lowest.id
2492
- } : controls;
2493
- } : void 0);
3059
+ ...chosen ? { reasoningEffort: chosen } : {},
3060
+ maxTokens: outputBudget(maxTokens, chosen, model)
3061
+ };
3062
+ });
2494
3063
  } catch (error) {
2495
3064
  if (signal?.aborted) throw signal.reason ?? error;
2496
3065
  throw Object.assign(toTutorError(error?.code, error?.message), { retryable: false });
@@ -2529,18 +3098,24 @@ function createLlmPort({ getLlm, getDefault, getAttachments, prefs, logger }) {
2529
3098
  }
2530
3099
  if (!textValue.trim()) throw Object.assign(toTutorError("EMPTY_RESPONSE"), {
2531
3100
  retryable: true,
2532
- streamed: false
3101
+ streamed: false,
3102
+ grow: STOPPED_AT_LIMIT.has(finish?.kind)
2533
3103
  });
2534
3104
  return textValue;
2535
3105
  }
2536
3106
  async function call(options) {
2537
3107
  let lastError;
3108
+ let current = options;
2538
3109
  for (let tries = 0; tries <= MAX_RETRIES; tries += 1) try {
2539
- return await attempt(options);
3110
+ return await attempt(current);
2540
3111
  } catch (error) {
2541
3112
  lastError = error;
2542
3113
  if (options.signal?.aborted || !error?.retryable || error?.streamed || tries === MAX_RETRIES) throw error;
2543
- await sleep$1(Math.min(2e4, Number(error.retryAfterMs) || 1500 * (tries + 1)), options.signal);
3114
+ if (error.grow) current = {
3115
+ ...current,
3116
+ maxTokens: current.maxTokens * 2
3117
+ };
3118
+ else await sleep$1(Math.min(2e4, Number(error.retryAfterMs) || 1500 * (tries + 1)), options.signal);
2544
3119
  }
2545
3120
  throw lastError;
2546
3121
  }
@@ -2642,12 +3217,14 @@ function createStore({ root }) {
2642
3217
  course: (id) => join(paths.courseDir(id), "course.json"),
2643
3218
  progress: (id) => join(paths.courseDir(id), "progress.json"),
2644
3219
  intake: (id) => join(paths.courseDir(id), "intake.json"),
3220
+ proposal: (id) => join(paths.courseDir(id), "proposal.json"),
2645
3221
  cards: (id) => join(paths.courseDir(id), "cards.json"),
2646
3222
  boardsDir: (id) => join(paths.courseDir(id), "boards"),
2647
3223
  practiceDir: (id) => join(paths.courseDir(id), "practice"),
2648
3224
  sheetsDir: (id) => join(paths.courseDir(id), "sheets"),
2649
3225
  board: (id, lessonId) => join(paths.courseDir(id), "boards", `${assertId(lessonId, "les")}.json`),
2650
3226
  practice: (id, lessonId) => join(paths.courseDir(id), "practice", `${assertId(lessonId, "les")}.json`),
3227
+ asides: (id, lessonId) => join(paths.courseDir(id), "asides", `${assertId(lessonId, "les")}.json`),
2651
3228
  sheet: (id, unitId) => join(paths.courseDir(id), "sheets", `${assertId(unitId, "unt")}.json`),
2652
3229
  unitQuiz: (id, unitId) => join(paths.courseDir(id), "quizzes", `${assertId(unitId, "unt")}.json`),
2653
3230
  materials: () => join(root, "materials"),
@@ -3344,10 +3921,20 @@ function createRoutes({ courses, lessons, learner, studio, projects, jobs, setti
3344
3921
  quick: payload.quick === true,
3345
3922
  language: typeof payload.language === "string" ? payload.language.slice(0, 12) : void 0
3346
3923
  }) }),
3924
+ "course/proposal-apply": (payload) => courses.applyProposal({ courseId: need(payload, "courseId", "crs") }),
3925
+ "course/proposal-discard": (payload) => courses.discardProposal({ courseId: need(payload, "courseId", "crs") }),
3926
+ "course/rework-lesson": (payload) => ({ job: courses.reworkLesson({
3927
+ courseId: need(payload, "courseId", "crs"),
3928
+ lesson: payload.lesson,
3929
+ unitTitle: payload.unitTitle,
3930
+ lectureTitle: payload.lectureTitle,
3931
+ mode: payload.mode === "split" ? "split" : "deepen"
3932
+ }) }),
3347
3933
  "course/intake": (payload) => ({ job: courses.submitIntake({
3348
3934
  courseId: need(payload, "courseId", "crs"),
3349
3935
  answers: payload.answers,
3350
3936
  timeline: payload.timeline,
3937
+ size: payload.size,
3351
3938
  weeklyMinutes: payload.weeklyMinutes,
3352
3939
  deadline: payload.deadline
3353
3940
  }) }),
@@ -3401,12 +3988,14 @@ function createRoutes({ courses, lessons, learner, studio, projects, jobs, setti
3401
3988
  "lesson/ask": (payload) => ({ job: lessons.ask({
3402
3989
  scope: scopeOf(payload),
3403
3990
  stepIndex: payload.stepIndex,
3404
- question: payload.question
3991
+ question: payload.question,
3992
+ anchorStepId: payload.anchorStepId
3405
3993
  }) }),
3406
3994
  "lesson/reexplain": (payload) => ({ job: lessons.reexplain({
3407
3995
  scope: scopeOf(payload),
3408
3996
  stepIndex: payload.stepIndex,
3409
- style: STYLES.includes(payload.style) ? payload.style : "example"
3997
+ style: STYLES.includes(payload.style) ? payload.style : "example",
3998
+ anchorStepId: payload.anchorStepId
3410
3999
  }) }),
3411
4000
  "lesson/record": (payload) => lessons.record({
3412
4001
  courseId: need(payload, "courseId", "crs"),
@@ -3428,6 +4017,14 @@ function createRoutes({ courses, lessons, learner, studio, projects, jobs, setti
3428
4017
  response: payload.response,
3429
4018
  language: typeof payload.language === "string" ? payload.language.slice(0, 12) : void 0
3430
4019
  }),
4020
+ "lesson/correct": (payload) => ({ job: lessons.correct({
4021
+ scope: scopeOf(payload),
4022
+ stepIndex: payload.stepIndex,
4023
+ anchorStepId: payload.anchorStepId,
4024
+ item: payload.item,
4025
+ response: payload.response,
4026
+ grade: payload.grade
4027
+ }) }),
3431
4028
  "lesson/position": (payload) => lessons.savePosition({
3432
4029
  courseId: need(payload, "courseId", "crs"),
3433
4030
  lessonId: need(payload, "lessonId", "les"),
@@ -3479,7 +4076,7 @@ function createRoutes({ courses, lessons, learner, studio, projects, jobs, setti
3479
4076
  courseId: payload.courseId
3480
4077
  }),
3481
4078
  "plan/remove-exam": (payload) => learner.removeExam({ id: need(payload, "id", "exm") }),
3482
- "review/session": () => learner.reviewSession(),
4079
+ "review/session": (payload) => learner.reviewSession({ courseId: optional(payload, "courseId", "crs") }),
3483
4080
  "material/upload": (payload) => ({ job: studio.upload({
3484
4081
  name: payload.name,
3485
4082
  base64: payload.base64
@@ -4083,6 +4680,7 @@ const PREF_DEFAULTS = Object.freeze({
4083
4680
  voiceEngine: "browser",
4084
4681
  voiceRate: 1,
4085
4682
  boardTheme: "auto",
4683
+ boardFont: "hand",
4086
4684
  ttsProvider: "openai",
4087
4685
  tts: Object.fromEntries(TTS_PROVIDERS.map((provider) => [provider, providerDefaults(provider)]))
4088
4686
  });
@@ -4112,6 +4710,7 @@ function cleanPrefs(input = {}, base = PREF_DEFAULTS) {
4112
4710
  "light",
4113
4711
  "dark"
4114
4712
  ].includes(input.boardTheme)) next.boardTheme = input.boardTheme;
4713
+ if (["hand", "standard"].includes(input.boardFont)) next.boardFont = input.boardFont;
4115
4714
  if (TTS_PROVIDERS.includes(input.ttsProvider)) next.ttsProvider = input.ttsProvider;
4116
4715
  for (const provider of TTS_PROVIDERS) {
4117
4716
  const patch = input.tts?.[provider];
@@ -4221,6 +4820,8 @@ const ASSIST_MODES = [
4221
4820
  "solve",
4222
4821
  "visualize"
4223
4822
  ];
4823
+ const DIGEST_SINGLE_CHARS = 24e3;
4824
+ const DIGEST_NOTES_CHARS = 48e3;
4224
4825
  function createStudio({ store, llm, jobs, now = () => /* @__PURE__ */ new Date(), uiLanguage = () => "zh-CN" }) {
4225
4826
  const stamp = () => now().toISOString();
4226
4827
  async function saveMaterial(result, { bytes, signal, extra = {} }) {
@@ -4246,7 +4847,7 @@ function createStudio({ store, llm, jobs, now = () => /* @__PURE__ */ new Date()
4246
4847
  purpose: "quick",
4247
4848
  prompt: materialBriefPrompt({
4248
4849
  title: meta.title,
4249
- passages: result.chunks.slice(0, 8).map((chunk) => chunk.text).join("\n\n").slice(0, 9e3),
4850
+ passages: formatPassages(spreadPassages(result.chunks, { maxChars: 12e3 }), ""),
4250
4851
  language: detectLanguage(result.chunks[0].text, uiLanguage())
4251
4852
  }),
4252
4853
  maxTokens: 1500,
@@ -4328,36 +4929,77 @@ function createStudio({ store, llm, jobs, now = () => /* @__PURE__ */ new Date()
4328
4929
  return jobs.start("material/digest", async (api) => {
4329
4930
  const [material] = await readMaterials(store, [assertId(id, "mat")]);
4330
4931
  if (!material?.chunks.length) throw new TutorError("invalid", "This material has no text to digest");
4932
+ const title = material.meta.title;
4331
4933
  const language = detectLanguage(material.chunks[0].text, uiLanguage());
4934
+ const size = material.chunks.reduce((sum, chunk) => sum + chunk.text.length, 0);
4935
+ let prompt;
4936
+ let missing = [];
4937
+ if (size <= DIGEST_SINGLE_CHARS) {
4938
+ api.progress(0, 1, "writing");
4939
+ prompt = digestPrompt({
4940
+ title,
4941
+ passages: formatPassages(material.chunks, ""),
4942
+ language
4943
+ });
4944
+ } else {
4945
+ const parts = documentParts(material.chunks);
4946
+ const notes = new Array(parts.length).fill(null);
4947
+ const readPart = async (index) => {
4948
+ try {
4949
+ notes[index] = await generateJson(llm, {
4950
+ purpose: "teach",
4951
+ prompt: digestPartPrompt({
4952
+ title,
4953
+ part: index + 1,
4954
+ parts: parts.length,
4955
+ range: passageRange(parts[index]),
4956
+ passages: formatPassages(parts[index], ""),
4957
+ language
4958
+ }),
4959
+ maxTokens: 6e3,
4960
+ signal: api.signal,
4961
+ normalize: normalizeDigestPart
4962
+ });
4963
+ } catch (error) {
4964
+ if (api.signal.aborted) throw error;
4965
+ }
4966
+ };
4967
+ const queue = parts.map((part, index) => index);
4968
+ let done = 0;
4969
+ api.progress(0, parts.length + 1, "reading");
4970
+ const worker = async () => {
4971
+ while (queue.length) {
4972
+ api.signal.throwIfAborted();
4973
+ await readPart(queue.shift());
4974
+ done += 1;
4975
+ api.progress(done, parts.length + 1, "reading");
4976
+ }
4977
+ };
4978
+ await Promise.all([worker(), worker()]);
4979
+ for (const [index, note] of notes.entries()) if (!note) await readPart(index);
4980
+ const read = notes.map((note, index) => note ? {
4981
+ part: index + 1,
4982
+ of: parts.length,
4983
+ range: passageRange(parts[index]),
4984
+ ...note
4985
+ } : null).filter(Boolean);
4986
+ if (read.length < Math.ceil(parts.length / 2)) throw new TutorError("bad-output", "The model could not read enough of this document. Try again or pick a stronger model in settings.");
4987
+ missing = parts.map((part, index) => notes[index] ? null : passageRange(part) || `${index + 1}/${parts.length}`).filter(Boolean);
4988
+ api.progress(parts.length, parts.length + 1, "writing");
4989
+ prompt = digestPrompt({
4990
+ title,
4991
+ notes: digestNotesText(read, DIGEST_NOTES_CHARS),
4992
+ language
4993
+ });
4994
+ }
4332
4995
  const result = await generateJson(llm, {
4333
4996
  purpose: "teach",
4334
- prompt: digestPrompt({
4335
- title: material.meta.title,
4336
- passages: passagesFor([material], "", 24e3),
4337
- language
4338
- }),
4997
+ prompt,
4339
4998
  maxTokens: 9e3,
4340
4999
  signal: api.signal,
4341
- normalize: (raw) => ({
4342
- overview: text(raw?.overview, 2e3),
4343
- structure: (Array.isArray(raw?.structure) ? raw.structure : []).slice(0, 30).map((item) => ({
4344
- heading: text(item?.heading, 160),
4345
- points: (item?.points ?? []).map((point) => text(point, 400)).filter(Boolean).slice(0, 10),
4346
- pages: text(item?.pages, 30)
4347
- })).filter((item) => item.heading),
4348
- keyIdeas: (raw?.keyIdeas ?? []).map((item) => text(item, 400)).filter(Boolean).slice(0, 15),
4349
- arguments: (raw?.arguments ?? []).slice(0, 15).map((item) => ({
4350
- claim: text(item?.claim, 400),
4351
- support: text(item?.support, 600),
4352
- page: text(item?.page, 20)
4353
- })).filter((item) => item.claim),
4354
- glossary: (raw?.glossary ?? []).slice(0, 30).map((item) => ({
4355
- term: text(item?.term, 80),
4356
- meaning: text(item?.meaning, 400)
4357
- })).filter((item) => item.term),
4358
- questions: (raw?.questions ?? []).map((item) => text(item, 300)).filter(Boolean).slice(0, 8)
4359
- })
5000
+ normalize: normalizeDigest
4360
5001
  });
5002
+ if (missing.length) result.missing = missing;
4361
5003
  return { material: await store.updateJson(store.paths.material(id), null, (value) => ({
4362
5004
  ...value,
4363
5005
  digest: {
@@ -4598,6 +5240,7 @@ function createStudio({ store, llm, jobs, now = () => /* @__PURE__ */ new Date()
4598
5240
  const images = await assistImages(ids);
4599
5241
  const board = await generateSteps(llm, {
4600
5242
  purpose: "teach",
5243
+ effort: "low",
4601
5244
  prompt: assistPrompt({
4602
5245
  mode: chosen,
4603
5246
  request: ask,
@@ -4618,21 +5261,24 @@ function createStudio({ store, llm, jobs, now = () => /* @__PURE__ */ new Date()
4618
5261
  board,
4619
5262
  at: stamp()
4620
5263
  };
4621
- if (!thread) thread = {
4622
- id: /^thd_[a-z0-9]{6,40}$/u.test(String(newThreadId)) ? newThreadId : newId("thd"),
4623
- title: board.title || ask.slice(0, 60),
4624
- language,
4625
- materialIds: ids,
4626
- turns: [],
4627
- createdAt: stamp()
4628
- };
4629
- thread.turns = [...thread.turns, turn].slice(-30);
4630
- thread.materialIds = ids;
4631
- thread.updatedAt = stamp();
4632
- await store.writeJson(store.paths.item("assist", thread.id), thread);
5264
+ const id = thread?.id ?? (/^thd_[a-z0-9]{6,40}$/u.test(String(newThreadId)) ? newThreadId : newId("thd"));
5265
+ const saved = await store.updateJson(store.paths.item("assist", id), null, (current) => {
5266
+ const next = current ?? {
5267
+ id,
5268
+ title: board.title || ask.slice(0, 60),
5269
+ language,
5270
+ materialIds: [],
5271
+ turns: [],
5272
+ createdAt: stamp()
5273
+ };
5274
+ next.turns = [...next.turns ?? [], turn].slice(-30);
5275
+ next.materialIds = [.../* @__PURE__ */ new Set([...next.materialIds ?? [], ...ids])].slice(0, 8);
5276
+ next.updatedAt = stamp();
5277
+ return next;
5278
+ });
4633
5279
  return {
4634
- threadId: thread.id,
4635
- turn: thread.turns.length - 1,
5280
+ threadId: saved.id,
5281
+ turn: saved.turns.length - 1,
4636
5282
  board
4637
5283
  };
4638
5284
  }, { meta: { threadId: threadId ?? null } });
@@ -4656,6 +5302,7 @@ function createStudio({ store, llm, jobs, now = () => /* @__PURE__ */ new Date()
4656
5302
  const language = detectLanguage(subject || passages, uiLanguage());
4657
5303
  const board = await generateSteps(llm, {
4658
5304
  purpose: "teach",
5305
+ effort: "low",
4659
5306
  prompt: shortPrompt({
4660
5307
  topic: subject || sourceTitle,
4661
5308
  passages,
@@ -4702,6 +5349,60 @@ function createStudio({ store, llm, jobs, now = () => /* @__PURE__ */ new Date()
4702
5349
  makeShort
4703
5350
  });
4704
5351
  }
5352
+ function normalizeDigest(raw) {
5353
+ return {
5354
+ overview: text(raw?.overview, 2e3),
5355
+ structure: (Array.isArray(raw?.structure) ? raw.structure : []).slice(0, 30).map((item) => ({
5356
+ heading: text(item?.heading, 160),
5357
+ points: (item?.points ?? []).map((point) => text(point, 400)).filter(Boolean).slice(0, 10),
5358
+ pages: text(item?.pages, 30)
5359
+ })).filter((item) => item.heading),
5360
+ keyIdeas: (raw?.keyIdeas ?? []).map((item) => text(item, 400)).filter(Boolean).slice(0, 15),
5361
+ arguments: (raw?.arguments ?? []).slice(0, 15).map((item) => ({
5362
+ claim: text(item?.claim, 400),
5363
+ support: text(item?.support, 600),
5364
+ page: text(item?.page, 20)
5365
+ })).filter((item) => item.claim),
5366
+ glossary: (raw?.glossary ?? []).slice(0, 30).map((item) => ({
5367
+ term: text(item?.term, 80),
5368
+ meaning: text(item?.meaning, 400)
5369
+ })).filter((item) => item.term),
5370
+ questions: (raw?.questions ?? []).map((item) => text(item, 300)).filter(Boolean).slice(0, 8)
5371
+ };
5372
+ }
5373
+ function normalizeDigestPart(raw) {
5374
+ const value = {
5375
+ sections: (Array.isArray(raw?.sections) ? raw.sections : []).slice(0, 12).map((item) => ({
5376
+ heading: text(item?.heading, 160),
5377
+ pages: text(item?.pages, 30),
5378
+ points: (Array.isArray(item?.points) ? item.points : []).map((point) => text(point, 300)).filter(Boolean).slice(0, 6)
5379
+ })).filter((item) => item.heading),
5380
+ keyIdeas: (Array.isArray(raw?.keyIdeas) ? raw.keyIdeas : []).map((item) => text(item, 300)).filter(Boolean).slice(0, 8),
5381
+ arguments: (Array.isArray(raw?.arguments) ? raw.arguments : []).slice(0, 8).map((item) => ({
5382
+ claim: text(item?.claim, 300),
5383
+ support: text(item?.support, 400),
5384
+ page: text(item?.page, 20)
5385
+ })).filter((item) => item.claim),
5386
+ glossary: (Array.isArray(raw?.glossary) ? raw.glossary : []).slice(0, 12).map((item) => ({
5387
+ term: text(item?.term, 80),
5388
+ meaning: text(item?.meaning, 300)
5389
+ })).filter((item) => item.term)
5390
+ };
5391
+ if (!value.sections.length && !value.keyIdeas.length) throw new Error("no notes for this part");
5392
+ return value;
5393
+ }
5394
+ function digestNotesText(notes, maxChars) {
5395
+ const share = Math.max(600, Math.floor(maxChars / Math.max(1, notes.length)));
5396
+ return notes.map((note) => {
5397
+ const lines = [`[Part ${note.part}/${note.of}${note.range ? ` · ${note.range}` : ""}]`];
5398
+ for (const section of note.sections) lines.push(`- ${section.heading}${section.pages ? ` (p.${section.pages})` : ""}: ${section.points.join("; ")}`);
5399
+ if (note.keyIdeas.length) lines.push(`Key ideas: ${note.keyIdeas.join("; ")}`);
5400
+ for (const argument of note.arguments) lines.push(`Claim: ${argument.claim}${argument.page ? ` (p.${argument.page})` : ""} — ${argument.support}`);
5401
+ if (note.glossary.length) lines.push(`Terms: ${note.glossary.map((entry) => `${entry.term}: ${entry.meaning}`).join("; ")}`);
5402
+ const block = lines.join("\n");
5403
+ return block.length > share ? `${block.slice(0, share)}…` : block;
5404
+ }).join("\n\n");
5405
+ }
4705
5406
  //#endregion
4706
5407
  //#region src/index.js
4707
5408
  const name = "aha-tutor";
@@ -4822,6 +5523,7 @@ function apply(ctx) {
4822
5523
  if (typeof fetchRoutes?.register !== "function") return;
4823
5524
  connectionCtx.effect(() => fetchRoutes.register(uploadRoute({ studio })), "aha-tutor: upload route");
4824
5525
  connectionCtx.effect(() => fetchRoutes.register(ttsRoute({ speak })), "aha-tutor: tts route");
5526
+ connectionCtx.effect(() => fetchRoutes.register(fontRoute()), "aha-tutor: font route");
4825
5527
  });
4826
5528
  ctx.effect(() => publishRuntime({
4827
5529
  store,