@kubohiroya/turbowarp-asset-cache 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,6 +4,13 @@ A browser-local image, audio, and runtime-text asset cache for TurboWarp project
4
4
 
5
5
  Asset Cache intentionally keeps the legacy `isLoaded` and cache lifecycle semantics. It is not a general server object store: every block is marked `server.supported: false` in the format 2 manifest. Use [`@kubohiroya/turbowarp-kvs`](https://github.com/kubohiroya/turbowarp-kvs) for portable namespace/key persistence and server lowering.
6
6
 
7
+ `@kubohiroya/turbowarp-asset-manager` is deprecated. Composition consumers can replace its import
8
+ path with `@kubohiroya/turbowarp-asset-cache/composition` for asset registration, rendering,
9
+ playback, DOM image resources, and verified remote-cache APIs. Import binary object, bundle, and
10
+ session-backing APIs directly from `@kubohiroya/turbowarp-kvs`. Standalone projects must also
11
+ replace the extension ID `kubohiroyaassetmanager` with `kubohiroyaassetcache`; published historical
12
+ SB3 artifacts should remain pinned to their original extension.
13
+
7
14
  ## User Guide
8
15
 
9
16
  For setup, safety notes, recipes, and the illustrated block guide, see the [English user guide](https://kubohiroya.github.io/turbowarp-asset-cache/) or [Japanese user guide](https://kubohiroya.github.io/turbowarp-asset-cache/ja/).
@@ -41,13 +48,13 @@ The built JavaScript file is committed to this repository so users do not need t
41
48
  The versioned npm package contains the same reviewed build:
42
49
 
43
50
  ```bash
44
- pnpm add --save-exact @kubohiroya/turbowarp-asset-cache@0.2.0
51
+ pnpm add --save-exact @kubohiroya/turbowarp-asset-cache@0.2.2
45
52
  ```
46
53
 
47
54
  Load `node_modules/@kubohiroya/turbowarp-asset-cache/dist/asset-cache.js`, or use the version-pinned CDN URL:
48
55
 
49
56
  ```text
50
- https://cdn.jsdelivr.net/npm/@kubohiroya/turbowarp-asset-cache@0.2.0/dist/asset-cache.js
57
+ https://cdn.jsdelivr.net/npm/@kubohiroya/turbowarp-asset-cache@0.2.2/dist/asset-cache.js
51
58
  ```
52
59
 
53
60
  ## Quick Start
@@ -287,7 +287,404 @@
287
287
  ]
288
288
  };
289
289
  //#endregion
