@kirkchinese/dsh-citeciter 0.9.0-beta.2 → 0.9.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +11 -8
  2. package/README.zh.md +11 -8
  3. package/lib/client.js +2175 -2344
  4. package/lib/index.js +1546 -307
  5. package/lib/{typert-common-g1lnIG8u.js → typert-common-B3cX-hWj.js} +1 -1
  6. package/lib/typert.host.js +2 -2
  7. package/lib/typert.remote-client.js +1 -1
  8. package/lib/types/actions.d.ts +7 -0
  9. package/lib/types/blackboard-tool.d.ts +6 -0
  10. package/lib/types/board-animation.d.ts +278 -0
  11. package/lib/types/board-capture-protocol.d.ts +61 -1
  12. package/lib/types/board-capture.d.ts +2 -0
  13. package/lib/types/board.d.ts +62 -3
  14. package/lib/types/client/board-timeline.d.ts +30 -0
  15. package/lib/types/client/companion-controller.d.ts +1 -1
  16. package/lib/types/client/components/BoardCaptureSurface.d.ts +3 -1
  17. package/lib/types/client/components/BoardView.d.ts +3 -1
  18. package/lib/types/client/components/PermissionControl.d.ts +1 -1
  19. package/lib/types/client/components/TopicActions.d.ts +1 -1
  20. package/lib/types/client/components/TopicModelControls.d.ts +1 -1
  21. package/lib/types/host-agent-modules.d.ts +4 -1
  22. package/lib/types/line-diff.d.ts +12 -0
  23. package/lib/types/message-projection.d.ts +1 -0
  24. package/lib/types/observer.d.ts +0 -30
  25. package/lib/types/source-read-tool.d.ts +3 -3
  26. package/lib/types/source-session.d.ts +3 -0
  27. package/lib/types/source-transcript.d.ts +58 -0
  28. package/lib/types/topic-context.d.ts +74 -0
  29. package/lib/types/topic-log.d.ts +2 -0
  30. package/lib/types/topic-runtime.d.ts +4 -0
  31. package/lib/types/topic.d.ts +198 -14
  32. package/lib/types/typert-common.d.ts +124 -5
  33. package/lib/types/typert.host.d.ts +311 -14
  34. package/lib/types/workspace-changes.d.ts +90 -0
  35. package/lib/{update-B_0nN1_f.js → update-CGteMuNJ.js} +651 -4
  36. package/package.json +62 -64
  37. package/lib/types/client/components/ChoicePopover.d.ts +0 -8
@@ -152,6 +152,641 @@ const nativeStateSchema = z.object({
152
152
  attachments: z.array(nativeAttachmentSchema)
153
153
  }).strict())
154
154
  }).strict();