290
- //#region node_modules/.pnpm/@kubohiroya+turbowarp-named-data@0.1.0/node_modules/@kubohiroya/turbowarp-named-data/dist/composition.js
290
+ //#region node_modules/.pnpm/@kubohiroya+turbowarp-extension-data-editor@0.2.1_react@18.3.1/node_modules/@kubohiroya/turbowarp-extension-data-editor/dist/fixed-input-schema.js
291
+ var numberToken = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/u;
292
+ var invalidNumber = Object.freeze({
293
+ kind: "invalid",
294
+ code: "fixed-number-input-invalid"
295
+ });
296
+ /**
297
+ * Parse a NEW numeric draft according to NP-finite-new-input-v1.
298
+ * Never apply this to opening/no-op raw values or use it as an owner validator.
299
+ * Keep the validated token so negative zero, precision and exponent spelling survive.
300
+ */
301
+ function parseFixedNumberInput(input) {
302
+ if (typeof input === "number") {
303
+ if (!Number.isFinite(input)) return invalidNumber;
304
+ return Object.freeze({
305
+ kind: "valid",
306
+ token: Object.is(input, -0) ? "-0" : String(input),
307
+ value: input
308
+ });
309
+ }
310
+ if (typeof input !== "string") return invalidNumber;
311
+ const token = input.trim();
312
+ if (!numberToken.test(token)) return invalidNumber;
313
+ const value = Number(token);
314
+ return Number.isFinite(value) ? Object.freeze({
315
+ kind: "valid",
316
+ token,
317
+ value
318
+ }) : invalidNumber;
319
+ }
320
+ var block_editor_fixed_inputs_schema_default = {
321
+ $schema: "https://json-schema.org/draft/2020-12/schema",
322
+ $id: "https://kubohiroya.github.io/turbowarp-asset-cache/schemas/block-editor-fixed-inputs.schema.json",
323
+ $defs: {
324
+ "setTextStyle": {
325
+ "type": "object",
326
+ "properties": {
327
+ "PROPERTY": {
328
+ "type": "string",
329
+ "enum": [
330
+ "animation",
331
+ "font",
332
+ "color",
333
+ "width",
334
+ "align"
335
+ ]
336
+ },
337
+ "VALUE": { "type": "string" }
338
+ },
339
+ "additionalProperties": false
340
+ },
341
+ "animation": {
342
+ "type": "string",
343
+ "enum": [
344
+ "",
345
+ "none",
346
+ "type",
347
+ "rainbow",
348
+ "zoom",
349
+ "shake"
350
+ ]
351
+ },
352
+ "font": { "type": "string" },
353
+ "color": {
354
+ "type": "string",
355
+ "pattern": "^(?:|#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6})$"
356
+ },
357
+ "width": {
358
+ "type": "string",
359
+ "$comment": "Empty/trimmed empty resets; otherwise normalizeTextStyleValue requires Number(value) finite and positive. Preserve the existing STRING grammar, including hexadecimal and leading plus."
360
+ },
361
+ "align": {
362
+ "type": "string",
363
+ "enum": [
364
+ "",
365
+ "left",
366
+ "center",
367
+ "right"
368
+ ]
369
+ },
370
+ "loadingCostumeAt": {
371
+ "type": "object",
372
+ "properties": { "INDEX": { "type": "number" } },
373
+ "additionalProperties": false
374
+ }
375
+ }
376
+ };
377
+ //#endregion
378
+ //#region src/fixed-input-schema.ts
379
+ /** Canonical owner schema fragments; no network resources or runtime providers. */
380
+ function fixedInputBundle(fragment, schema) {
381
+ const source = {
382
+ repository: "turbowarp-asset-cache",
383
+ path: "schemas/block-editor-fixed-inputs.schema.json",
384
+ symbol: {
385
+ "setTextStyle-PROPERTY": "#/$defs/setTextStyle/properties/PROPERTY",
386
+ "setTextStyle-VALUE": "#/$defs/setTextStyle/properties/VALUE",
387
+ "loadingCostumeAt-INDEX": "#/$defs/loadingCostumeAt/properties/INDEX"
388
+ }[fragment] ?? `#/$defs/${fragment}`
389
+ };
390
+ return {
391
+ identity: {
392
+ ownerId: "turbowarp-asset-cache",
393
+ contractId: "fixed-inputs",
394
+ revision: "1",
395
+ variantId: fragment
396
+ },
397
+ dialect: "2020-12",
398
+ root: {
399
+ retrievalUri: block_editor_fixed_inputs_schema_default.$id,
400
+ document: schema,
401
+ artifact: {
402
+ role: "canonical-schema",
403
+ source
404
+ }
405
+ },
406
+ resources: [],
407
+ provenance: {
408
+ canonicalSources: [
409
+ source,
410
+ {
411
+ repository: "turbowarp-asset-cache",
412
+ path: "src/text-style.ts",
413
+ symbol: "normalizeTextStyleProperty / normalizeTextStyleValue"
414
+ },
415
+ {
416
+ repository: "turbowarp-asset-cache",
417
+ path: "src/extension.ts",
418
+ symbol: "getInfo / loadingCostumeAt"
419
+ }
420
+ ],
421
+ generatedFiles: [],
422
+ referenceSchemas: [],
423
+ generationSources: []
424
+ }
425
+ };
426
+ }
427
+ var fixedSchemas = block_editor_fixed_inputs_schema_default.$defs;
428
+ //#endregion
429
+ //#region src/text-style.ts
430
+ var TEXT_RUNTIME_NAMESPACE = "text";
431
+ var TEXT_STYLE_RUNTIME_NAMESPACE = "textStyle";
432
+ var TEXT_STYLE_PROPERTIES = [
433
+ "animation",
434
+ "font",
435
+ "color",
436
+ "width",
437
+ "align"
438
+ ];
439
+ var DEFAULT_STAGE_WIDTH = 480;
440
+ var DEFAULT_FONT = "Handwriting";
441
+ var DEFAULT_COLOR = "#ffffff";
442
+ var DEFAULT_ALIGNMENT = "center";
443
+ var DEFAULT_OUTLINE_COLOR = "#000000";
444
+ function textRuntimeVariableName(name) {
445
+ return `${TEXT_RUNTIME_NAMESPACE}:${name}`;
446
+ }
447
+ function textStyleRuntimeVariableName(name, property) {
448
+ return `${TEXT_STYLE_RUNTIME_NAMESPACE}:${name}:${property}`;
449
+ }
450
+ function normalizeTextStyleProperty(value) {
451
+ const property = String(value ?? "").trim().toLowerCase();
452
+ if (TEXT_STYLE_PROPERTIES.includes(property)) return property;
453
+ throw new Error(`Unknown text style property: ${property || "(empty)"}`);
454
+ }
455
+ /**
456
+ * Normalize a DSL-facing style value for runtime-variable storage.
457
+ * An empty value intentionally resets the property to its default.
458
+ */
459
+ function normalizeTextStyleValue(property, value) {
460
+ const raw = String(value ?? "").trim();
461
+ if (!raw) return "";
462
+ switch (property) {
463
+ case "animation": {
464
+ const animation = raw.toLowerCase() === "typing" ? "type" : raw.toLowerCase();
465
+ if (animation === "none" || animation === "type" || animation === "rainbow" || animation === "zoom" || animation === "shake") return animation;
466
+ throw new Error(`Invalid text animation: ${raw}`);
467
+ }
468
+ case "font": return raw;
469
+ case "color": {
470
+ if (/^#[0-9a-f]{6}$/i.test(raw)) return raw.toLowerCase();
471
+ const shortColor = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(raw);
472
+ if (shortColor) return `#${shortColor[1]}${shortColor[1]}${shortColor[2]}${shortColor[2]}${shortColor[3]}${shortColor[3]}`.toLowerCase();
473
+ throw new Error(`Invalid text color: ${raw}`);
474
+ }
475
+ case "width": {
476
+ const width = Number(raw);
477
+ if (!Number.isFinite(width) || width <= 0) throw new Error(`Text width must be a positive number: ${raw}`);
478
+ return String(width);
479
+ }
480
+ case "align": {
481
+ const align = raw.toLowerCase();
482
+ if (align === "left" || align === "center" || align === "right") return align;
483
+ throw new Error(`Invalid text alignment: ${raw}`);
484
+ }
485
+ }
486
+ }
487
+ function resolveTextStyle(name, stageWidth, getRuntimeVariable) {
488
+ const width = Number(stageWidth);
489
+ const defaults = {
490
+ animation: "none",
491
+ font: DEFAULT_FONT,
492
+ color: DEFAULT_COLOR,
493
+ width: Number.isFinite(width) && width > 0 ? width : DEFAULT_STAGE_WIDTH,
494
+ align: DEFAULT_ALIGNMENT
495
+ };
496
+ const read = (property) => normalizeTextStyleValue(property, getRuntimeVariable(textStyleRuntimeVariableName(name, property)));
497
+ const animation = read("animation");
498
+ const font = read("font");
499
+ const color = read("color");
500
+ const configuredWidth = read("width");
501
+ const align = read("align");
502
+ return {
503
+ animation: animation ? animation : defaults.animation,
504
+ font: font || defaults.font,
505
+ color: color || defaults.color,
506
+ width: configuredWidth ? Number(configuredWidth) : defaults.width,
507
+ align: align ? align : defaults.align
508
+ };
509
+ }
510
+ //#endregion
511
+ //#region src/fixed-input-editors.ts
512
+ var extensionId = "kubohiroyaassetcache";
513
+ var choices = (values) => values.map((value) => ({ value }));
514
+ var context = () => ({
515
+ consumerId: "turbowarp-asset-cache",
516
+ nowMs: 0,
517
+ settings: null
518
+ });
519
+ var string = (value) => {
520
+ if (typeof value !== "string") throw new TypeError("Expected a string literal.");
521
+ return value;
522
+ };
523
+ var issue = () => [{ message: "Invalid asset style value." }];
524
+ /** New edits preserve the candidate token; existing aliases are display projections only. */
525
+ function styleBinding(property) {
526
+ const values = property === "animation" ? [
527
+ "",
528
+ "none",
529
+ "type",
530
+ "rainbow",
531
+ "zoom",
532
+ "shake"
533
+ ] : property === "align" ? [
534
+ "",
535
+ "left",
536
+ "center",
537
+ "right"
538
+ ] : void 0;
539
+ return {
540
+ argument: "VALUE",
541
+ schemaBundle: fixedInputBundle(property, fixedSchemas[property]),
542
+ control: {
543
+ kind: values ? "enum" : property === "color" ? "color" : property === "width" ? "number" : "text",
544
+ reset: "",
545
+ ...values ? { choices: choices(values) } : {}
546
+ },
547
+ read(value) {
548
+ return normalizeTextStyleValue(property, string(value));
549
+ },
550
+ encode(value) {
551
+ const raw = string(value);
552
+ if (property !== "width" || raw === "") return raw;
553
+ normalizeTextStyleValue(property, raw);
554
+ return raw;
555
+ },
556
+ validateCandidate(value) {
557
+ try {
558
+ normalizeTextStyleValue(property, string(value));
559
+ return [];
560
+ } catch {
561
+ return issue();
562
+ }
563
+ }
564
+ };
565
+ }
566
+ var fixedInputEditors = [{
567
+ apiVersion: 1,
568
+ extensionId,
569
+ editorId: "text-style-fixed",
570
+ opcodes: [`${extensionId}_setTextStyle`],
571
+ contractId: "CP-asset-cache",
572
+ contractRevision: "1",
573
+ validationContext: context,
574
+ bindings: [{
575
+ argument: "PROPERTY",
576
+ schemaBundle: fixedInputBundle("setTextStyle-PROPERTY", fixedSchemas.setTextStyle.properties.PROPERTY),
577
+ control: {
578
+ kind: "enum",
579
+ choices: choices(TEXT_STYLE_PROPERTIES)
580
+ },
581
+ read(value) {
582
+ const raw = string(value);
583
+ try {
584
+ return normalizeTextStyleProperty(raw);
585
+ } catch {
586
+ return raw;
587
+ }
588
+ },
589
+ encode: (value) => string(value),
590
+ validateCandidate(value) {
591
+ try {
592
+ normalizeTextStyleProperty(value);
593
+ return [];
594
+ } catch {
595
+ return issue();
596
+ }
597
+ }
598
+ }, {
599
+ argument: "VALUE",
600
+ schemaBundle: fixedInputBundle("setTextStyle-VALUE", fixedSchemas.setTextStyle.properties.VALUE),
601
+ read: (value) => string(value),
602
+ encode: (value) => string(value),
603
+ validateCandidate: () => []
604
+ }],
605
+ dependencies: [{
606
+ selector: "PROPERTY",
607
+ dependents: ["VALUE"]
608
+ }],
609
+ branchPolicies: [{
610
+ selector: "PROPERTY",
611
+ normalize: {
612
+ trim: true,
613
+ lowercase: true
614
+ },
615
+ cases: TEXT_STYLE_PROPERTIES.map((property) => ({
616
+ values: [property],
617
+ activeDependents: ["VALUE"],
618
+ bindings: [styleBinding(property)]
619
+ }))
620
+ }]
621
+ }, {
622
+ apiVersion: 1,
623
+ extensionId,
624
+ editorId: "loading-costume-index-fixed",
625
+ opcodes: [`${extensionId}_loadingCostumeAt`],
626
+ contractId: "turbowarp-asset-cache/loadingCostumeAt.INDEX",
627
+ contractRevision: "1",
628
+ validationContext: context,
629
+ bindings: [{
630
+ argument: "INDEX",
631
+ schemaBundle: fixedInputBundle("loadingCostumeAt-INDEX", fixedSchemas.loadingCostumeAt.properties.INDEX),
632
+ control: { kind: "number" },
633
+ read(value) {
634
+ if (typeof value !== "number" && typeof value !== "string" || typeof value === "string" && value.trim() === "") throw new TypeError("Non-numeric index.");
635
+ const number = Number(value);
636
+ if (!Number.isFinite(number)) throw new TypeError("Nonfinite index.");
637
+ return number;
638
+ },
639
+ encode(value) {
640
+ const parsed = parseFixedNumberInput(value);
641
+ if (parsed.kind !== "valid") throw new TypeError("Invalid index.");
642
+ return parsed.token;
643
+ },
644
+ validateCandidate(value) {
645
+ return Number.isFinite(Number(value)) ? [] : [{ message: "Invalid index." }];
646
+ }
647
+ }]
648
+ }];
649
+ /** Per-instance, idempotent registration. Legacy hosts never acquire the fixed capability. */
650
+ function fixedInputRegistration(scratch) {
651
+ const host = scratch;
652
+ let active;
653
+ let removers = [];
654
+ let disposed = false;
655
+ const dispose = () => {
656
+ host.vm?.runtime?.off?.("RUNTIME_DISPOSED", shutdown);
657
+ for (const remove of removers.splice(0).reverse()) remove();
658
+ active = void 0;
659
+ };
660
+ const shutdown = () => {
661
+ disposed = true;
662
+ dispose();
663
+ };
664
+ return {
665
+ ensure(opcodes) {
666
+ const api = host.gui?.blockEditors;
667
+ if (disposed || api === active) return;
668
+ dispose();
669
+ if (api?.apiVersion !== 1 || typeof api.registerFixedInputEditor !== "function") return;
670
+ try {
671
+ for (const definition of fixedInputEditors) if (definition.opcodes.every((opcode) => opcodes.includes(opcode))) {
672
+ const remove = api.registerFixedInputEditor.call(api, definition);
673
+ if (typeof remove !== "function") throw new TypeError("Invalid fixed registration cleanup.");
674
+ removers.push(remove);
675
+ }
676
+ active = api;
677
+ host.vm?.runtime?.on?.("RUNTIME_DISPOSED", shutdown);
678
+ } catch (error) {
679
+ dispose();
680
+ throw error;
681
+ }
682
+ },
683
+ dispose: shutdown
684
+ };
685
+ }
686
+ //#endregion
687
+ //#region node_modules/.pnpm/@kubohiroya+turbowarp-named-data@0.3.1/node_modules/@kubohiroya/turbowarp-named-data/dist/composition.js
291
688
  var NAMED_DATA_REGISTRY_SYMBOL_KEY = "@kubohiroya/turbowarp-named-data/registry";
292
689
  var NAMED_DATA_REGISTRY_SYMBOL = Symbol.for(NAMED_DATA_REGISTRY_SYMBOL_KEY);
293
690
  var NAMED_DATA_NAMESPACE_PATTERN = /^[a-z][a-z0-9.-]{0,63}$/u;
@@ -308,6 +705,8 @@
308
705
  "markdown",
309
706
  "raw"
310
707
  ];
708
+ var NAMED_DATA_SCHEMA_ID_PATTERN = /^[a-z][a-z0-9.-]{0,127}$/u;
709
+ var NAMED_DATA_SCHEMA_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/u;
311
710
  var NAMED_DATA_ERROR_CODES = [
312
711
  "NAMED_DATA_INVALID_REF",
313
712
  "NAMED_DATA_INVALID_REGISTRY",
@@ -316,6 +715,9 @@
316
715
  "NAMED_DATA_NOT_FOUND",
317
716
  "NAMED_DATA_KIND_MISMATCH",
318
717
  "NAMED_DATA_SCOPE_MISMATCH",
718
+ "NAMED_DATA_SCHEMA_NOT_FOUND",
719
+ "NAMED_DATA_SCHEMA_VERSION_MISMATCH",
720
+ "NAMED_DATA_SCHEMA_UNRESOLVED",
319
721
  "NAMED_DATA_REPRESENTATION_UNSUPPORTED",
320
722
  "NAMED_DATA_INVALID_METADATA",
321
723
  "NAMED_DATA_BODY_TOO_LARGE",
@@ -536,9 +938,21 @@
536
938
  if (!reference || typeof reference !== "object") throw invalidReference$1();
537
939
  requireNamespace(reference.namespace);
538
940
  if (typeof reference.name !== "string" || reference.name.length === 0 || reference.name.length > 256 || containsControlCharacter(reference.name) || !NAMED_DATA_KINDS.includes(reference.kind) || !NAMED_DATA_SCOPES.includes(reference.scope) || !NAMED_DATA_REPRESENTATIONS.includes(representation)) throw invalidReference$1();
941
+ if (reference.schemaRef !== void 0) validateSchemaRef(reference.kind, reference.schemaRef);
539
942
  }
540
943
  function validateMetadata(metadata, reference, representation) {
541
944
  if (metadata.representation !== representation || !isNativeRepresentation(metadata.reference.kind, metadata.nativeRepresentation) || metadata.reference.namespace !== reference.namespace || metadata.reference.name !== reference.name || metadata.reference.kind !== reference.kind || metadata.reference.scope !== reference.scope || typeof metadata.mediaType !== "string" || metadata.mediaType.length === 0 || typeof metadata.revision !== "string" || metadata.revision.length === 0 || typeof metadata.replayable !== "boolean" || metadata.byteLength !== void 0 && (!Number.isSafeInteger(metadata.byteLength) || metadata.byteLength < 0) || metadata.digest !== void 0 && !/^sha256-[A-Za-z0-9_-]+$/u.test(metadata.digest)) throw new NamedDataError("NAMED_DATA_INVALID_METADATA", "Provider returned invalid metadata.");
945
+ const expectedSchema = reference.schemaRef;
946
+ const actualSchema = metadata.schemaRef;
947
+ if (actualSchema !== void 0) validateSchemaRef(metadata.reference.kind, actualSchema, "NAMED_DATA_INVALID_METADATA");
948
+ if (metadata.reference.schemaRef !== void 0) validateSchemaRef(metadata.reference.kind, metadata.reference.schemaRef, "NAMED_DATA_INVALID_METADATA");
949
+ if (!sameSchemaRef(expectedSchema, actualSchema) || !sameSchemaRef(expectedSchema, metadata.reference.schemaRef)) throw new NamedDataError("NAMED_DATA_SCHEMA_VERSION_MISMATCH", "Provider schemaRef does not match the requested reference.");
950
+ }
951
+ function validateSchemaRef(kind, schemaRef, errorCode = "NAMED_DATA_INVALID_REF") {
952
+ if (kind !== "structured" || typeof schemaRef !== "object" || schemaRef === null || !NAMED_DATA_SCHEMA_ID_PATTERN.test(String(schemaRef.id ?? "")) || !NAMED_DATA_SCHEMA_VERSION_PATTERN.test(String(schemaRef.version ?? "")) || Object.keys(schemaRef).some((key) => key !== "id" && key !== "version")) throw new NamedDataError(errorCode, "Invalid schemaRef; schema references require a structured value, stable id, and full SemVer version.");
953
+ }
954
+ function sameSchemaRef(left, right) {
955
+ return left === void 0 ? right === void 0 : right !== void 0 && left.id === right.id && left.version === right.version;
542
956
  }
543
957
  function isNativeRepresentation(kind, representation) {
544
958
  if (kind === "structured") return representation === "json" || representation === "yaml";
@@ -575,7 +989,10 @@
575
989
  const value = globalThis.__TW_NAMED_DATA_FEATURE_FLAGS__?.[name];
576
990
  return value === true || value === "true";
577
991
  }
578
- Object.freeze({ NAMED_DATA_REGISTRY_MVP: configuredFlag$1("NAMED_DATA_REGISTRY_MVP") });
992
+ Object.freeze({
993
+ NAMED_DATA_REGISTRY_MVP: configuredFlag$1("NAMED_DATA_REGISTRY_MVP"),
994
+ NAMED_DATA_SCHEMA_REF: configuredFlag$1("NAMED_DATA_SCHEMA_REF")
995
+ });
579
996
  //#endregion
580
997
  //#region \0@oxc-project+runtime@0.148.0/helpers/esm/typeof.js
581
998
  function _typeof(o) {
@@ -994,88 +1411,6 @@
994
1411
  if (signal?.aborted) throw namedBodyAbortError();
995
1412
  }
996
1413
  //#endregion