155
+ const ANIMATION_MAX_DURATION_MS = 3e4;
156
+ /** Offset in canvas percent used by directional appear and disappear. */
157
+ const SLIDE_DISTANCE = 6;
158
+ const easingSchema = z.enum([
159
+ "linear",
160
+ "ease-in",
161
+ "ease-out",
162
+ "ease-in-out"
163
+ ]);
164
+ const directionSchema = z.enum([
165
+ "left",
166
+ "right",
167
+ "up",
168
+ "down"
169
+ ]);
170
+ const timeSchema = z.number().int().min(0).max(ANIMATION_MAX_DURATION_MS);
171
+ const coordinateSchema = z.number().min(-50).max(150);
172
+ const expressionSchema = z.string().min(1).max(240);
173
+ /** One verb applied to one board element. */
174
+ const animationTrackSchema = z.object({
175
+ target: z.string().min(1).max(80),
176
+ verb: z.enum([
177
+ "appear",
178
+ "disappear",
179
+ "move",
180
+ "scale",
181
+ "rotate",
182
+ "highlight",
183
+ "reveal",
184
+ "follow",
185
+ "keyframes",
186
+ "parametric"
187
+ ]),
188
+ startMs: timeSchema.optional(),
189
+ endMs: timeSchema.optional(),
190
+ easing: easingSchema.optional(),
191
+ to: z.object({
192
+ x: coordinateSchema,
193
+ y: coordinateSchema
194
+ }).strict().optional(),
195
+ scale: z.number().min(.05).max(10).optional(),
196
+ degrees: z.number().min(-3600).max(3600).optional(),
197
+ direction: directionSchema.optional(),
198
+ path: z.string().min(1).max(4e3).optional(),
199
+ orient: z.boolean().optional(),
200
+ frames: z.array(z.object({
201
+ atMs: timeSchema,
202
+ x: coordinateSchema.optional(),
203
+ y: coordinateSchema.optional(),
204
+ opacity: z.number().min(0).max(1).optional(),
205
+ scale: z.number().min(.05).max(10).optional(),
206
+ rotate: z.number().min(-3600).max(3600).optional()
207
+ }).strict()).min(1).max(40).optional(),
208
+ x: expressionSchema.optional(),
209
+ y: expressionSchema.optional(),
210
+ t: z.tuple([z.number().min(-1e6).max(1e6), z.number().min(-1e6).max(1e6)]).optional()
211
+ }).strict();
212
+ /** One complete timeline as written by the model. */
213
+ const animationSpecSchema = z.object({
214
+ durationMs: z.number().int().min(200).max(ANIMATION_MAX_DURATION_MS),
215
+ loop: z.boolean().optional(),
216
+ beats: z.array(z.object({
217
+ atMs: timeSchema,
218
+ caption: z.string().min(1).max(160)
219
+ }).strict()).max(20).optional(),
220
+ tracks: z.array(animationTrackSchema).min(1).max(40)
221
+ }).strict();
222
+ /** The board's current timeline: the spec of the latest successful call, keyed by that call. */
223
+ const boardTimelineSchema = z.object({
224
+ id: z.string().min(1),
225
+ spec: animationSpecSchema
226
+ }).strict();
227
+ const FUNCTIONS = {
228
+ sin: Math.sin,
229
+ cos: Math.cos,
230
+ tan: Math.tan,
231
+ asin: Math.asin,
232
+ acos: Math.acos,
233
+ atan: Math.atan,
234
+ atan2: Math.atan2,
235
+ sqrt: Math.sqrt,
236
+ abs: Math.abs,
237
+ exp: Math.exp,
238
+ log: Math.log,
239
+ min: Math.min,
240
+ max: Math.max,
241
+ pow: Math.pow,
242
+ floor: Math.floor,
243
+ ceil: Math.ceil,
244
+ round: Math.round,
245
+ sign: Math.sign
246
+ };
247
+ const CONSTANTS = {
248
+ pi: Math.PI,
249
+ e: Math.E,
250
+ tau: Math.PI * 2
251
+ };
252
+ /**
253
+ * Compile a restricted arithmetic expression of `t`: numbers, `+ - * / ^`, parentheses,
254
+ * the constants pi, e, tau and a fixed set of Math functions. Nothing else is accepted.
255
+ * @throws an Error naming the first unexpected token.
256
+ */
257
+ function compileExpression(source) {
258
+ const tokens = source.match(/\d+(?:\.\d+)?(?:e[+-]?\d+)?|\.\d+|[a-z_][a-z0-9_]*|\*\*|[-+*/^(),]|\S/giu) ?? [];
259
+ let index = 0;
260
+ const peek = () => tokens[index];
261
+ const take = (expected) => {
262
+ const token = tokens[index];
263
+ if (token === void 0 || expected !== void 0 && token !== expected) throw new Error(`expression "${source}": expected ${expected ?? "a value"} at ${token ?? "end"}`);
264
+ index += 1;
265
+ return token;
266
+ };
267
+ const primary = () => {
268
+ const token = take();
269
+ if (/^(?:\d|\.\d)/u.test(token)) {
270
+ const value = Number(token);
271
+ return () => value;
272
+ }
273
+ if (token === "(") {
274
+ const inner = sum();
275
+ take(")");
276
+ return inner;
277
+ }
278
+ if (token === "-") {
279
+ const inner = power();
280
+ return (t) => -inner(t);
281
+ }
282
+ if (token === "+") return power();
283
+ const name = token.toLowerCase();
284
+ if (name === "t") return (t) => t;
285
+ if (Object.hasOwn(CONSTANTS, name)) {
286
+ const value = CONSTANTS[name];
287
+ return () => value;
288
+ }
289
+ if (Object.hasOwn(FUNCTIONS, name)) {
290
+ const fn = FUNCTIONS[name];
291
+ take("(");
292
+ const args = [sum()];
293
+ while (peek() === ",") {
294
+ take(",");
295
+ args.push(sum());
296
+ }
297
+ take(")");
298
+ return (t) => fn(...args.map((arg) => arg(t)));
299
+ }
300
+ throw new Error(`expression "${source}": unknown name or symbol "${token}"`);
301
+ };
302
+ const power = () => {
303
+ const base = primary();
304
+ if (peek() === "^" || peek() === "**") {
305
+ take();
306
+ const exponent = power();
307
+ return (t) => base(t) ** exponent(t);
308
+ }
309
+ return base;
310
+ };
311
+ const product = () => {
312
+ let left = power();
313
+ while (peek() === "*" || peek() === "/") {
314
+ const op = take();
315
+ const right = power();
316
+ const previous = left;
317
+ left = op === "*" ? (t) => previous(t) * right(t) : (t) => previous(t) / right(t);
318
+ }
319
+ return left;
320
+ };
321
+ const sum = () => {
322
+ let left = product();
323
+ while (peek() === "+" || peek() === "-") {
324
+ const op = take();
325
+ const right = product();
326
+ const previous = left;
327
+ left = op === "+" ? (t) => previous(t) + right(t) : (t) => previous(t) - right(t);
328
+ }
329
+ return left;
330
+ };
331
+ const expression = sum();
332
+ if (index !== tokens.length) throw new Error(`expression "${source}": unexpected "${tokens[index]}"`);
333
+ return expression;
334
+ }
335
+ const CURVE_STEPS = 24;
336
+ function arcPoints(from, rx, ry, rotation, large, sweep, to) {
337
+ if (rx === 0 || ry === 0) return [to];
338
+ const phi = rotation * Math.PI / 180;
339
+ const cos = Math.cos(phi);
340
+ const sin = Math.sin(phi);
341
+ const dx = (from.x - to.x) / 2;
342
+ const dy = (from.y - to.y) / 2;
343
+ const x1 = cos * dx + sin * dy;
344
+ const y1 = -sin * dx + cos * dy;
345
+ let a = Math.abs(rx);
346
+ let b = Math.abs(ry);
347
+ const lambda = x1 * x1 / (a * a) + y1 * y1 / (b * b);
348
+ if (lambda > 1) {
349
+ a *= Math.sqrt(lambda);
350
+ b *= Math.sqrt(lambda);
351
+ }
352
+ const numerator = a * a * b * b - a * a * y1 * y1 - b * b * x1 * x1;
353
+ const factor = (large === sweep ? -1 : 1) * Math.sqrt(Math.max(0, numerator / (a * a * y1 * y1 + b * b * x1 * x1)));
354
+ const cx1 = factor * (a * y1) / b;
355
+ const cy1 = factor * -(b * x1) / a;
356
+ const cx = cos * cx1 - sin * cy1 + (from.x + to.x) / 2;
357
+ const cy = sin * cx1 + cos * cy1 + (from.y + to.y) / 2;
358
+ const angle = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
359
+ const start = angle(1, 0, (x1 - cx1) / a, (y1 - cy1) / b);
360
+ let delta = angle((x1 - cx1) / a, (y1 - cy1) / b, (-x1 - cx1) / a, (-y1 - cy1) / b);
361
+ if (!sweep && delta > 0) delta -= Math.PI * 2;
362
+ if (sweep && delta < 0) delta += Math.PI * 2;
363
+ const points = [];
364
+ for (let step = 1; step <= CURVE_STEPS; step += 1) {
365
+ const theta = start + delta * step / CURVE_STEPS;
366
+ points.push({
367
+ x: cos * a * Math.cos(theta) - sin * b * Math.sin(theta) + cx,
368
+ y: sin * a * Math.cos(theta) + cos * b * Math.sin(theta) + cy
369
+ });
370
+ }
371
+ return points;
372
+ }
373
+ /**
374
+ * Sample an SVG path (M L H V C S Q T A Z, absolute and relative) into a polyline.
375
+ * @throws an Error for unsupported commands or malformed numbers.
376
+ */
377
+ function samplePath(d) {
378
+ const tokens = d.match(/[a-z]|[-+]?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/giu) ?? [];
379
+ if (tokens.join("").length < d.replace(/[\s,]/gu, "").length) throw new Error("path contains unsupported characters");
380
+ const points = [];
381
+ let index = 0;
382
+ let command = "";
383
+ let current = {
384
+ x: 0,
385
+ y: 0
386
+ };
387
+ let start = {
388
+ x: 0,
389
+ y: 0
390
+ };
391
+ let control = null;
392
+ const number = () => {
393
+ const token = tokens[index];
394
+ if (token === void 0 || /^[a-z]$/iu.test(token)) throw new Error(`path command ${command} is missing a number`);
395
+ index += 1;
396
+ return Number(token);
397
+ };
398
+ const flag = () => number() !== 0;
399
+ while (index < tokens.length) {
400
+ if (/^[a-z]$/iu.test(tokens[index])) command = tokens[index++];
401
+ else if (command === "") throw new Error("path must start with a command");
402
+ const relative = command === command.toLowerCase();
403
+ const base = relative ? current : {
404
+ x: 0,
405
+ y: 0
406
+ };
407
+ const at = (x, y) => ({
408
+ x: base.x + x,
409
+ y: base.y + y
410
+ });
411
+ switch (command.toUpperCase()) {
412
+ case "M":
413
+ current = at(number(), number());
414
+ start = current;
415
+ points.push(current);
416
+ control = null;
417
+ command = relative ? "l" : "L";
418
+ break;
419
+ case "L":
420
+ current = at(number(), number());
421
+ points.push(current);
422
+ control = null;
423
+ break;
424
+ case "H":
425
+ current = {
426
+ x: (relative ? current.x : 0) + number(),
427
+ y: current.y
428
+ };
429
+ points.push(current);
430
+ control = null;
431
+ break;
432
+ case "V":
433
+ current = {
434
+ x: current.x,
435
+ y: (relative ? current.y : 0) + number()
436
+ };
437
+ points.push(current);
438
+ control = null;
439
+ break;
440
+ case "Z":
441
+ current = start;
442
+ points.push(current);
443
+ control = null;
444
+ break;
445
+ case "C":
446
+ case "S": {
447
+ const first = command.toUpperCase() === "C" ? at(number(), number()) : control === null ? current : {
448
+ x: 2 * current.x - control.x,
449
+ y: 2 * current.y - control.y
450
+ };
451
+ const second = at(number(), number());
452
+ const end = at(number(), number());
453
+ for (let step = 1; step <= CURVE_STEPS; step += 1) {
454
+ const s = step / CURVE_STEPS;
455
+ const r = 1 - s;
456
+ points.push({
457
+ x: r ** 3 * current.x + 3 * r * r * s * first.x + 3 * r * s * s * second.x + s ** 3 * end.x,
458
+ y: r ** 3 * current.y + 3 * r * r * s * first.y + 3 * r * s * s * second.y + s ** 3 * end.y
459
+ });
460
+ }
461
+ control = second;
462
+ current = end;
463
+ break;
464
+ }
465
+ case "Q":
466
+ case "T": {
467
+ const middle = command.toUpperCase() === "Q" ? at(number(), number()) : control === null ? current : {
468
+ x: 2 * current.x - control.x,
469
+ y: 2 * current.y - control.y
470
+ };
471
+ const end = at(number(), number());
472
+ for (let step = 1; step <= CURVE_STEPS; step += 1) {
473
+ const s = step / CURVE_STEPS;
474
+ const r = 1 - s;
475
+ points.push({
476
+ x: r * r * current.x + 2 * r * s * middle.x + s * s * end.x,
477
+ y: r * r * current.y + 2 * r * s * middle.y + s * s * end.y
478
+ });
479
+ }
480
+ control = middle;
481
+ current = end;
482
+ break;
483
+ }
484
+ case "A": {
485
+ const rx = number();
486
+ const ry = number();
487
+ const rotation = number();
488
+ const large = flag();
489
+ const sweep = flag();
490
+ const end = at(number(), number());
491
+ points.push(...arcPoints(current, rx, ry, rotation, large, sweep, end));
492
+ current = end;
493
+ control = null;
494
+ break;
495
+ }
496
+ default: throw new Error(`unsupported path command ${command}`);
497
+ }
498
+ if (command.toUpperCase() === "Z" && index < tokens.length && !/^[a-z]$/iu.test(tokens[index])) throw new Error("numbers after Z need a command");
499
+ }
500
+ if (points.length < 2) throw new Error("path needs at least two points");
501
+ if (points.some((point) => !Number.isFinite(point.x) || !Number.isFinite(point.y))) throw new Error("path has non-finite coordinates");
502
+ const lengths = [0];
503
+ for (let i = 1; i < points.length; i += 1) lengths.push(lengths[i - 1] + Math.hypot(points[i].x - points[i - 1].x, points[i].y - points[i - 1].y));
504
+ return {
505
+ points,
506
+ lengths,
507
+ total: lengths.at(-1)
508
+ };
509
+ }
510
+ /** Position and tangent angle (degrees) at a fraction of a sampled path's length. */
511
+ function pointAlong(path, fraction) {
512
+ const target = Math.min(1, Math.max(0, fraction)) * path.total;
513
+ let i = 1;
514
+ while (i < path.points.length - 1 && path.lengths[i] < target) i += 1;
515
+ const a = path.points[i - 1];
516
+ const b = path.points[i];
517
+ const span = path.lengths[i] - path.lengths[i - 1];
518
+ const local = span === 0 ? 0 : (target - path.lengths[i - 1]) / span;
519
+ return {
520
+ x: a.x + (b.x - a.x) * local,
521
+ y: a.y + (b.y - a.y) * local,
522
+ angle: Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI
523
+ };
524
+ }
525
+ const BASE = {
526
+ dx: 0,
527
+ dy: 0,
528
+ scale: 1,
529
+ rotate: 0,
530
+ opacity: 1,
531
+ clip: 0,
532
+ glow: 0,
533
+ slideX: 0,
534
+ slideY: 0
535
+ };
536
+ function ease(kind, p) {
537
+ if (kind === "ease-in") return p ** 3;
538
+ if (kind === "ease-out") return 1 - (1 - p) ** 3;
539
+ if (kind === "ease-in-out") return p < .5 ? 4 * p ** 3 : 1 - (-2 * p + 2) ** 3 / 2;
540
+ return p;
541
+ }
542
+ const lerp = (a, b, p) => a + (b - a) * p;
543
+ function direction(value) {
544
+ if (value === "left") return {
545
+ x: -1,
546
+ y: 0
547
+ };
548
+ if (value === "right") return {
549
+ x: 1,
550
+ y: 0
551
+ };
552
+ if (value === "up") return {
553
+ x: 0,
554
+ y: -1
555
+ };
556
+ return {
557
+ x: 0,
558
+ y: 1
559
+ };
560
+ }
561
+ function valueAt(segments, from, base, t) {
562
+ let active;
563
+ for (const segment of segments) {
564
+ if (segment.start > t) break;
565
+ active = segment;
566
+ }
567
+ if (active === void 0) return segments[0]?.before ?? base;
568
+ return active.at(t, from.get(active) ?? base);
569
+ }
570
+ /**
571
+ * Validate and compile a timeline against the current board.
572
+ * @param spec - parsed timeline spec.
573
+ * @param targets - current board element envelopes.
574
+ * @returns the compiled timeline.
575
+ * @throws an Error describing the first invalid track.
576
+ */
577
+ function compileTimeline(spec, targets) {
578
+ const byId = new Map(targets.map((target) => [target.id, target]));
579
+ const pending = /* @__PURE__ */ new Map();
580
+ const clipSides = /* @__PURE__ */ new Map();
581
+ spec.tracks.forEach((track, index) => {
582
+ const label = `track ${index + 1} (${track.verb} ${track.target})`;
583
+ const target = byId.get(track.target);
584
+ if (target === void 0) throw new Error(`${label}: no board element with id "${track.target}"`);
585
+ const cx = target.x + target.w / 2;
586
+ const cy = target.y + target.h / 2;
587
+ const start = track.startMs ?? 0;
588
+ const end = track.endMs ?? spec.durationMs;
589
+ if (track.verb !== "keyframes" && (end <= start || end > spec.durationMs)) throw new Error(`${label}: needs 0 <= startMs < endMs <= durationMs`);
590
+ const easing = track.easing ?? (track.verb === "follow" || track.verb === "parametric" ? "linear" : "ease-in-out");
591
+ const progress = (t) => ease(easing, Math.min(1, Math.max(0, (t - start) / (end - start))));
592
+ const props = pending.get(track.target) ?? /* @__PURE__ */ new Map();
593
+ pending.set(track.target, props);
594
+ const add = (prop, segment) => {
595
+ props.set(prop, [...props.get(prop) ?? [], segment]);
596
+ };
597
+ const require = (value, name) => {
598
+ if (value === void 0) throw new Error(`${label}: ${track.verb} requires ${name}`);
599
+ return value;
600
+ };
601
+ switch (track.verb) {
602
+ case "appear":
603
+ case "disappear": {
604
+ const showing = track.verb === "appear";
605
+ add("opacity", {
606
+ start,
607
+ end,
608
+ ...showing ? { before: 0 } : {},
609
+ at: (t, from) => showing ? progress(t) : lerp(from, 0, progress(t))
610
+ });
611
+ if (track.direction !== void 0) {
612
+ const unit = direction(track.direction);
613
+ add("slideX", {
614
+ start,
615
+ end,
616
+ at: (t) => (showing ? -unit.x * (1 - progress(t)) : unit.x * progress(t)) * SLIDE_DISTANCE
617
+ });
618
+ add("slideY", {
619
+ start,
620
+ end,
621
+ at: (t) => (showing ? -unit.y * (1 - progress(t)) : unit.y * progress(t)) * SLIDE_DISTANCE
622
+ });
623
+ }
624
+ break;
625
+ }
626
+ case "move": {
627
+ const to = require(track.to, "to");
628
+ add("dx", {
629
+ start,
630
+ end,
631
+ at: (t, from) => lerp(from, to.x - cx, progress(t))
632
+ });
633
+ add("dy", {
634
+ start,
635
+ end,
636
+ at: (t, from) => lerp(from, to.y - cy, progress(t))
637
+ });
638
+ break;
639
+ }
640
+ case "scale": {
641
+ const factor = require(track.scale, "scale");
642
+ add("scale", {
643
+ start,
644
+ end,
645
+ at: (t, from) => lerp(from, factor, progress(t))
646
+ });
647
+ break;
648
+ }
649
+ case "rotate": {
650
+ const degrees = require(track.degrees, "degrees");
651
+ add("rotate", {
652
+ start,
653
+ end,
654
+ at: (t, from) => from + degrees * progress(t)
655
+ });
656
+ break;
657
+ }
658
+ case "highlight":
659
+ add("glow", {
660
+ start,
661
+ end,
662
+ at: (t) => t >= end ? 0 : Math.sin(Math.PI * Math.min(1, Math.max(0, (t - start) / (end - start))))
663
+ });
664
+ break;
665
+ case "reveal": {
666
+ const side = track.direction ?? "right";
667
+ clipSides.set(track.target, side === "right" ? "right" : side === "left" ? "left" : side === "up" ? "top" : "bottom");
668
+ add("clip", {
669
+ start,
670
+ end,
671
+ before: 100,
672
+ at: (t) => 100 * (1 - progress(t))
673
+ });
674
+ break;
675
+ }
676
+ case "follow": {
677
+ let path;
678
+ try {
679
+ path = samplePath(require(track.path, "path"));
680
+ } catch (error) {
681
+ throw new Error(`${label}: ${error instanceof Error ? error.message : String(error)}`);
682
+ }
683
+ add("dx", {
684
+ start,
685
+ end,
686
+ at: (t) => pointAlong(path, progress(t)).x - cx
687
+ });
688
+ add("dy", {
689
+ start,
690
+ end,
691
+ at: (t) => pointAlong(path, progress(t)).y - cy
692
+ });
693
+ if (track.orient === true) add("rotate", {
694
+ start,
695
+ end,
696
+ at: (t) => pointAlong(path, progress(t)).angle
697
+ });
698
+ break;
699
+ }
700
+ case "parametric": {
701
+ let x;
702
+ let y;
703
+ try {
704
+ x = compileExpression(require(track.x, "x"));
705
+ y = compileExpression(require(track.y, "y"));
706
+ } catch (error) {
707
+ throw new Error(`${label}: ${error instanceof Error ? error.message : String(error)}`);
708
+ }
709
+ const [t0, t1] = track.t ?? [0, 1];
710
+ const param = (t) => lerp(t0, t1, Math.min(1, Math.max(0, (t - start) / (end - start))));
711
+ for (let step = 0; step <= 16; step += 1) {
712
+ const value = lerp(t0, t1, step / 16);
713
+ if (!Number.isFinite(x(value)) || !Number.isFinite(y(value))) throw new Error(`${label}: x or y is not finite at t = ${value}`);
714
+ }
715
+ add("dx", {
716
+ start,
717
+ end,
718
+ at: (t) => x(param(t)) - cx
719
+ });
720
+ add("dy", {
721
+ start,
722
+ end,
723
+ at: (t) => y(param(t)) - cy
724
+ });
725
+ break;
726
+ }
727
+ case "keyframes": {
728
+ const frames = [...require(track.frames, "frames")].sort((a, b) => a.atMs - b.atMs);
729
+ if (frames.some((frame) => frame.atMs > spec.durationMs)) throw new Error(`${label}: frame atMs exceeds durationMs`);
730
+ const channels = [
731
+ ["dx", (frame) => frame.x === void 0 ? void 0 : frame.x - cx],
732
+ ["dy", (frame) => frame.y === void 0 ? void 0 : frame.y - cy],
733
+ ["opacity", (frame) => frame.opacity],
734
+ ["scale", (frame) => frame.scale],
735
+ ["rotate", (frame) => frame.rotate]
736
+ ];
737
+ for (const [prop, read] of channels) {
738
+ const stops = frames.flatMap((frame) => {
739
+ const value = read(frame);
740
+ return value === void 0 ? [] : [{
741
+ at: frame.atMs,
742
+ value
743
+ }];
744
+ });
745
+ if (stops.length === 0) continue;
746
+ const first = stops[0];
747
+ const last = stops.at(-1);
748
+ add(prop, {
749
+ start: first.at,
750
+ end: Math.max(first.at + 1, last.at),
751
+ at: (t) => {
752
+ if (t >= last.at) return last.value;
753
+ let i = 1;
754
+ while (i < stops.length - 1 && stops[i].at <= t) i += 1;
755
+ const a = stops[i - 1];
756
+ const b = stops[i];
757
+ return lerp(a.value, b.value, ease(easing, b.at === a.at ? 1 : Math.min(1, Math.max(0, (t - a.at) / (b.at - a.at)))));
758
+ }
759
+ });
760
+ }
761
+ break;
762
+ }
763
+ default: return track.verb;
764
+ }
765
+ });
766
+ const elements = /* @__PURE__ */ new Map();
767
+ for (const [id, props] of pending) {
768
+ const segments = /* @__PURE__ */ new Map();
769
+ const from = /* @__PURE__ */ new Map();
770
+ for (const [prop, list] of props) {
771
+ const sorted = [...list].sort((a, b) => a.start - b.start);
772
+ sorted.forEach((segment, index) => {
773
+ from.set(segment, valueAt(sorted.slice(0, index), from, BASE[prop], segment.start));
774
+ });
775
+ segments.set(prop, sorted);
776
+ }
777
+ elements.set(id, {
778
+ segments,
779
+ from,
780
+ clipSide: clipSides.get(id) ?? "right"
781
+ });
782
+ }
783
+ return {
784
+ durationMs: spec.durationMs,
785
+ loop: spec.loop ?? false,
786
+ beats: [...spec.beats ?? []].sort((a, b) => a.atMs - b.atMs),
787
+ elements
788
+ };
789
+ }
155
790
  /** Maximum combined UTF-8 element-content bytes retained on one board. */