997
- //#region src/text-style.ts
998
- var TEXT_RUNTIME_NAMESPACE = "text";
999
- var TEXT_STYLE_RUNTIME_NAMESPACE = "textStyle";
1000
- var TEXT_STYLE_PROPERTIES = [
1001
- "animation",
1002
- "font",
1003
- "color",
1004
- "width",
1005
- "align"
1006
- ];
1007
- var DEFAULT_STAGE_WIDTH = 480;
1008
- var DEFAULT_FONT = "Handwriting";
1009
- var DEFAULT_COLOR = "#ffffff";
1010
- var DEFAULT_ALIGNMENT = "center";
1011
- var DEFAULT_OUTLINE_COLOR = "#000000";
1012
- function textRuntimeVariableName(name) {
1013
- return `${TEXT_RUNTIME_NAMESPACE}:${name}`;
1014
- }
1015
- function textStyleRuntimeVariableName(name, property) {
1016
- return `${TEXT_STYLE_RUNTIME_NAMESPACE}:${name}:${property}`;
1017
- }
1018
- function normalizeTextStyleProperty(value) {
1019
- const property = String(value ?? "").trim().toLowerCase();
1020
- if (TEXT_STYLE_PROPERTIES.includes(property)) return property;
1021
- throw new Error(`Unknown text style property: ${property || "(empty)"}`);
1022
- }
1023
- /**
1024
- * Normalize a DSL-facing style value for runtime-variable storage.
1025
- * An empty value intentionally resets the property to its default.
1026
- */
1027
- function normalizeTextStyleValue(property, value) {
1028
- const raw = String(value ?? "").trim();
1029
- if (!raw) return "";
1030
- switch (property) {
1031
- case "animation": {
1032
- const animation = raw.toLowerCase() === "typing" ? "type" : raw.toLowerCase();
1033
- if (animation === "none" || animation === "type" || animation === "rainbow" || animation === "zoom" || animation === "shake") return animation;
1034
- throw new Error(`Invalid text animation: ${raw}`);
1035
- }
1036
- case "font": return raw;
1037
- case "color": {
1038
- if (/^#[0-9a-f]{6}$/i.test(raw)) return raw.toLowerCase();
1039
- const shortColor = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(raw);
1040
- if (shortColor) return `#${shortColor[1]}${shortColor[1]}${shortColor[2]}${shortColor[2]}${shortColor[3]}${shortColor[3]}`.toLowerCase();
1041
- throw new Error(`Invalid text color: ${raw}`);
1042
- }
1043
- case "width": {
1044
- const width = Number(raw);
1045
- if (!Number.isFinite(width) || width <= 0) throw new Error(`Text width must be a positive number: ${raw}`);
1046
- return String(width);
1047
- }
1048
- case "align": {
1049
- const align = raw.toLowerCase();
1050
- if (align === "left" || align === "center" || align === "right") return align;
1051
- throw new Error(`Invalid text alignment: ${raw}`);
1052
- }
1053
- }
1054
- }
1055
- function resolveTextStyle(name, stageWidth, getRuntimeVariable) {
1056
- const width = Number(stageWidth);
1057
- const defaults = {
1058
- animation: "none",
1059
- font: DEFAULT_FONT,
1060
- color: DEFAULT_COLOR,
1061
- width: Number.isFinite(width) && width > 0 ? width : DEFAULT_STAGE_WIDTH,
1062
- align: DEFAULT_ALIGNMENT
1063
- };
1064
- const read = (property) => normalizeTextStyleValue(property, getRuntimeVariable(textStyleRuntimeVariableName(name, property)));
1065
- const animation = read("animation");
1066
- const font = read("font");
1067
- const color = read("color");
1068
- const configuredWidth = read("width");
1069
- const align = read("align");
1070
- return {
1071
- animation: animation ? animation : defaults.animation,
1072
- font: font || defaults.font,
1073
- color: color || defaults.color,
1074
- width: configuredWidth ? Number(configuredWidth) : defaults.width,
1075
- align: align ? align : defaults.align
1076
- };
1077
- }
1078
- //#endregion
1079
1414
  //#region src/extension.ts
1080
1415
  var EXTENSION_ID = "kubohiroyaassetcache";
1081
1416
  var EXTENSION_VERSION = "0.14.0";
@@ -1581,6 +1916,7 @@
1581
1916
  this.unbindNamedDataRegistryLifecycle?.();
1582
1917
  this.unbindNamedDataRegistryLifecycle = void 0;
1583
1918
  });
1919
+ _defineProperty(this, "fixedEditors", fixedInputRegistration(Scratch));
1584
1920
  this.featureFlags = Object.freeze({ ...featureFlags });
1585
1921
  if (this.featureFlags.NAMED_ASSET_BODY_PROVIDER) {
1586
1922
  this.namedBodyProviderValue = this.createNamedBodyProvider();
@@ -1646,6 +1982,8 @@
1646
1982
  return loadingCostumes[((Number.isFinite(numericIndex) ? Math.max(1, Math.trunc(numericIndex)) : 1) - 1) % loadingCostumes.length];
1647
1983
  }
1648
1984
  getInfo() {
1985
+ const blocks = blockDefinitions.map((block) => this.toScratchBlock(block));
1986
+ this.fixedEditors.ensure(blocks.map((block) => `${EXTENSION_ID}_${block.opcode}`));
1649
1987
  return {
1650
1988
  id: EXTENSION_ID,
1651
1989
  name: Scratch.translate(block_definitions_default.extensionName),
@@ -1654,9 +1992,13 @@
1654
1992
  color1: "#5b7cfa",
1655
1993
  color2: "#425ed8",
1656
1994
  color3: "#2f46aa",
1657
- blocks: blockDefinitions.map((block) => this.toScratchBlock(block))
1995
+ blocks
1658
1996
  };
1659
1997
  }