156
791
  const BOARD_MAX_CONTENT_BYTES = 5e5;
157
792
  /** Element kinds the blackboard renders safely on the chalk canvas. */
@@ -398,7 +1033,9 @@ const boardSnapshotSchema = z.object({
398
1033
  version: z.literal(4),
399
1034
  revision: z.number().int().nonnegative(),
400
1035
  elements: z.array(boardElementStateSchema).max(50),
401
- invalid: z.number().int().nonnegative()
1036
+ invalid: z.number().int().nonnegative(),
1037
+ /** Latest animation timeline; absent in snapshots from earlier versions. */
1038
+ timeline: boardTimelineSchema.nullable().optional()
402
1039
  }).strict();
403
1040
  Object.freeze({
404
1041
  version: 4,
@@ -412,7 +1049,9 @@ Object.freeze({
412
1049
  const boardCaptureJobSchema = z.object({
413
1050
  id: z.string().min(1),
414
1051
  sessionId: z.string().min(1),
415
- board: boardSnapshotSchema
1052
+ board: boardSnapshotSchema,
1053
+ /** Render the board's timeline frozen at this time instead of its resting state. */
1054
+ atMs: z.number().int().min(0).max(ANIMATION_MAX_DURATION_MS).optional()
416
1055
  }).strict();
417
1056
  //#endregion
418
1057
  //#region lib/types/learning-example.js
@@ -712,7 +1351,13 @@ const citeCiterSettingsSchema = z.object({
712
1351
  "workspace-write",
713
1352
  "danger-full-access"
714
1353
  ]).optional(),
715
- learningRoute: z.boolean().optional()
1354
+ learningRoute: z.boolean().optional(),
1355
+ autoReadSource: z.boolean().optional(),
1356
+ workspaceChanges: z.enum([
1357
+ "off",
1358
+ "summary",
1359
+ "diff"
1360
+ ]).optional()
716
1361
  }).strict();
717
1362
  /** Settings used before an optional DSH settings provider becomes available. */
718
1363
  const DEFAULT_CITECITER_SETTINGS = Object.freeze({
@@ -724,6 +1369,8 @@ const DEFAULT_CITECITER_SETTINGS = Object.freeze({
724
1369
  activeRecall: false,
725
1370
  defaultPermission: "read-only",
726
1371
  learningRoute: false,
1372
+ autoReadSource: false,
1373
+ workspaceChanges: "summary",
727
1374
  updateNotifications: true,
728
1375
  selectionToolbar: true,
729
1376
  actionDelivery: "send",
@@ -1602,4 +2249,4 @@ function waitForCaller(operation, signal) {
1602
2249
  });
1603
2250
  }
1604
2251
  //#endregion
1605
- export { applyBoardOps as A, questionReplyText as C, learningCardsInputSchema as D, LEARNING_CARD_FIELD_DESCRIPTIONS as E, DRAFT_CHUNK_BYTES as F, EMPTY_DRAFT_STATE as I, draftFileSchema as L, EMPTY_QUESTION_DRAFT_STATE as M, questionDraftKeySchema as N, LEARNING_EXAMPLE_PARAMETER as O, questionDraftRecordSchema as P, draftStateSchema as R, topicSummarySchema as S, DEFAULT_WHEEL_SLOTS as T, readCiteCiterSettings as _, DEFAULT_CITECITER_SETTINGS as a, topicMetadataSchema as b, citationDraftSchema as c, citeCiterRequestSchema as d, citeCiterResponseSchema as f, parseTopicMetadataFile as g, documentSummarySchema as h, CITECITER_SETTINGS_NAMESPACE as i, boardBatchSchema as j, EMPTY_BOARD_STATE as k, citationRecordSchema as l, documentEvidenceClaimSchema as m, updateCheckErrorCodeSchema as n, TUTOR_SECTION_NAME as o, documentContentSchema as p, updateCheckResponseSchema as r, canonicalCitationIdentity as s, UpdateChecker as t, citationSelectionClaimSchema as u, toolEvidenceClaimSchema as v, readQuestionReply as w, topicSnapshotSchema as x, topicDisplayTitle as y, subtractSubmitted as z };
2252
+ export { applyBoardOps as A, draftFileSchema as B, questionReplyText as C, learningCardsInputSchema as D, LEARNING_CARD_FIELD_DESCRIPTIONS as E, EMPTY_QUESTION_DRAFT_STATE as F, subtractSubmitted as H, questionDraftKeySchema as I, questionDraftRecordSchema as L, ANIMATION_MAX_DURATION_MS as M, animationSpecSchema as N, LEARNING_EXAMPLE_PARAMETER as O, compileTimeline as P, DRAFT_CHUNK_BYTES as R, topicSummarySchema as S, DEFAULT_WHEEL_SLOTS as T, draftStateSchema as V, readCiteCiterSettings as _, DEFAULT_CITECITER_SETTINGS as a, topicMetadataSchema as b, citationDraftSchema as c, citeCiterRequestSchema as d, citeCiterResponseSchema as f, parseTopicMetadataFile as g, documentSummarySchema as h, CITECITER_SETTINGS_NAMESPACE as i, boardBatchSchema as j, EMPTY_BOARD_STATE as k, citationRecordSchema as l, documentEvidenceClaimSchema as m, updateCheckErrorCodeSchema as n, TUTOR_SECTION_NAME as o, documentContentSchema as p, updateCheckResponseSchema as r, canonicalCitationIdentity as s, UpdateChecker as t, citationSelectionClaimSchema as u, toolEvidenceClaimSchema as v, readQuestionReply as w, topicSnapshotSchema as x, topicDisplayTitle as y, EMPTY_DRAFT_STATE as z };