1998
+ /** Release only this optional editor registration; runtime data remains unchanged. */
1999
+ dispose() {
2000
+ this.fixedEditors.dispose();
2001
+ }
1660
2002
  /**
1661
2003
  * Exposes the stock extension registry through a host-neutral DOM image
1662
2004
  * resource contract for other unsandboxed extensions.
@@ -278,7 +278,404 @@ var block_definitions_default = {
278
278
  ]
279
279
  };
280
280
  //#endregion
281
- //#region node_modules/.pnpm/@kubohiroya+turbowarp-named-data@0.1.0/node_modules/@kubohiroya/turbowarp-named-data/dist/composition.js
281
+ //#region node_modules/.pnpm/@kubohiroya+turbowarp-extension-data-editor@0.2.1_react@18.3.1/node_modules/@kubohiroya/turbowarp-extension-data-editor/dist/fixed-input-schema.js
282
+ var numberToken = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/u;
283
+ var invalidNumber = Object.freeze({
284
+ kind: "invalid",
285
+ code: "fixed-number-input-invalid"
286
+ });
287
+ /**
288
+ * Parse a NEW numeric draft according to NP-finite-new-input-v1.
289
+ * Never apply this to opening/no-op raw values or use it as an owner validator.
290
+ * Keep the validated token so negative zero, precision and exponent spelling survive.
291
+ */
292
+ function parseFixedNumberInput(input) {
293
+ if (typeof input === "number") {
294
+ if (!Number.isFinite(input)) return invalidNumber;
295
+ return Object.freeze({
296
+ kind: "valid",
297
+ token: Object.is(input, -0) ? "-0" : String(input),
298
+ value: input
299
+ });
300
+ }
301
+ if (typeof input !== "string") return invalidNumber;
302
+ const token = input.trim();
303
+ if (!numberToken.test(token)) return invalidNumber;
304
+ const value = Number(token);
305
+ return Number.isFinite(value) ? Object.freeze({
306
+ kind: "valid",
307
+ token,
308
+ value
309
+ }) : invalidNumber;
310
+ }
311
+ var block_editor_fixed_inputs_schema_default = {
312
+ $schema: "https://json-schema.org/draft/2020-12/schema",
313
+ $id: "https://kubohiroya.github.io/turbowarp-asset-cache/schemas/block-editor-fixed-inputs.schema.json",
314
+ $defs: {
315
+ "setTextStyle": {
316
+ "type": "object",
317
+ "properties": {
318
+ "PROPERTY": {
319
+ "type": "string",
320
+ "enum": [
321
+ "animation",
322
+ "font",
323
+ "color",
324
+ "width",
325
+ "align"
326
+ ]
327
+ },
328
+ "VALUE": { "type": "string" }
329
+ },
330
+ "additionalProperties": false
331
+ },
332
+ "animation": {
333
+ "type": "string",
334
+ "enum": [
335
+ "",
336
+ "none",
337
+ "type",
338
+ "rainbow",
339
+ "zoom",
340
+ "shake"
341
+ ]
342
+ },
343
+ "font": { "type": "string" },
344
+ "color": {
345
+ "type": "string",
346
+ "pattern": "^(?:|#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6})$"
347
+ },
348
+ "width": {
349
+ "type": "string",
350
+ "$comment": "Empty/trimmed empty resets; otherwise normalizeTextStyleValue requires Number(value) finite and positive. Preserve the existing STRING grammar, including hexadecimal and leading plus."
351
+ },
352
+ "align": {
353
+ "type": "string",
354
+ "enum": [
355
+ "",
356
+ "left",
357
+ "center",
358
+ "right"
359
+ ]
360
+ },
361
+ "loadingCostumeAt": {
362
+ "type": "object",
363
+ "properties": { "INDEX": { "type": "number" } },
364
+ "additionalProperties": false
365
+ }
366
+ }
367
+ };
368
+ //#endregion
369
+ //#region src/fixed-input-schema.ts
370
+ /** Canonical owner schema fragments; no network resources or runtime providers. */
371
+ function fixedInputBundle(fragment, schema) {
372
+ const source = {
373
+ repository: "turbowarp-asset-cache",
374
+ path: "schemas/block-editor-fixed-inputs.schema.json",
375
+ symbol: {
376
+ "setTextStyle-PROPERTY": "#/$defs/setTextStyle/properties/PROPERTY",
377
+ "setTextStyle-VALUE": "#/$defs/setTextStyle/properties/VALUE",
378
+ "loadingCostumeAt-INDEX": "#/$defs/loadingCostumeAt/properties/INDEX"
379
+ }[fragment] ?? `#/$defs/${fragment}`
380
+ };
381
+ return {
382
+ identity: {
383
+ ownerId: "turbowarp-asset-cache",
384
+ contractId: "fixed-inputs",
385
+ revision: "1",
386
+ variantId: fragment
387
+ },
388
+ dialect: "2020-12",
389
+ root: {
390
+ retrievalUri: block_editor_fixed_inputs_schema_default.$id,
391
+ document: schema,
392
+ artifact: {
393
+ role: "canonical-schema",
394
+ source
395
+ }
396
+ },
397
+ resources: [],
398
+ provenance: {
399
+ canonicalSources: [
400
+ source,
401
+ {
402
+ repository: "turbowarp-asset-cache",
403
+ path: "src/text-style.ts",
404
+ symbol: "normalizeTextStyleProperty / normalizeTextStyleValue"
405
+ },
406
+ {
407
+ repository: "turbowarp-asset-cache",
408
+ path: "src/extension.ts",
409
+ symbol: "getInfo / loadingCostumeAt"
410
+ }
411
+ ],
412
+ generatedFiles: [],
413
+ referenceSchemas: [],
414
+ generationSources: []
415
+ }
416
+ };
417
+ }
418
+ var fixedSchemas = block_editor_fixed_inputs_schema_default.$defs;
419
+ //#endregion
420
+ //#region src/text-style.ts
421
+ var TEXT_RUNTIME_NAMESPACE = "text";
422
+ var TEXT_STYLE_RUNTIME_NAMESPACE = "textStyle";
423
+ var TEXT_STYLE_PROPERTIES = [
424
+ "animation",
425
+ "font",
426
+ "color",
427
+ "width",
428
+ "align"
429
+ ];
430
+ var DEFAULT_STAGE_WIDTH = 480;
431
+ var DEFAULT_FONT = "Handwriting";
432
+ var DEFAULT_COLOR = "#ffffff";
433
+ var DEFAULT_ALIGNMENT = "center";
434
+ var DEFAULT_OUTLINE_COLOR = "#000000";
435
+ function textRuntimeVariableName(name) {
436
+ return `${TEXT_RUNTIME_NAMESPACE}:${name}`;
437
+ }
438
+ function textStyleRuntimeVariableName(name, property) {
439
+ return `${TEXT_STYLE_RUNTIME_NAMESPACE}:${name}:${property}`;
440
+ }
441
+ function normalizeTextStyleProperty(value) {
442
+ const property = String(value ?? "").trim().toLowerCase();
443
+ if (TEXT_STYLE_PROPERTIES.includes(property)) return property;
444
+ throw new Error(`Unknown text style property: ${property || "(empty)"}`);
445
+ }
446
+ /**
447
+ * Normalize a DSL-facing style value for runtime-variable storage.
448
+ * An empty value intentionally resets the property to its default.
449
+ */
450
+ function normalizeTextStyleValue(property, value) {
451
+ const raw = String(value ?? "").trim();
452
+ if (!raw) return "";
453
+ switch (property) {
454
+ case "animation": {
455
+ const animation = raw.toLowerCase() === "typing" ? "type" : raw.toLowerCase();
456
+ if (animation === "none" || animation === "type" || animation === "rainbow" || animation === "zoom" || animation === "shake") return animation;
457
+ throw new Error(`Invalid text animation: ${raw}`);
458
+ }
459
+ case "font": return raw;
460
+ case "color": {
461
+ if (/^#[0-9a-f]{6}$/i.test(raw)) return raw.toLowerCase();
462
+ const shortColor = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(raw);
463
+ if (shortColor) return `#${shortColor[1]}${shortColor[1]}${shortColor[2]}${shortColor[2]}${shortColor[3]}${shortColor[3]}`.toLowerCase();
464
+ throw new Error(`Invalid text color: ${raw}`);
465
+ }
466
+ case "width": {
467
+ const width = Number(raw);
468
+ if (!Number.isFinite(width) || width <= 0) throw new Error(`Text width must be a positive number: ${raw}`);
469
+ return String(width);
470
+ }
471
+ case "align": {
472
+ const align = raw.toLowerCase();
473
+ if (align === "left" || align === "center" || align === "right") return align;
474
+ throw new Error(`Invalid text alignment: ${raw}`);
475
+ }
476
+ }
477
+ }
478
+ function resolveTextStyle(name, stageWidth, getRuntimeVariable) {
479
+ const width = Number(stageWidth);
480
+ const defaults = {
481
+ animation: "none",
482
+ font: DEFAULT_FONT,
483
+ color: DEFAULT_COLOR,
484
+ width: Number.isFinite(width) && width > 0 ? width : DEFAULT_STAGE_WIDTH,
485
+ align: DEFAULT_ALIGNMENT
486
+ };
487
+ const read = (property) => normalizeTextStyleValue(property, getRuntimeVariable(textStyleRuntimeVariableName(name, property)));
488
+ const animation = read("animation");
489
+ const font = read("font");
490
+ const color = read("color");
491
+ const configuredWidth = read("width");
492
+ const align = read("align");
493
+ return {
494
+ animation: animation ? animation : defaults.animation,
495
+ font: font || defaults.font,
496
+ color: color || defaults.color,
497
+ width: configuredWidth ? Number(configuredWidth) : defaults.width,
498
+ align: align ? align : defaults.align
499
+ };
500
+ }
501
+ //#endregion
502
+ //#region src/fixed-input-editors.ts
503
+ var extensionId = "kubohiroyaassetcache";
504
+ var choices = (values) => values.map((value) => ({ value }));
505
+ var context = () => ({
506
+ consumerId: "turbowarp-asset-cache",
507
+ nowMs: 0,
508
+ settings: null
509
+ });
510
+ var string = (value) => {
511
+ if (typeof value !== "string") throw new TypeError("Expected a string literal.");
512
+ return value;
513
+ };
514
+ var issue = () => [{ message: "Invalid asset style value." }];
515
+ /** New edits preserve the candidate token; existing aliases are display projections only. */
516
+ function styleBinding(property) {
517
+ const values = property === "animation" ? [
518
+ "",
519
+ "none",
520
+ "type",
521
+ "rainbow",
522
+ "zoom",
523
+ "shake"
524
+ ] : property === "align" ? [
525
+ "",
526
+ "left",
527
+ "center",
528
+ "right"
529
+ ] : void 0;
530
+ return {
531
+ argument: "VALUE",
532
+ schemaBundle: fixedInputBundle(property, fixedSchemas[property]),
533
+ control: {
534
+ kind: values ? "enum" : property === "color" ? "color" : property === "width" ? "number" : "text",
535
+ reset: "",
536
+ ...values ? { choices: choices(values) } : {}
537
+ },
538
+ read(value) {
539
+ return normalizeTextStyleValue(property, string(value));
540
+ },
541
+ encode(value) {
542
+ const raw = string(value);
543
+ if (property !== "width" || raw === "") return raw;
544
+ normalizeTextStyleValue(property, raw);
545
+ return raw;
546
+ },
547
+ validateCandidate(value) {
548
+ try {
549
+ normalizeTextStyleValue(property, string(value));
550
+ return [];
551
+ } catch {
552
+ return issue();
553
+ }
554
+ }
555
+ };
556
+ }
557
+ var fixedInputEditors = [{
558
+ apiVersion: 1,
559
+ extensionId,
560
+ editorId: "text-style-fixed",
561
+ opcodes: [`${extensionId}_setTextStyle`],
562
+ contractId: "CP-asset-cache",
563
+ contractRevision: "1",
564
+ validationContext: context,
565
+ bindings: [{
566
+ argument: "PROPERTY",
567
+ schemaBundle: fixedInputBundle("setTextStyle-PROPERTY", fixedSchemas.setTextStyle.properties.PROPERTY),
568
+ control: {
569
+ kind: "enum",
570
+ choices: choices(TEXT_STYLE_PROPERTIES)
571
+ },
572
+ read(value) {
573
+ const raw = string(value);
574
+ try {
575
+ return normalizeTextStyleProperty(raw);
576
+ } catch {
577
+ return raw;
578
+ }
579
+ },
580
+ encode: (value) => string(value),
581
+ validateCandidate(value) {
582
+ try {
583
+ normalizeTextStyleProperty(value);
584
+ return [];
585
+ } catch {
586
+ return issue();
587
+ }
588
+ }
589
+ }, {
590
+ argument: "VALUE",
591
+ schemaBundle: fixedInputBundle("setTextStyle-VALUE", fixedSchemas.setTextStyle.properties.VALUE),
592
+ read: (value) => string(value),
593
+ encode: (value) => string(value),
594
+ validateCandidate: () => []
595
+ }],
596
+ dependencies: [{
597
+ selector: "PROPERTY",
598
+ dependents: ["VALUE"]
599
+ }],
600
+ branchPolicies: [{
601
+ selector: "PROPERTY",
602
+ normalize: {
603
+ trim: true,
604
+ lowercase: true
605
+ },
606
+ cases: TEXT_STYLE_PROPERTIES.map((property) => ({
607
+ values: [property],
608
+ activeDependents: ["VALUE"],
609
+ bindings: [styleBinding(property)]
610
+ }))
611
+ }]
612
+ }, {
613
+ apiVersion: 1,
614
+ extensionId,
615
+ editorId: "loading-costume-index-fixed",
616
+ opcodes: [`${extensionId}_loadingCostumeAt`],
617
+ contractId: "turbowarp-asset-cache/loadingCostumeAt.INDEX",
618
+ contractRevision: "1",
619
+ validationContext: context,
620
+ bindings: [{
621
+ argument: "INDEX",
622
+ schemaBundle: fixedInputBundle("loadingCostumeAt-INDEX", fixedSchemas.loadingCostumeAt.properties.INDEX),
623
+ control: { kind: "number" },
624
+ read(value) {
625
+ if (typeof value !== "number" && typeof value !== "string" || typeof value === "string" && value.trim() === "") throw new TypeError("Non-numeric index.");
626
+ const number = Number(value);
627
+ if (!Number.isFinite(number)) throw new TypeError("Nonfinite index.");
628
+ return number;
629
+ },
630
+ encode(value) {
631
+ const parsed = parseFixedNumberInput(value);
632
+ if (parsed.kind !== "valid") throw new TypeError("Invalid index.");
633
+ return parsed.token;
634
+ },
635
+ validateCandidate(value) {
636
+ return Number.isFinite(Number(value)) ? [] : [{ message: "Invalid index." }];
637
+ }
638
+ }]
639
+ }];
640
+ /** Per-instance, idempotent registration. Legacy hosts never acquire the fixed capability. */
641
+ function fixedInputRegistration(scratch) {
642
+ const host = scratch;
643
+ let active;
644
+ let removers = [];
645
+ let disposed = false;
646
+ const dispose = () => {
647
+ host.vm?.runtime?.off?.("RUNTIME_DISPOSED", shutdown);
648
+ for (const remove of removers.splice(0).reverse()) remove();
649
+ active = void 0;
650
+ };
651
+ const shutdown = () => {
652
+ disposed = true;
653
+ dispose();
654
+ };
655
+ return {
656
+ ensure(opcodes) {
657
+ const api = host.gui?.blockEditors;
658
+ if (disposed || api === active) return;
659
+ dispose();
660
+ if (api?.apiVersion !== 1 || typeof api.registerFixedInputEditor !== "function") return;
661
+ try {
662
+ for (const definition of fixedInputEditors) if (definition.opcodes.every((opcode) => opcodes.includes(opcode))) {
663
+ const remove = api.registerFixedInputEditor.call(api, definition);
664
+ if (typeof remove !== "function") throw new TypeError("Invalid fixed registration cleanup.");
665
+ removers.push(remove);
666
+ }
667
+ active = api;
668
+ host.vm?.runtime?.on?.("RUNTIME_DISPOSED", shutdown);
669
+ } catch (error) {
670
+ dispose();
671
+ throw error;
672
+ }
673
+ },
674
+ dispose: shutdown
675
+ };
676
+ }
677
+ //#endregion
678
+ //#region node_modules/.pnpm/@kubohiroya+turbowarp-named-data@0.3.1/node_modules/@kubohiroya/turbowarp-named-data/dist/composition.js
282
679
  var NAMED_DATA_REGISTRY_SYMBOL_KEY = "@kubohiroya/turbowarp-named-data/registry";
283
680
  var NAMED_DATA_REGISTRY_SYMBOL = Symbol.for(NAMED_DATA_REGISTRY_SYMBOL_KEY);
284
681
  var NAMED_DATA_NAMESPACE_PATTERN = /^[a-z][a-z0-9.-]{0,63}$/u;
@@ -299,6 +696,8 @@ var NAMED_DATA_REPRESENTATIONS = [
299
696
  "markdown",
300
697
  "raw"
301
698
  ];
699
+ var NAMED_DATA_SCHEMA_ID_PATTERN = /^[a-z][a-z0-9.-]{0,127}$/u;
700
+ var NAMED_DATA_SCHEMA_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/u;
302
701
  var NAMED_DATA_ERROR_CODES = [
303
702
  "NAMED_DATA_INVALID_REF",
304
703
  "NAMED_DATA_INVALID_REGISTRY",
@@ -307,6 +706,9 @@ var NAMED_DATA_ERROR_CODES = [
307
706
  "NAMED_DATA_NOT_FOUND",
308
707
  "NAMED_DATA_KIND_MISMATCH",
309
708
  "NAMED_DATA_SCOPE_MISMATCH",
709
+ "NAMED_DATA_SCHEMA_NOT_FOUND",
710
+ "NAMED_DATA_SCHEMA_VERSION_MISMATCH",
711
+ "NAMED_DATA_SCHEMA_UNRESOLVED",
310
712
  "NAMED_DATA_REPRESENTATION_UNSUPPORTED",
311
713
  "NAMED_DATA_INVALID_METADATA",
312
714
  "NAMED_DATA_BODY_TOO_LARGE",
@@ -527,9 +929,21 @@ function validateReferenceShape(reference, representation) {
527
929
  if (!reference || typeof reference !== "object") throw invalidReference$1();
528
930
  requireNamespace(reference.namespace);
529
931
  if (typeof reference.name !== "string" || reference.name.length === 0 || reference.name.length > 256 || containsControlCharacter(reference.name) || !NAMED_DATA_KINDS.includes(reference.kind) || !NAMED_DATA_SCOPES.includes(reference.scope) || !NAMED_DATA_REPRESENTATIONS.includes(representation)) throw invalidReference$1();
932
+ if (reference.schemaRef !== void 0) validateSchemaRef(reference.kind, reference.schemaRef);
530
933
  }
531
934
  function validateMetadata(metadata, reference, representation) {
532
935
  if (metadata.representation !== representation || !isNativeRepresentation(metadata.reference.kind, metadata.nativeRepresentation) || metadata.reference.namespace !== reference.namespace || metadata.reference.name !== reference.name || metadata.reference.kind !== reference.kind || metadata.reference.scope !== reference.scope || typeof metadata.mediaType !== "string" || metadata.mediaType.length === 0 || typeof metadata.revision !== "string" || metadata.revision.length === 0 || typeof metadata.replayable !== "boolean" || metadata.byteLength !== void 0 && (!Number.isSafeInteger(metadata.byteLength) || metadata.byteLength < 0) || metadata.digest !== void 0 && !/^sha256-[A-Za-z0-9_-]+$/u.test(metadata.digest)) throw new NamedDataError("NAMED_DATA_INVALID_METADATA", "Provider returned invalid metadata.");
936
+ const expectedSchema = reference.schemaRef;
937
+ const actualSchema = metadata.schemaRef;
938
+ if (actualSchema !== void 0) validateSchemaRef(metadata.reference.kind, actualSchema, "NAMED_DATA_INVALID_METADATA");
939
+ if (metadata.reference.schemaRef !== void 0) validateSchemaRef(metadata.reference.kind, metadata.reference.schemaRef, "NAMED_DATA_INVALID_METADATA");
940
+ if (!sameSchemaRef(expectedSchema, actualSchema) || !sameSchemaRef(expectedSchema, metadata.reference.schemaRef)) throw new NamedDataError("NAMED_DATA_SCHEMA_VERSION_MISMATCH", "Provider schemaRef does not match the requested reference.");
941
+ }
942
+ function validateSchemaRef(kind, schemaRef, errorCode = "NAMED_DATA_INVALID_REF") {
943
+ if (kind !== "structured" || typeof schemaRef !== "object" || schemaRef === null || !NAMED_DATA_SCHEMA_ID_PATTERN.test(String(schemaRef.id ?? "")) || !NAMED_DATA_SCHEMA_VERSION_PATTERN.test(String(schemaRef.version ?? "")) || Object.keys(schemaRef).some((key) => key !== "id" && key !== "version")) throw new NamedDataError(errorCode, "Invalid schemaRef; schema references require a structured value, stable id, and full SemVer version.");
944
+ }
945
+ function sameSchemaRef(left, right) {
946
+ return left === void 0 ? right === void 0 : right !== void 0 && left.id === right.id && left.version === right.version;
533
947
  }
534
948
  function isNativeRepresentation(kind, representation) {
535
949
  if (kind === "structured") return representation === "json" || representation === "yaml";
@@ -566,7 +980,10 @@ function configuredFlag$1(name) {
566
980
  const value = globalThis.__TW_NAMED_DATA_FEATURE_FLAGS__?.[name];
567
981
  return value === true || value === "true";
568
982
  }
569
- Object.freeze({ NAMED_DATA_REGISTRY_MVP: configuredFlag$1("NAMED_DATA_REGISTRY_MVP") });
983
+ Object.freeze({
984
+ NAMED_DATA_REGISTRY_MVP: configuredFlag$1("NAMED_DATA_REGISTRY_MVP"),
985
+ NAMED_DATA_SCHEMA_REF: configuredFlag$1("NAMED_DATA_SCHEMA_REF")
986
+ });
570
987
  //#endregion
571
988
  //#region src/asset-cache-error.ts
572
989
  var AssetManagerError = class extends Error {
@@ -948,88 +1365,6 @@ function throwIfNamedBodyAborted(signal) {
948
1365
  if (signal?.aborted) throw namedBodyAbortError();
949
1366
  }
950
1367
  //#endregion
951
- //#region src/text-style.ts
952
- var TEXT_RUNTIME_NAMESPACE = "text";
953
- var TEXT_STYLE_RUNTIME_NAMESPACE = "textStyle";
954
- var TEXT_STYLE_PROPERTIES = [
955
- "animation",
956
- "font",
957
- "color",
958
- "width",
959
- "align"
960
- ];
961
- var DEFAULT_STAGE_WIDTH = 480;
962
- var DEFAULT_FONT = "Handwriting";
963
- var DEFAULT_COLOR = "#ffffff";
964
- var DEFAULT_ALIGNMENT = "center";
965
- var DEFAULT_OUTLINE_COLOR = "#000000";
966
- function textRuntimeVariableName(name) {
967
- return `${TEXT_RUNTIME_NAMESPACE}:${name}`;
968
- }
969
- function textStyleRuntimeVariableName(name, property) {
970
- return `${TEXT_STYLE_RUNTIME_NAMESPACE}:${name}:${property}`;
971
- }
972
- function normalizeTextStyleProperty(value) {
973
- const property = String(value ?? "").trim().toLowerCase();
974
- if (TEXT_STYLE_PROPERTIES.includes(property)) return property;
975
- throw new Error(`Unknown text style property: ${property || "(empty)"}`);
976
- }
977
- /**
978
- * Normalize a DSL-facing style value for runtime-variable storage.
979
- * An empty value intentionally resets the property to its default.
980
- */
981
- function normalizeTextStyleValue(property, value) {
982
- const raw = String(value ?? "").trim();
983
- if (!raw) return "";
984
- switch (property) {
985
- case "animation": {
986
- const animation = raw.toLowerCase() === "typing" ? "type" : raw.toLowerCase();
987
- if (animation === "none" || animation === "type" || animation === "rainbow" || animation === "zoom" || animation === "shake") return animation;
988
- throw new Error(`Invalid text animation: ${raw}`);
989
- }
990
- case "font": return raw;
991
- case "color": {
992
- if (/^#[0-9a-f]{6}$/i.test(raw)) return raw.toLowerCase();
993
- const shortColor = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(raw);
994
- if (shortColor) return `#${shortColor[1]}${shortColor[1]}${shortColor[2]}${shortColor[2]}${shortColor[3]}${shortColor[3]}`.toLowerCase();
995
- throw new Error(`Invalid text color: ${raw}`);
996
- }
997
- case "width": {
998
- const width = Number(raw);
999
- if (!Number.isFinite(width) || width <= 0) throw new Error(`Text width must be a positive number: ${raw}`);
1000
- return String(width);
1001
- }
1002
- case "align": {
1003
- const align = raw.toLowerCase();
1004
- if (align === "left" || align === "center" || align === "right") return align;
1005
- throw new Error(`Invalid text alignment: ${raw}`);
1006
- }
1007
- }
1008
- }
1009
- function resolveTextStyle(name, stageWidth, getRuntimeVariable) {
1010
- const width = Number(stageWidth);
1011
- const defaults = {
1012
- animation: "none",
1013
- font: DEFAULT_FONT,
1014
- color: DEFAULT_COLOR,
1015
- width: Number.isFinite(width) && width > 0 ? width : DEFAULT_STAGE_WIDTH,
1016
- align: DEFAULT_ALIGNMENT
1017
- };
1018
- const read = (property) => normalizeTextStyleValue(property, getRuntimeVariable(textStyleRuntimeVariableName(name, property)));
1019
- const animation = read("animation");
1020
- const font = read("font");
1021
- const color = read("color");
1022
- const configuredWidth = read("width");
1023
- const align = read("align");
1024
- return {
1025
- animation: animation ? animation : defaults.animation,
1026
- font: font || defaults.font,
1027
- color: color || defaults.color,
1028
- width: configuredWidth ? Number(configuredWidth) : defaults.width,
1029
- align: align ? align : defaults.align
1030
- };
1031
- }
1032
- //#endregion
1033
1368
  //#region src/extension.ts
1034
1369
  var EXTENSION_ID = "kubohiroyaassetcache";
1035
1370
  var EXTENSION_VERSION = "0.14.0";
@@ -1534,6 +1869,7 @@ var AssetManagerExtension = class {
1534
1869
  this.unbindNamedDataRegistryLifecycle?.();
1535
1870
  this.unbindNamedDataRegistryLifecycle = void 0;
1536
1871
  };
1872
+ fixedEditors = fixedInputRegistration(Scratch);
1537
1873
  constructor(featureFlags = FEATURE_FLAGS) {
1538
1874
  this.featureFlags = Object.freeze({ ...featureFlags });
1539
1875
  if (this.featureFlags.NAMED_ASSET_BODY_PROVIDER) {
@@ -1600,6 +1936,8 @@ var AssetManagerExtension = class {
1600
1936
  return loadingCostumes[((Number.isFinite(numericIndex) ? Math.max(1, Math.trunc(numericIndex)) : 1) - 1) % loadingCostumes.length];
1601
1937
  }
1602
1938
  getInfo() {
1939
+ const blocks = blockDefinitions.map((block) => this.toScratchBlock(block));
1940
+ this.fixedEditors.ensure(blocks.map((block) => `${EXTENSION_ID}_${block.opcode}`));
1603
1941
  return {
1604
1942
  id: EXTENSION_ID,
1605
1943
  name: Scratch.translate(block_definitions_default.extensionName),
@@ -1608,9 +1946,13 @@ var AssetManagerExtension = class {
1608
1946
  color1: "#5b7cfa",
1609
1947
  color2: "#425ed8",
1610
1948
  color3: "#2f46aa",
1611
- blocks: blockDefinitions.map((block) => this.toScratchBlock(block))
1949
+ blocks
1612
1950
  };
1613
1951
  }
1952
+ /** Release only this optional editor registration; runtime data remains unchanged. */
1953
+ dispose() {
1954
+ this.fixedEditors.dispose();
1955
+ }
1614
1956
  /**
1615
1957
  * Exposes the stock extension registry through a host-neutral DOM image
1616
1958
  * resource contract for other unsandboxed extensions.
@@ -121,6 +121,7 @@ export declare class AssetManagerExtension {
121
121
  private readonly releaseDOMImageResourcesForRuntimeDispose;
122
122
  private readonly releaseNamedBodySnapshotsForLifecycle;
123
123
  private readonly unregisterNamedBodyProviderForRuntimeDispose;
124
+ private readonly fixedEditors;
124
125
  constructor(featureFlags?: AssetManagerFeatureFlags);
125
126
  setLoadingBackdrop(args: BlockArgs): void;
126
127
  setLoadingCostumes(args: BlockArgs): void;
@@ -138,6 +139,8 @@ export declare class AssetManagerExtension {
138
139
  color3: string;
139
140
  blocks: Record<string, unknown>[];
140
141
  };
142
+ /** Release only this optional editor registration; runtime data remains unchanged. */
143
+ dispose(): void;
141
144
  /**
142
145
  * Exposes the stock extension registry through a host-neutral DOM image
143
146
  * resource contract for other unsandboxed extensions.
@@ -0,0 +1,10 @@
1
+ import type { FixedInputBinding, FixedInputEditorOptions } from '@kubohiroya/turbowarp-extension-data-editor';
2
+ import { type TextStyleProperty } from './text-style.js';
3
+ /** New edits preserve the candidate token; existing aliases are display projections only. */
4
+ export declare function styleBinding(property: TextStyleProperty): FixedInputBinding;
5
+ export declare const fixedInputEditors: readonly FixedInputEditorOptions[];
6
+ /** Per-instance, idempotent registration. Legacy hosts never acquire the fixed capability. */
7
+ export declare function fixedInputRegistration(scratch: unknown): {
8
+ ensure(opcodes: readonly string[]): void;
9
+ dispose: () => void;
10
+ };
@@ -0,0 +1,46 @@
1
+ import type { SchemaBundle, SchemaDocument } from '@kubohiroya/turbowarp-extension-data-editor/schema';
2
+ /** Canonical owner schema fragments; no network resources or runtime providers. */
3
+ export declare function fixedInputBundle(fragment: string, schema: SchemaDocument): SchemaBundle;
4
+ export declare const fixedSchemas: {
5
+ setTextStyle: {
6
+ type: string;
7
+ properties: {
8
+ PROPERTY: {
9
+ type: string;
10
+ enum: string[];
11
+ };
12
+ VALUE: {
13
+ type: string;
14
+ };
15
+ };
16
+ additionalProperties: boolean;
17
+ };
18
+ animation: {
19
+ type: string;
20
+ enum: string[];
21
+ };
22
+ font: {
23
+ type: string;
24
+ };
25
+ color: {
26
+ type: string;
27
+ pattern: string;
28
+ };
29
+ width: {
30
+ type: string;
31
+ $comment: string;
32
+ };
33
+ align: {
34
+ type: string;
35
+ enum: string[];
36
+ };
37
+ loadingCostumeAt: {
38
+ type: string;
39
+ properties: {
40
+ INDEX: {
41
+ type: string;
42
+ };
43
+ };
44
+ additionalProperties: boolean;
45
+ };
46
+ };
@@ -0,0 +1,20 @@
1
+ # 固定入力エディターの契約
2
+
3
+ 対応GUIの起動時に`blockEditorDialogs=1&blockEditorFixedInputs=1`を指定し、再読込した場合だけ登録する。既定OFFで、旧GUIやCLIに固定登録APIがなければ何もしない。公開するブロック・引数・既定値・`docsURI`・既存データ形式は変更しない。
4
+
5
+ | ブロック | 対象 | UIと保存 |
6
+ |---|---|---|
7
+ | `setTextStyle` | `PROPERTY` / `VALUE` | PROPERTYの固定5選択とVALUEの有限分岐。animation/alignは選択、fontは自由入力、colorは色、widthは数値文字列。VALUEは常にSTRINGで空文字reset可能 |
8
+ | `loadingCostumeAt` | `INDEX` | 数値UI。新入力は有限の数値表記。小数・負数を許容し、既存runtimeの切捨て・下限補正・循環を維持。非表示の互換ブロック |
9
+
10
+ Schemaの正本は`schemas/block-editor-fixed-inputs.schema.json`。`getInfo()`と`src/text-style.ts`の`normalizeTextStyleProperty` / `normalizeTextStyleValue`、`src/extension.ts`の`loadingCostumeAt`がruntime契約の正本であり、棚卸しやUI候補だけを受け入れ根拠にしない。新Schemaは登録用の各fragmentに投影し、ネットワークSchema参照を行わない。NP-finite-new-input-v1の対象はINDEXであり、STRINGであるVALUE.widthへ一律適用しない。widthのSchemaはSTRING構造を確認し、CPの正の有限値/resetをpure normalizerで意味検証する。STRINGの数値UIとprogrammatic候補はともに`0x10`・`+20`等を既存runtimeと同じく受け入れる。JSON数値文法のbuffer検査はINDEX等のnumber型投影に限定し、widthと区別する。
11
+
12
+ 既存alias(`typing`、短縮色、大小文字、旧数値表記)は表示投影だけでは書き換えない。PROPERTYの修復・変更時は最終branchで必要なVALUEを検査し、不適合なら修正/resetまで全patchを拒否する。selectorがreporter・欠落・非対応ならVALUEは読取専用。reporterを評価・置換しない。未知literal selectorは既知値へ修復可能で、未変更なら未知rawも保持する。NAMEや素材registry、font候補providerには触れない。
13
+
14
+ 同じ画面で分岐を切り替えてもdraftを保持し、保存失敗後も保持する。取消・dispose・project/target/登録の失効では共通sessionが破棄する。再openは現在のrawから開始し、旧draftを復活させない。登録はgetInfoに存在するopcodeだけを対象とし、同じinstance/APIへの重複登録を行わず、API置換・部分失敗・runtime終了・disposeでcleanupする。
15
+
16
+ 共通依存はGit commit `485f0e550e622235624b5e9176820161aeeba252`に固定する。開発用の型とpure数値parserを使い、登録はhostの`registerFixedInputEditor`へoptionsを渡す。既存composition APIにGUI/React実行依存を追加しない。runtime同梱JSと公開入口の型が外部common参照を要求しないことをbuildとpack consumerで確認する。
17
+
18
+ `tests/fixed-input-editors.test.ts`でCP-asset-cacheのBC01〜05/14全6fixture、全5branchのruntime比較、空文字reset、有限数値と精度表記、旧raw保持、登録とcleanupを検証する。Asset Managerは別ownerであり、この結果を流用して合格とはしない。文書入口は`docs/index.html`と`docs/ja/index.html`の各固定アンカーから到達でき、Markdown再生成ではこれらの静的HTML説明を削除しない。
19
+
20
+ 切戻しは固定フラグをOFFにして再読込する。依存を戻す場合はmanifest・lock・実解決を一組で戻す。既存raw値やsb3の移行は不要。今回の作業はローカルのみで、commit・push・PR・merge・Pages公開・npm公開・Issue操作は含まない。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-asset-cache",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "A browser-local image, audio, and runtime-text asset cache for TurboWarp projects.",
5
5
  "author": "Hiroya Kubo <hiroya@cuc.ac.jp>",
6
6
  "type": "module",
@@ -22,6 +22,8 @@
22
22
  },
23
23
  "files": [
24
24
  "dist/",
25
+ "schemas/",
26
+ "docs/fixed-input-editors.ja.md",
25
27
  "README.md",
26
28
  "LICENSE"
27
29
  ],
@@ -60,9 +62,10 @@
60
62
  },
61
63
  "devDependencies": {
62
64
  "@eslint/js": "^9.39.5",
63
- "@kubohiroya/turbowarp-extension-manifest": "0.3.0",
64
- "@kubohiroya/turbowarp-named-data": "0.1.0",
65
- "@kubohiroya/vite-plugin-turbowarp-extension": "0.4.0",
65
+ "@kubohiroya/turbowarp-extension-data-editor": "0.2.1",
66
+ "@kubohiroya/turbowarp-extension-manifest": "0.3.2",
67
+ "@kubohiroya/turbowarp-named-data": "0.3.1",
68
+ "@kubohiroya/vite-plugin-turbowarp-extension": "0.6.1",
66
69
  "@types/node": "^22.20.1",
67
70
  "@xmldom/xmldom": "0.9.11",
68
71
  "eslint": "^9.39.5",
@@ -73,7 +76,7 @@
73
76
  "vitest": "^4.1.10"
74
77
  },
75
78
  "peerDependencies": {
76
- "@kubohiroya/turbowarp-named-data": "^0.1.0"
79
+ "@kubohiroya/turbowarp-named-data": "0.3.1"
77
80
  },
78
81
  "keywords": [
79
82
  "turbowarp",
@@ -81,5 +84,11 @@
81
84
  "extension",
82
85
  "assets",
83
86
  "indexeddb"
84
- ]
87
+ ],
88
+ "gitHead": "9a1a4a1a016722eccf649207f8517c8533bf68b0",
89
+ "x-turbowarp-source": {
90
+ "revision": "9a1a4a1a016722eccf649207f8517c8533bf68b0",
91
+ "revisionRole": "base-commit",
92
+ "sourceState": "local-release-overlay"
93
+ }
85
94
  }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://kubohiroya.github.io/turbowarp-asset-cache/schemas/block-editor-fixed-inputs.schema.json",
4
+ "$defs": {
5
+ "setTextStyle": {
6
+ "type": "object",
7
+ "properties": {
8
+ "PROPERTY": {"type": "string", "enum": ["animation", "font", "color", "width", "align"]},
9
+ "VALUE": {"type": "string"}
10
+ },
11
+ "additionalProperties": false
12
+ },
13
+ "animation": {"type": "string", "enum": ["", "none", "type", "rainbow", "zoom", "shake"]},
14
+ "font": {"type": "string"},
15
+ "color": {"type": "string", "pattern": "^(?:|#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6})$"},
16
+ "width": {"type": "string", "$comment": "Empty/trimmed empty resets; otherwise normalizeTextStyleValue requires Number(value) finite and positive. Preserve the existing STRING grammar, including hexadecimal and leading plus."},
17
+ "align": {"type": "string", "enum": ["", "left", "center", "right"]},
18
+ "loadingCostumeAt": {
19
+ "type": "object",
20
+ "properties": {"INDEX": {"type": "number"}},
21
+ "additionalProperties": false
22
+ }
23
+ }
24
+ }