narraleaf-studio 0.8.0 → 0.9.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/dist/_api.d.ts CHANGED
@@ -22,6 +22,8 @@ declare enum FsRejectErrorCode {
22
22
  NOT_A_FILE = "NOT_A_FILE",
23
23
  NOT_A_DIR = "NOT_A_DIR",
24
24
  IO_ERROR = "IO_ERROR",
25
+ /** The volume is full (`ENOSPC`). Its own code because it is the one an author can clear by hand. */
26
+ NO_SPACE = "NO_SPACE",
25
27
  IPC_ERROR = "IPC_ERROR",
26
28
  INVALID_JSON = "INVALID_JSON",
27
29
  UNKNOWN = "UNKNOWN",
@@ -568,6 +570,14 @@ type StoryActionPayload = {
568
570
  * reveal, and they are NOT migrated: such a row now declares and the object stays
569
571
  * invisible until a `show` row is added. `story/declared-never-shown` reports the ones
570
572
  * that never are.
573
+ *
574
+ * **A `show` that carries {@link assetId} is the one-row form**, and it is the exception the
575
+ * two-row rule needed: showing a picture that is not on stage yet otherwise meant going back
576
+ * for a `create` row, while `/show Alice` worked straight away - the same verb with a
577
+ * boundary nothing on screen drew. Such a row creates the image, names it via
578
+ * {@link objectName}, sources it and reveals it. It is not a second operation on purpose:
579
+ * one verb, one row shape, and the object it leaves behind is addressable by every later
580
+ * row exactly as a `create` row's is.
571
581
  */
572
582
  action: "image";
573
583
  operation: "create" | "setSource" | "show" | "hide";
@@ -660,6 +670,10 @@ type StoryActionPayload = {
660
670
  * `create` declares, like the `image` arm's: it compiles to `Video.preload()`, which puts a
661
671
  * hidden element on stage so the clip starts buffering, and `show` is what reveals it.
662
672
  *
673
+ * A `show` carrying {@link assetId} is the one-row form, on the same terms as the `image`
674
+ * arm's - it builds the clip, names it and reveals it. Revealing is still not playing: the
675
+ * element is on screen holding its first frame, and `/play` is what runs it.
676
+ *
663
677
  * Additive: the four transport operations and `timeMs` are new in A3, and no document
664
678
  * written before them carries either, so no schema bump.
665
679
  */
@@ -1660,6 +1674,24 @@ type BlueprintDiagnostic = {
1660
1674
  /** Source span or module location; M1 opaque */
1661
1675
  location?: Record<string, unknown>;
1662
1676
  };
1677
+ type BlueprintAssetPinKind = "image" | "audio" | "font";
1678
+ type BlueprintAssetPinRef = {
1679
+ kind: BlueprintAssetPinKind;
1680
+ paramKey?: string;
1681
+ /**
1682
+ * Where the pin's value comes from.
1683
+ *
1684
+ * `"stored"` (the default) means the document holds it: the id sits under `paramKey`, and it is
1685
+ * a reference the reverse-lookup index reads and reports.
1686
+ *
1687
+ * `"published"` means the host resolves it while the game runs, and **the pin provably cannot
1688
+ * carry a library asset id**. Nothing is stored to read, and an edge out of such a pin is not a
1689
+ * reference the index is failing to see - so it must not be reported as one. Marking a pin this
1690
+ * way is a claim about what can flow through it: it needs an argument at the declaration, not a
1691
+ * guess, because a wrong one hides a real reference.
1692
+ */
1693
+ origin?: "stored" | "published";
1694
+ };
1663
1695
  type AudioClipRegion = {
1664
1696
  /** Offset from the start of the clip, in milliseconds. Where playback starts. */
1665
1697
  inMs?: number;
@@ -1677,6 +1709,16 @@ type AudioClipRegion = {
1677
1709
  * degrades to the plain loop the two other markers already describe.
1678
1710
  */
1679
1711
  loopStartMs?: number;
1712
+ /**
1713
+ * Playback gain in decibels, never above 0: the engine cannot raise a clip past its own level, so
1714
+ * balancing loudness means bringing the loud clips down. Absent is unity. Folded into a volume
1715
+ * only by {@link clipVolume}.
1716
+ */
1717
+ gainDb?: number;
1718
+ };
1719
+ type StoredAudioClipRegion = Pick<AudioClipRegion, "inMs" | "outMs" | "loopStartMs">;
1720
+ type StoredAudioGain = {
1721
+ db: number;
1680
1722
  };
1681
1723
  type BlueprintOpenExternalRequest = {
1682
1724
  url: string;
@@ -1851,6 +1893,25 @@ declare const PluginRuntimeCapability: {
1851
1893
  * throwing.
1852
1894
  */
1853
1895
  readonly Diagnostics: "diagnostics";
1896
+ /**
1897
+ * `app.game.process.memory()` — read how much memory the game's processes hold, as the
1898
+ * operating system counts it.
1899
+ *
1900
+ * Its own capability rather than a member of `diagnostics`, because what it reports is not the
1901
+ * game's: `diagnostics` describes the engine's caches, which are the game's own bookkeeping,
1902
+ * while this is the player's machine - how much of its memory every process of the game is
1903
+ * holding, the main process and the GPU process included. An author who approved the first did
1904
+ * not approve the second, and a plugin that already holds `diagnostics` must not gain this by
1905
+ * an update without being asked again - which is what a capability of its own guarantees.
1906
+ *
1907
+ * Read-only, and numbers only: sizes and what each process is for, never a path, an id or a
1908
+ * command line. Nothing is sent anywhere by the host; what a plugin does with a reading is
1909
+ * visible in the code the author installed.
1910
+ *
1911
+ * Absent where there are no processes to count - the web export - and narrowed in Dev Mode to
1912
+ * the window's own renderer, since everything around it is Studio's.
1913
+ */
1914
+ readonly ProcessMemory: "process.memory";
1854
1915
  };
1855
1916
  type PluginRuntimeCapability = typeof PluginRuntimeCapability[keyof typeof PluginRuntimeCapability];
1856
1917
  /**
@@ -2313,6 +2374,58 @@ type GameMenuSpecMenu = {
2313
2374
  type GameMenuSpec = {
2314
2375
  readonly menus: readonly GameMenuSpecMenu[];
2315
2376
  };
2377
+ /**
2378
+ * How much memory a running game costs the machine it runs on, process by process.
2379
+ *
2380
+ * Read in the main process from Electron's process metrics (`app.getAppMetrics()`), which is the
2381
+ * operating system's own account: the same working set a task manager shows. Nothing a page can
2382
+ * read gives this. `performance.memory` describes one JavaScript heap, and in Electron it is also
2383
+ * rounded to coarse buckets - the two arms of a measured comparison both read exactly 92.9 MB -
2384
+ * while the memory a visual novel actually spends is mostly outside any heap:
2385
+ * decoded pictures, textures in the GPU process, audio buffers, the main process serving and
2386
+ * decrypting assets. The whole game is several processes, and only the process that owns them can
2387
+ * see all of them.
2388
+ *
2389
+ * `process.memoryUsage()` in the main process was the other candidate and answers a narrower
2390
+ * question - the main process's own heap and buffers - which is the one process a game's pictures
2391
+ * are not in.
2392
+ *
2393
+ * Comments in English per project convention.
2394
+ */
2395
+ /** What a process is for, in the words a reader of a memory report needs. */
2396
+ type GameProcessKind = "main" | "renderer" | "gpu" | "utility" | "other";
2397
+ type GameProcessMemoryEntry = {
2398
+ kind: GameProcessKind;
2399
+ /**
2400
+ * What the platform calls it, when it says: a utility process names its service ("Network
2401
+ * Service", "Audio Service", a sidecar's own name). Absent for the rest.
2402
+ */
2403
+ name?: string;
2404
+ /** Whether this is the process the asking page runs in. */
2405
+ current: boolean;
2406
+ /** Memory currently resident in physical RAM, in bytes. */
2407
+ workingSetBytes: number;
2408
+ /** The most this process has ever had resident, in bytes. */
2409
+ peakWorkingSetBytes: number;
2410
+ /** Memory no other process shares, in bytes. Windows reports it; other platforms do not. */
2411
+ privateBytes?: number;
2412
+ };
2413
+ type GameProcessMemoryReading = {
2414
+ /**
2415
+ * What the processes below add up to.
2416
+ *
2417
+ * - `game` - every process the game is made of. A packaged game and a preview.
2418
+ * - `window` - only the process that draws this window. Dev Mode: the window is Studio's, and
2419
+ * the processes around it (Studio's main process, its GPU process, its other windows) are
2420
+ * shared with everything else Studio has open, so counting them would report Studio.
2421
+ */
2422
+ scope: "game" | "window";
2423
+ processes: GameProcessMemoryEntry[];
2424
+ /** The sum of every process's working set, in bytes. */
2425
+ workingSetBytes: number;
2426
+ /** The sum of every process's private bytes, or null where the platform does not report them. */
2427
+ privateBytes: number | null;
2428
+ };
2316
2429
  declare const UI_INPUT_POINTER_GESTURES: readonly [
2317
2430
  "click",
2318
2431
  "doubleClick",
@@ -2813,6 +2926,20 @@ declare const en: {
2813
2926
  readonly error: "Error";
2814
2927
  readonly warning: "Warning";
2815
2928
  readonly untitled: "Untitled";
2929
+ readonly count: {
2930
+ readonly errors: {
2931
+ readonly one: "{count} error";
2932
+ readonly other: "{count} errors";
2933
+ };
2934
+ readonly warnings: {
2935
+ readonly one: "{count} warning";
2936
+ readonly other: "{count} warnings";
2937
+ };
2938
+ readonly infos: {
2939
+ readonly one: "{count} info";
2940
+ readonly other: "{count} info";
2941
+ };
2942
+ };
2816
2943
  };
2817
2944
  readonly menu: {
2818
2945
  readonly app: {
@@ -2991,11 +3118,13 @@ declare const en: {
2991
3118
  };
2992
3119
  };
2993
3120
  readonly windowIcon: {
2994
- readonly label: "Window icon";
2995
- readonly description: "The icon on Studio's windows and taskbar buttons. Desktop and Start menu shortcuts keep the installed icon.";
3121
+ readonly label: "App icon";
3122
+ readonly description: "The icon Studio shows on the taskbar and in the notification area. Desktop and Start menu shortcuts keep the installed icon.";
3123
+ readonly descriptionMac: "The icon in the Dock while Studio is running. Finder and Launchpad keep the installed icon.";
2996
3124
  readonly options: {
2997
- readonly default: "NarraLeaf";
2998
3125
  readonly narra: "Narra";
3126
+ readonly leafWhite: "Leaf on white";
3127
+ readonly leaf: "Leaf";
2999
3128
  };
3000
3129
  readonly unsupportedPlatform: "Not available on this operating system.";
3001
3130
  };
@@ -3040,10 +3169,6 @@ declare const en: {
3040
3169
  readonly monospace: "Monospace";
3041
3170
  };
3042
3171
  };
3043
- readonly editorSurfaceOpacity: {
3044
- readonly label: "Editor surface opacity";
3045
- readonly description: "Opacity of the surfaces behind story text and inspector fields.";
3046
- };
3047
3172
  readonly maxActiveEditors: {
3048
3173
  readonly label: "Maximum active editors";
3049
3174
  readonly description: "How many editor tabs stay loaded at once, keeping their scroll position and focus ({min}-{max}). The rest reload when reopened.";
@@ -3270,6 +3395,8 @@ declare const en: {
3270
3395
  readonly checking: "Checking\u2026";
3271
3396
  readonly done: "Done";
3272
3397
  readonly signOut: "Sign out";
3398
+ readonly usedBy: "Used by {projects}";
3399
+ readonly unused: "No project uses this sign-in.";
3273
3400
  readonly signIn: "Sign in";
3274
3401
  readonly signingIn: "Signing in\u2026";
3275
3402
  readonly addressLabel: "Server address";
@@ -3392,7 +3519,10 @@ declare const en: {
3392
3519
  readonly apply: "Apply";
3393
3520
  readonly exportHint: "Writes the settings to a plain JSON file. The workspace background, the name recorded on commits, recent projects, statistics and window layout stay on this machine.";
3394
3521
  readonly exported: "Saved to {path}";
3395
- readonly imported: "Applied {count} settings.";
3522
+ readonly imported: {
3523
+ readonly one: "Applied {count} setting.";
3524
+ readonly other: "Applied {count} settings.";
3525
+ };
3396
3526
  readonly exportFailed: "The settings could not be saved.";
3397
3527
  readonly importFailed: "The file could not be read.";
3398
3528
  readonly planSummary: "{change} to change, {same} already the same, {skipped} skipped.";
@@ -3580,7 +3710,15 @@ declare const en: {
3580
3710
  readonly openTable: "Open translation table";
3581
3711
  readonly progress: "{completed}/{total} translated";
3582
3712
  readonly staleCount: "{count} to review";
3583
- readonly importSummary: "Imported {applied} translations ({unchanged} unchanged, {unknown} unknown, {skippedEmpty} empty skipped)";
3713
+ readonly importCounts: "Imported {applied} ({unchanged} unchanged, {unknown} unknown, {skippedEmpty} empty skipped)";
3714
+ readonly translationCount: {
3715
+ readonly one: "{count} translation";
3716
+ readonly other: "{count} translations";
3717
+ };
3718
+ readonly readFailed: "The translations for {name} could not be read.";
3719
+ readonly alreadyAdded: "{name} is already in the language list.";
3720
+ readonly sourceLocked: "{name} is the source language. Remove the other languages first, or set another language as the source language.";
3721
+ readonly languageGone: "This language is no longer in the language list.";
3584
3722
  };
3585
3723
  readonly settings: {
3586
3724
  readonly menu: "Language settings\u2026";
@@ -3589,6 +3727,9 @@ declare const en: {
3589
3727
  readonly fallbackLabel: "Fallback language";
3590
3728
  readonly fallbackHint: "An entry with no translation here uses this language, then the source language.";
3591
3729
  readonly fallbackLoops: "leads back here";
3730
+ readonly fallbackSelf: "A language cannot be its own fallback language.";
3731
+ readonly fallbackGone: "The fallback language is no longer in the language list.";
3732
+ readonly fallbackLoop: "{fallback} leads back to {name}.";
3592
3733
  };
3593
3734
  readonly exchange: {
3594
3735
  readonly exportMenu: "Export translations\u2026";
@@ -3608,12 +3749,16 @@ declare const en: {
3608
3749
  readonly scopeAll: "Everything";
3609
3750
  readonly scopePending: "Untranslated and to review";
3610
3751
  readonly exportAction: "Export";
3611
- readonly exportDone: "Exported {count} lines to {path}";
3752
+ readonly exportDone: {
3753
+ readonly one: "Exported {count} line to {path}";
3754
+ readonly other: "Exported {count} lines to {path}";
3755
+ };
3612
3756
  readonly exportEmpty: "Nothing to export.";
3613
- readonly importFailed: "Could not read the file";
3614
3757
  readonly importUnsupported: "Studio reads CSV, XLIFF, PO and JSON.";
3615
- readonly importNoRows: "No translation units in this file";
3616
- readonly importWarnings: "{count} entries were skipped. First: {first}";
3758
+ readonly importWarnings: {
3759
+ readonly one: "{count} entry was skipped. First: {first}";
3760
+ readonly other: "{count} entries were skipped. First: {first}";
3761
+ };
3617
3762
  readonly localeMismatch: "This file is for {declared}. Import it into {name}?";
3618
3763
  readonly localeMismatchDetail: "The translations are imported into the selected language regardless of what the file declares.";
3619
3764
  };
@@ -3637,6 +3782,7 @@ declare const en: {
3637
3782
  readonly keyNamePlaceholder: "Key (menu.start\u2026)";
3638
3783
  readonly keySourcePlaceholder: "Source text";
3639
3784
  readonly invalidKeyName: "Key names may contain letters, digits, and dots/underscores/hyphens between them.";
3785
+ readonly keyExists: "A key named \u201C{name}\u201D already exists.";
3640
3786
  readonly removeKey: "Remove key";
3641
3787
  readonly removeKeyConfirm: "Remove {name}?";
3642
3788
  readonly removeKeyConfirmDetail: "Existing translations of this key stay in the language files.";
@@ -3695,11 +3841,25 @@ declare const en: {
3695
3841
  readonly importAudio: "Import audio\u2026";
3696
3842
  readonly exportDone: "Exported to {path}";
3697
3843
  readonly pickupEmpty: "No outdated lines to re-record.";
3698
- readonly importSummary: "Linked {linked} takes ({unmatched} unmatched, {failed} failed)";
3844
+ readonly importCounts: "Linked {linked} ({unmatched} unmatched, {failed} failed)";
3845
+ readonly takeCount: {
3846
+ readonly one: "{count} take";
3847
+ readonly other: "{count} takes";
3848
+ };
3699
3849
  readonly importFailed: "Could not import the audio files";
3700
3850
  readonly importScript: "Import recording script\u2026";
3701
- readonly importScriptSummary: "Applied {applied} rows ({unchanged} unchanged, {unknown} not voiced)";
3702
- readonly importScriptFailed: "Could not read that recording script";
3851
+ readonly importScriptCounts: "Applied {applied} ({unchanged} unchanged, {unknown} not voiced)";
3852
+ readonly scriptRowCount: {
3853
+ readonly one: "{count} row";
3854
+ readonly other: "{count} rows";
3855
+ };
3856
+ readonly importScriptSkipped: {
3857
+ readonly one: "{count} row was skipped";
3858
+ readonly other: "{count} rows were skipped";
3859
+ };
3860
+ readonly readFailed: "The voice assignments for {name} could not be read.";
3861
+ readonly alreadyAdded: "{name} is already in the voice language list.";
3862
+ readonly languageGone: "This voice language is no longer in the list.";
3703
3863
  readonly namingTitle: "Recording filename pattern";
3704
3864
  readonly namingHint: "Tokens: {tokens}. Imported audio is matched to lines by this name.";
3705
3865
  readonly namingReset: "Reset to default";
@@ -3782,7 +3942,7 @@ declare const en: {
3782
3942
  };
3783
3943
  readonly details: {
3784
3944
  readonly noStories: "No stories in this project.";
3785
- readonly storiesRead: "{count} story documents read.";
3945
+ readonly storiesRead: "Story documents read: {count}.";
3786
3946
  };
3787
3947
  readonly tools: {
3788
3948
  readonly title: "Tools";
@@ -3839,7 +3999,7 @@ declare const en: {
3839
3999
  readonly scope: {
3840
4000
  readonly storyScene: "scene";
3841
4001
  readonly storyMotion: "motion";
3842
- readonly audioLoop: "audio markers";
4002
+ readonly audioLoop: "audio markers and gain";
3843
4003
  readonly uiSurface: "interface";
3844
4004
  readonly blueprint: "blueprint";
3845
4005
  readonly project: "project";
@@ -3857,6 +4017,7 @@ declare const en: {
3857
4017
  readonly storySpeakerChange: "speaker change";
3858
4018
  readonly storyMotionEdit: "motion edit";
3859
4019
  readonly audioMarkers: "marker change";
4020
+ readonly audioGain: "gain change";
3860
4021
  readonly surfaceEdit: "interface edit";
3861
4022
  readonly blueprintEdit: "blueprint edit";
3862
4023
  readonly replaceText: "text replacement";
@@ -3871,6 +4032,9 @@ declare const en: {
3871
4032
  readonly destination: "Server";
3872
4033
  readonly projectOnServer: "Project name: {name}";
3873
4034
  readonly noAccountHere: "This machine has no account on that server.";
4035
+ readonly signInUnused: "This project does not use the sign-in for that server.";
4036
+ readonly useSignIn: "Use the sign-in as {name}\u2026";
4037
+ readonly signOutHint: "Only this project is signed out. Other projects that use this sign-in are not affected.";
3874
4038
  readonly manage: "Manage servers\u2026";
3875
4039
  readonly notThere: "That server does not hold this project.";
3876
4040
  readonly connected: "Connected";
@@ -3888,6 +4052,7 @@ declare const en: {
3888
4052
  readonly liveBlockedMerge: "Finish the merge to start or join a live session.";
3889
4053
  readonly liveBlockedRecovery: "Live sessions are unavailable in recovery mode.";
3890
4054
  readonly liveBlockedSession: "This workspace is already in a live session.";
4055
+ readonly liveBlockedTakenOver: "This project is now open in another NarraLeaf Studio.";
3891
4056
  readonly liveHost: "Host";
3892
4057
  readonly liveGuest: "Guest";
3893
4058
  readonly liveEntering: "Entering";
@@ -3979,6 +4144,12 @@ declare const en: {
3979
4144
  readonly projectLockedTitle: "This project is open in another NarraLeaf Studio";
3980
4145
  readonly projectLockedHere: "Open on this computer since {time}. Retry once it has been closed there.";
3981
4146
  readonly projectLockedElsewhere: "Open on {host} since {time}. Retry once it has been closed there.";
4147
+ readonly projectTakenOverTitle: "This project is now open in another NarraLeaf Studio";
4148
+ readonly projectTakenOverHere: "Open on this computer since {time}. Changes in this window are no longer saved. Retry once it has been closed there.";
4149
+ readonly projectTakenOverElsewhere: "Open on {host} since {time}. Changes in this window are no longer saved. Retry once it has been closed there.";
4150
+ readonly projectDisplacedTitle: "This project was opened in another NarraLeaf Studio";
4151
+ readonly projectDisplacedHere: "Opened on this computer at {time}. Changes in this window are no longer saved. Retry to reopen the project.";
4152
+ readonly projectDisplacedElsewhere: "Opened on {host} at {time}. Changes in this window are no longer saved. Retry to reopen the project.";
3982
4153
  readonly openLauncher: "Open launcher";
3983
4154
  readonly panelRenderError: "This panel hit a rendering error";
3984
4155
  readonly mainEditorRegion: "Main editor";
@@ -4069,6 +4240,10 @@ declare const en: {
4069
4240
  readonly blurOff: "Off";
4070
4241
  readonly fillMode: "Fill";
4071
4242
  readonly anchor: "Position";
4243
+ readonly editorPlate: "Editor background";
4244
+ readonly editorPlateOpacity: "Editor background opacity";
4245
+ readonly sidebarPlate: "Sidebar background";
4246
+ readonly sidebarPlateOpacity: "Sidebar background opacity";
4072
4247
  readonly fill: {
4073
4248
  readonly cover: "Scale to fill";
4074
4249
  readonly contain: "Fit";
@@ -4123,8 +4298,14 @@ declare const en: {
4123
4298
  readonly retrySave: "Retry saving now";
4124
4299
  readonly resetZoom: "Reset zoom to 100%";
4125
4300
  readonly shortcuts: "Keyboard shortcuts";
4126
- readonly words: "{count} words";
4127
- readonly lines: "{count} lines";
4301
+ readonly words: {
4302
+ readonly one: "{count} word";
4303
+ readonly other: "{count} words";
4304
+ };
4305
+ readonly lines: {
4306
+ readonly one: "{count} line";
4307
+ readonly other: "{count} lines";
4308
+ };
4128
4309
  readonly noStoryOpen: "No story open";
4129
4310
  readonly openDashboard: "Open the project dashboard";
4130
4311
  readonly openCurrentScene: "Open the current scene";
@@ -4147,16 +4328,39 @@ declare const en: {
4147
4328
  };
4148
4329
  };
4149
4330
  readonly save: {
4150
- readonly failedTitle: "Could not save {file}";
4151
- readonly failedDetailTransient: "Still retrying in the background. {error}";
4152
- readonly failedDetailPermanent: "Retrying fails until this is fixed. {error}";
4331
+ readonly failedTitle: "Could not save the {name}";
4332
+ readonly failedTitleNamed: "Could not save \u201C{name}\u201D";
4333
+ readonly failedTitleUnnamed: "Could not save a file";
4334
+ readonly failedDetailTransient: "Still retrying in the background.";
4335
+ readonly failedDetailPermanent: "Retrying fails until this is fixed.";
4336
+ readonly failedDetailNotSaved: "The change was not saved.";
4337
+ readonly failedDetailWithReason: "{reason} {retry}";
4338
+ readonly refusedUnreadable: "The {name} could not be read. Changes are not saved.";
4339
+ readonly fileFailed: {
4340
+ readonly plain: "Could not save \u201C{name}\u201D.";
4341
+ readonly withReason: "Could not save \u201C{name}\u201D. {reason}";
4342
+ };
4343
+ readonly storeFailed: {
4344
+ readonly plain: "Could not save the {name}.";
4345
+ readonly withReason: "Could not save the {name}. {reason}";
4346
+ };
4347
+ readonly reason: {
4348
+ readonly permissionDenied: "The file is read-only, or Studio is not allowed to write to it.";
4349
+ readonly folderMissing: "The folder it belongs in no longer exists.";
4350
+ readonly diskFull: "The disk is full.";
4351
+ };
4153
4352
  readonly retry: "Retry now";
4154
4353
  readonly consoleFailed: "write failed ({code}, attempt {attempt}): {path} \u00B7 {error}";
4354
+ readonly consoleFailedNotRetried: "write failed ({code}), not retried: {path} \u00B7 {error}";
4155
4355
  readonly consoleRecovered: "write succeeded: {path}";
4156
4356
  readonly flushFailed: "could not flush {label}: {error}";
4157
- readonly unreadableTitle: "Could not read {file}";
4357
+ readonly unreadableTitle: "Could not read the {name}";
4158
4358
  readonly unreadableDetail: "{reason} The file is unchanged. Nothing was written over it.";
4159
- readonly unreadableDetailQuarantined: "{reason} The file is unchanged. A copy of it is at {path}.";
4359
+ readonly unreadableDetailQuarantined: "{reason} The file is unchanged, and a copy of it has been kept.";
4360
+ readonly unreadableReason: {
4361
+ readonly damaged: "The file is damaged or is not in a format Studio can read.";
4362
+ readonly newerVersion: "It was saved by a newer version of NarraLeaf Studio.";
4363
+ };
4160
4364
  readonly consoleUnreadable: "read failed ({kind}): {path} \u00B7 {reason}";
4161
4365
  readonly consoleQuarantined: "kept a copy of the unreadable file at {path}";
4162
4366
  readonly frozenTitle: "Changes are not being saved";
@@ -4184,9 +4388,68 @@ declare const en: {
4184
4388
  readonly characters: "characters";
4185
4389
  readonly project: "project settings";
4186
4390
  readonly assets: "asset library";
4391
+ readonly projectIcon: "project icon";
4392
+ readonly panelLayout: "panel layout";
4393
+ readonly recentColors: "recent colors";
4394
+ readonly pluginData: "plugin data";
4187
4395
  readonly openEditors: "the open editor";
4188
4396
  };
4189
4397
  };
4398
+ readonly import: {
4399
+ readonly failed: "Could not import \u201C{name}\u201D.";
4400
+ readonly withReason: "{headline} {reason}";
4401
+ readonly reason: {
4402
+ readonly missing: "The file no longer exists.";
4403
+ readonly accessDenied: "Studio is not allowed to read the file.";
4404
+ readonly empty: "The file is empty.";
4405
+ readonly noIdColumn: "The file has no unit_id column.";
4406
+ readonly notFormat: "The file is not a readable {format} file.";
4407
+ readonly noRows: "The file has no entries to import.";
4408
+ readonly wrongType: "Files ending in .{ext} cannot be imported here.";
4409
+ readonly cannotDisplay: "NarraLeaf cannot display .{ext} files. Convert the file to {first} or {second} before importing.";
4410
+ readonly cannotPlay: "NarraLeaf cannot play .{ext} files. Convert the file to {first} or {second} before importing.";
4411
+ readonly cannotUse: "NarraLeaf cannot use .{ext} files. Convert the file to {first} or {second} before importing.";
4412
+ readonly mismatch: "The file is named .{ext}, but its contents are {actual}.";
4413
+ readonly copyFailed: "It could not be copied into the project folder.";
4414
+ readonly projectReadOnly: "Studio is not allowed to write to the project folder.";
4415
+ readonly notAFolder: "Models are imported as a folder.";
4416
+ readonly emptyFolder: "The folder is empty.";
4417
+ readonly copyIncomplete: "Not every file in the folder could be copied into the project.";
4418
+ readonly projectNotAccepting: "The project is not accepting changes right now.";
4419
+ };
4420
+ readonly remote: {
4421
+ readonly invalidUrl: "The address is not a valid URL.";
4422
+ readonly unsupportedScheme: "Only http and https addresses can be downloaded.";
4423
+ readonly unreachable: "Studio could not connect to the server.";
4424
+ readonly timeout: "The server did not respond within {seconds} seconds.";
4425
+ readonly notFound: "The server has no file at this address.";
4426
+ readonly accessDenied: "The server does not allow this file to be downloaded.";
4427
+ readonly serverError: "The server reported an error. Try again later.";
4428
+ readonly refused: "The server did not accept the request.";
4429
+ readonly tooLarge: "The file is larger than {limit} MB, the limit for a remote asset.";
4430
+ readonly noContent: "The server returned no content.";
4431
+ readonly bundle: "Models cannot be imported from a URL.";
4432
+ readonly unplayableCodecs: "NarraLeaf cannot play {codecs}. Convert the file and import the converted copy from disk.";
4433
+ readonly unplayableContainer: "NarraLeaf cannot open {container} files. Convert the file and import the converted copy from disk.";
4434
+ readonly unplayableFormat: "NarraLeaf cannot open this file's format. Convert the file and import the converted copy from disk.";
4435
+ readonly unplayableNoStreams: "The file holds no sound or picture that NarraLeaf can play.";
4436
+ readonly webPage: "The address returned a web page instead of a file.";
4437
+ readonly unrecognizedImage: "The address did not return an image in a format NarraLeaf recognizes.";
4438
+ readonly unrecognizedMedia: "The address did not return sound or video in a format NarraLeaf recognizes.";
4439
+ readonly unrecognizedFont: "The address did not return a font in a format NarraLeaf recognizes.";
4440
+ };
4441
+ readonly skipped: {
4442
+ readonly missingId: "An entry has no ID.";
4443
+ readonly missingIdAtRow: "Row {n} has no ID.";
4444
+ readonly missingIdAtEntry: "Entry {n} has no ID.";
4445
+ readonly notEntry: "An entry is not a translation.";
4446
+ readonly notEntryAtEntry: "Entry {n} is not a translation.";
4447
+ readonly unreadableLine: "Line {n} could not be read.";
4448
+ readonly noTake: "A row is for a line that is not voiced. Its note and status were not applied.";
4449
+ readonly noTakeAtRow: "Row {n} is for a line that is not voiced. Its note and status were not applied.";
4450
+ };
4451
+ };
4452
+ readonly fileDialogFailed: "The file dialog could not be opened.";
4190
4453
  readonly reload: {
4191
4454
  readonly failedTitle: "The project was not fully reloaded";
4192
4455
  readonly failedDetail: "These still show their previous contents: {stores}. Reopen the project to read them again.";
@@ -4268,7 +4531,7 @@ declare const en: {
4268
4531
  readonly moved: "Moved";
4269
4532
  readonly copied: "Copied";
4270
4533
  };
4271
- readonly changeFromPath: "from {path}";
4534
+ readonly changeFrom: "from {name}";
4272
4535
  readonly changeConflict: "Unresolved conflict";
4273
4536
  readonly changesMore: "{count} more not shown";
4274
4537
  readonly command: {
@@ -4278,7 +4541,10 @@ declare const en: {
4278
4541
  readonly compareChanges: "Compare Changes with the Last Version";
4279
4542
  };
4280
4543
  readonly filterPlaceholder: "Find a version by name or number";
4281
- readonly filterNoMatch: "No match in the {count} versions read so far.";
4544
+ readonly filterNoMatch: {
4545
+ readonly one: "No match in the {count} version read so far.";
4546
+ readonly other: "No match in the {count} versions read so far.";
4547
+ };
4282
4548
  readonly today: "Today";
4283
4549
  readonly yesterday: "Yesterday";
4284
4550
  readonly compareBase: {
@@ -4287,7 +4553,10 @@ declare const en: {
4287
4553
  readonly current: "Comparing with {version}";
4288
4554
  readonly compare: "Compare with {version}";
4289
4555
  };
4290
- readonly showCheckpoints: "Show {count} checkpoints";
4556
+ readonly showCheckpoints: {
4557
+ readonly one: "Show {count} checkpoint";
4558
+ readonly other: "Show {count} checkpoints";
4559
+ };
4291
4560
  readonly hideCheckpoints: "Hide checkpoints";
4292
4561
  readonly systemMessage: {
4293
4562
  readonly unnamed: "Unnamed version";
@@ -4361,6 +4630,7 @@ declare const en: {
4361
4630
  readonly nameTaken: "Another project on that server is already called that. Choose a different name.";
4362
4631
  readonly alreadyPublished: "This project is already on that server as {name}, and that server gives a project one name.";
4363
4632
  readonly unknown: "That server did not record the project.";
4633
+ readonly declined: "This project does not use the sign-in for that server, so the project was not recorded.";
4364
4634
  readonly connectedAs: "This project is already on that server as {name}, so it is connected under that name. Use Send to put this machine's versions on top of what is there.";
4365
4635
  };
4366
4636
  readonly signIn: {
@@ -4579,6 +4849,10 @@ declare const en: {
4579
4849
  readonly deleteSceneConfirm: "Delete scene \"{name}\"?";
4580
4850
  readonly deleteSceneDetail: "This removes the scene and its blocks. Jumps to it will stop resolving.";
4581
4851
  };
4852
+ readonly readFailed: {
4853
+ readonly missing: "This story is no longer in this project.";
4854
+ readonly named: "The story \u201C{name}\u201D could not be read.";
4855
+ };
4582
4856
  readonly script: {
4583
4857
  readonly exportScene: "Export as Script\u2026";
4584
4858
  readonly exportStory: "Export Story as Script\u2026";
@@ -4755,7 +5029,11 @@ declare const en: {
4755
5029
  readonly paste: {
4756
5030
  readonly title: "Paste as Rows";
4757
5031
  readonly action: "Paste";
4758
- readonly totals: "{dialogue} dialogue \u00B7 {narration} narration \u00B7 {created} new characters";
5032
+ readonly totalCounts: "{dialogue} dialogue \u00B7 {narration} narration \u00B7 {created}";
5033
+ readonly newCharacterCount: {
5034
+ readonly one: "{count} new character";
5035
+ readonly other: "{count} new characters";
5036
+ };
4759
5037
  readonly lineCount: {
4760
5038
  readonly one: "{count} line";
4761
5039
  readonly other: "{count} lines";
@@ -5423,7 +5701,6 @@ declare const en: {
5423
5701
  readonly sceneMusicIntroLoop: "Plays {from}s, loops {loop}s to {to}s";
5424
5702
  readonly sceneMusicFromIn: "Starts at {from}s";
5425
5703
  readonly sceneMusicWholeClip: "Whole clip";
5426
- readonly backgroundResolveError: "Image asset could not be resolved: {error}";
5427
5704
  readonly selectDefaultBackground: "Select Default Background";
5428
5705
  readonly tabInvalid: "Story scene editor tab is invalid.";
5429
5706
  readonly loadingScene: "Loading story scene\u2026";
@@ -5467,7 +5744,7 @@ declare const en: {
5467
5744
  readonly videoSkipped: "Videos are not previewed.";
5468
5745
  readonly ambienceSkipped: "Ambience effects are not previewed.";
5469
5746
  readonly storyActionSkipped: "Story Action Blueprint effects are not simulated in the preview.";
5470
- readonly displayableNotFound: "Displayable target not found: {target}";
5747
+ readonly displayableNotFound: "\u201C{target}\u201D is not on stage.";
5471
5748
  readonly displayableUnnamed: "(empty)";
5472
5749
  readonly persistentAssignmentSkipped: "Persistent-variable assignments are not applied in the preview.";
5473
5750
  readonly assignmentUnresolved: "Expression `{expression}` did not resolve; the assignment was skipped in the preview.";
@@ -5475,10 +5752,142 @@ declare const en: {
5475
5752
  readonly persistentReadEmpty: "Persistent variables read as empty in the preview.";
5476
5753
  readonly sceneVisitUntracked: "Scene visits are not tracked in the preview; `visited({name})` reads as false.";
5477
5754
  readonly choicePickUntracked: "Choice picks are not tracked in the preview; `picked({name})` reads as false.";
5478
- readonly presetNotFoldable: "{preset} transforms cannot be folded into character show yet.";
5479
- readonly animationNotFound: "Story animation not found: {animationId}";
5480
- readonly animationIdMissing: "Animation transform is missing animationId.";
5755
+ readonly presetNotFoldable: "An entrance cannot apply {preset}; use a separate transform row.";
5756
+ readonly animationNotFound: "The animation this row plays is no longer in this project.";
5757
+ readonly animationIdMissing: "This animation transform names no animation.";
5758
+ };
5759
+ };
5760
+ readonly compile: {
5761
+ readonly asset: {
5762
+ readonly missing: "{owner} refers to an asset that is no longer in this project.";
5763
+ readonly unreadable: "{owner} refers to an asset that could not be read.";
5764
+ readonly unreadableNamed: "{owner} refers to \u201C{asset}\u201D, which could not be read.";
5765
+ readonly notAsset: "{owner} is set to a value that is not an asset.";
5766
+ };
5767
+ readonly owner: {
5768
+ readonly row: "This row";
5769
+ readonly pose: "The pose \u201C{pose}\u201D of \u201C{character}\u201D";
5770
+ readonly layer: "The layer \u201C{layer}\u201D of \u201C{character}\u201D";
5771
+ readonly layerTag: "The \u201C{tag}\u201D image of the layer \u201C{layer}\u201D of \u201C{character}\u201D";
5772
+ readonly avatar: "The dialog avatar of \u201C{character}\u201D";
5773
+ readonly model: "The model of \u201C{character}\u201D";
5774
+ readonly sceneBackground: "The background of the scene \u201C{scene}\u201D";
5775
+ readonly sceneMusic: "The music of the scene \u201C{scene}\u201D";
5776
+ readonly voice: "The {language} voice recording of this line";
5777
+ readonly inlineSound: "The sound effect inside this line";
5778
+ };
5779
+ readonly unnamed: "(unnamed)";
5780
+ readonly unnamedCharacter: "Unnamed character";
5781
+ readonly variable: {
5782
+ readonly persistentCollision: "The persistent variable \u201C{name}\u201D is declared both in the project's variables and by a story row; references to it are ambiguous.";
5783
+ readonly savedCollision: "The saved variable \u201C{name}\u201D is declared both in the project's variables and by a story row; references to it are ambiguous.";
5784
+ readonly sceneMissingInText: "A scene variable shown in this line is no longer declared; it was left out.";
5785
+ readonly savedMissingInText: "A saved variable shown in this line is no longer declared; it was left out.";
5786
+ readonly persistentMissingInText: "A persistent variable shown in this line is no longer declared; it was left out.";
5787
+ readonly sceneMissingAssignment: "The scene variable this row assigns is no longer declared; the assignment was skipped.";
5788
+ readonly savedMissingAssignment: "The saved variable this row assigns is no longer declared; the assignment was skipped.";
5789
+ readonly persistentMissingAssignment: "The persistent variable this row assigns is no longer declared; the assignment was skipped.";
5790
+ readonly sceneMissingCondition: "The scene variable this condition tests is no longer declared; it evaluates false.";
5791
+ readonly savedMissingCondition: "The saved variable this condition tests is no longer declared; it evaluates false.";
5792
+ readonly persistentMissingCondition: "The persistent variable this condition tests is no longer declared; it evaluates false.";
5793
+ readonly persistenceUnavailableInText: "Persistent variables cannot be read here; the value was left out.";
5794
+ readonly persistenceUnavailableAssignment: "Persistent variables cannot be written here; the assignment was skipped.";
5795
+ };
5796
+ readonly expression: {
5797
+ readonly inTextUnresolved: "The value `{expression}` in this line did not resolve; it was left out.";
5798
+ readonly assignmentUnresolved: "Expression `{expression}` did not resolve; the assignment was skipped.";
5799
+ readonly conditionUnresolved: "Condition `{expression}` did not resolve; it evaluates false.";
5800
+ readonly callNeedsBlueprints: "Expression calls `{name}()`, which needs the project's blueprints; the expression was skipped.";
5801
+ };
5802
+ readonly blueprint: {
5803
+ readonly textNeedsBlueprints: "A blueprint value in this line needs the project's blueprints; it was left out.";
5804
+ readonly actionNeedsBlueprints: "This story action blueprint needs the project's blueprints; the row was skipped.";
5805
+ readonly conditionNeedsBlueprints: "This blueprint condition needs the project's blueprints; it evaluates false.";
5806
+ readonly missing: "The story action blueprint of this row is no longer in this project; the row was skipped.";
5807
+ readonly asyncValue: "\u201C{name}\u201D is evaluated where the story cannot wait, so {file} has to return a value rather than a promise.";
5808
+ readonly noDefaultExportSkipped: "{file} has no default export for \u201C{name}\u201D to run; the row was skipped.";
5809
+ readonly noDefaultExportEmpty: "{file} has no default export for \u201C{name}\u201D to run; nothing was evaluated.";
5810
+ };
5811
+ readonly flow: {
5812
+ readonly missingRow: "A row this scene refers to is missing from the story.";
5813
+ readonly pluginInjectionOutsideScene: "A plugin's compile step addressed a row that is not in this scene.";
5814
+ readonly optionOutsideMenu: "This option is not inside a menu.";
5815
+ readonly branchOutsideCondition: "This branch is not inside a condition.";
5816
+ readonly jumpTargetMissing: "The scene this jump goes to is no longer in this story.";
5817
+ readonly jumpTargetEmpty: "This jump names no scene.";
5818
+ readonly previewHoldsAtJump: "The preview stops before this jump instead of leaving the scene.";
5819
+ readonly previewHoldsAtBreak: "The preview stops at this break; a break only acts inside its loop.";
5820
+ readonly menuEmpty: "This menu has no options.";
5821
+ readonly conditionEmpty: "This condition has no branch.";
5822
+ readonly labelUnnamed: "This label has no name.";
5823
+ readonly labelDuplicate: "Label \u201C{name}\u201D is declared more than once in this scene.";
5824
+ readonly gotoEmpty: "This go-to names no label.";
5825
+ readonly gotoMissing: "Label \u201C{name}\u201D is not in this scene.";
5826
+ readonly breakOutsideLoop: "This break is not inside a repeat; there is no loop for it to leave.";
5827
+ readonly repeatUntilUnusable: "This repeat-until has no usable condition; the group was skipped.";
5828
+ readonly sharedSceneVariables: "The scenes \u201C{first}\u201D and \u201C{second}\u201D keep their scene variables under one name, so each overwrites the other's.";
5829
+ readonly sharedSceneVariablesSameName: "The two scenes named \u201C{name}\u201D keep their scene variables under one name, so each overwrites the other's.";
5830
+ };
5831
+ readonly notOnStage: {
5832
+ readonly image: "Image \u201C{name}\u201D is not on stage; an earlier row has to create it.";
5833
+ readonly text: "Text \u201C{name}\u201D is not on stage; an earlier row has to create it.";
5834
+ readonly layer: "Layer \u201C{name}\u201D is not on stage; an earlier row has to create it.";
5835
+ readonly video: "Video \u201C{name}\u201D is not on stage; an earlier row has to create it.";
5836
+ readonly ambience: "Ambience effect \u201C{name}\u201D is not on stage; an earlier row has to create it.";
5837
+ readonly character: "Character \u201C{name}\u201D is not on stage; an earlier row has to bring it on stage.";
5838
+ readonly characterGone: "The character this row acts on is no longer in this project, so it is not on stage.";
5839
+ readonly target: "\u201C{name}\u201D is not on stage.";
5840
+ };
5841
+ readonly character: {
5842
+ readonly missing: "The character of this row is no longer in this project.";
5843
+ readonly noPoses: "\u201C{character}\u201D has no poses, so nothing is drawn.";
5844
+ readonly poseMissing: "The pose this row selects is no longer among the poses of \u201C{character}\u201D.";
5845
+ readonly poseNoImage: "The pose \u201C{pose}\u201D of \u201C{character}\u201D has no image.";
5846
+ readonly noImage: "No image resolves for \u201C{character}\u201D.";
5847
+ readonly singleImage: "\u201C{character}\u201D is on stage as a single image, so its appearance tags cannot change here.";
5848
+ readonly noTagSelected: "This appearance change for \u201C{character}\u201D selects no tag; nothing changes.";
5849
+ readonly noRuntimeMotion: "\u201C{character}\u201D is not drawn by a runtime, so it has no motion to set.";
5850
+ readonly noRuntimeSkin: "\u201C{character}\u201D is not drawn by a runtime, so it has no skin to set.";
5851
+ readonly noRuntimeParams: "\u201C{character}\u201D is not drawn by a runtime, so it has no parameters to set.";
5852
+ readonly puppetNoModel: "\u201C{character}\u201D has no model.";
5853
+ readonly puppetNoRuntime: "\u201C{character}\u201D names no runtime; nothing draws it.";
5854
+ readonly inlineNoCharacter: "An appearance change inside this line names no character; it was skipped.";
5855
+ readonly inlineSingleImage: "An appearance change for \u201C{character}\u201D inside this line was skipped: the character is on stage as a single image, so its appearance tags cannot change.";
5856
+ readonly inlineNotOnStage: "An appearance change for \u201C{character}\u201D inside this line was skipped: the character is not on stage. Show it before this line; a character shown under a custom stage name cannot be changed from inside a line.";
5857
+ };
5858
+ readonly media: {
5859
+ readonly backgroundEmpty: "This background has no image or color.";
5860
+ readonly imageNoSource: "Image \u201C{name}\u201D has no asset or color.";
5861
+ readonly videoNoAsset: "Video \u201C{name}\u201D has no asset.";
5862
+ readonly soundNoAsset: "Sound \u201C{name}\u201D has no asset.";
5863
+ readonly soundNotPlaying: "Sound \u201C{name}\u201D is not playing; an earlier sound row has to start it.";
5864
+ readonly musicNotPlaying: "No background music is set before this row; a BGM row has to run first.";
5865
+ readonly trackConflict: "\u201C{name}\u201D is already playing on the track \u201C{existing}\u201D, so this row's track \u201C{requested}\u201D is ignored. Use a different sound name, or set the track on the row that starts it.";
5866
+ readonly removedTrack: "(removed track)";
5867
+ readonly ambienceClipConflict: "Ambience effect \u201C{name}\u201D already plays a different clip; this row uses the first one.";
5868
+ readonly ambienceNoClip: "Ambience effect \u201C{name}\u201D has no clip.";
5869
+ readonly weatherUnavailable: "Ambience effect \u201C{name}\u201D needs its weather produced, which is not possible here.";
5870
+ readonly weatherFailed: "The weather for ambience effect \u201C{name}\u201D could not be produced.";
5871
+ readonly assetSetWithoutLanguage: "An asset set was resolved without a language; the stage may show the member for another language.";
5872
+ };
5873
+ readonly camera: {
5874
+ readonly noTransform: "This camera row has no transform.";
5875
+ readonly unknownLens: "Camera lens \u201C{preset}\u201D is not a known effect.";
5876
+ readonly unknownLook: "Camera look \u201C{preset}\u201D is not a known grade.";
5481
5877
  };
5878
+ readonly transform: {
5879
+ readonly loopNoReveal: "A looping transform cannot carry a clip reveal; the reveal is ignored.";
5880
+ readonly loopNotInterpolable: "A looping transform can only animate channels that interpolate; the rest of this row does not change.";
5881
+ readonly loopEmpty: "This looping transform states nothing to animate.";
5882
+ readonly loopUnsupported: "This engine version cannot play looping transforms. Update narraleaf-react to 0.32.0 or newer.";
5883
+ readonly filterConflict: "This transform sets the CSS filter channel more than once; only one reaches the stage.";
5884
+ readonly maskNoImage: "This mask effect has no image.";
5885
+ readonly maskOnEntrance: "An entrance cannot apply a mask; use a separate transform row.";
5886
+ readonly revealTargetOnly: "{effect} only applies to stage objects.";
5887
+ readonly ruleNoImage: "This rule transition names no rule image; the change was played as a cut.";
5888
+ readonly transitionUnavailable: "Transition \u201C{kind}\u201D is not available; the change was played as a cut. Choose a transition on this row.";
5889
+ };
5890
+ readonly positionalElementNames: "This engine version names stage elements by position, so saves do not survive edits to the story.";
5482
5891
  };
5483
5892
  readonly blueprintCard: {
5484
5893
  readonly openAria: "Open story action blueprint";
@@ -5563,7 +5972,7 @@ declare const en: {
5563
5972
  };
5564
5973
  readonly show: {
5565
5974
  readonly label: "Show";
5566
- readonly detail: "Show a character or a stage object";
5975
+ readonly detail: "Show a character, a stage object, or an image or video from the asset library";
5567
5976
  };
5568
5977
  readonly hide: {
5569
5978
  readonly label: "Hide";
@@ -6542,7 +6951,6 @@ declare const en: {
6542
6951
  readonly change: "Change";
6543
6952
  readonly select: "Select";
6544
6953
  readonly clearImage: "Clear image";
6545
- readonly assetError: "Image asset could not be resolved: {error}";
6546
6954
  readonly selectImageTitle: "Select Background Image";
6547
6955
  };
6548
6956
  readonly control: {
@@ -6616,6 +7024,8 @@ declare const en: {
6616
7024
  readonly moreReferences: "\u2026and {count} more";
6617
7025
  readonly unverifiedTitle: "Cannot check what uses these assets";
6618
7026
  readonly unverifiedMessage: "Their usage could not be determined. Delete them anyway?";
7027
+ readonly unverifiedComputed: "These places pick their asset by a name assembled at run time:";
7028
+ readonly unverifiedUnreadable: "These could not be read:";
6619
7029
  readonly confirmTitle: {
6620
7030
  readonly one: "Delete {count} item?";
6621
7031
  readonly other: "Delete {count} items?";
@@ -6623,7 +7033,6 @@ declare const en: {
6623
7033
  readonly confirmMessage: "Everything inside a selected group is deleted too.";
6624
7034
  readonly action: "Delete";
6625
7035
  readonly failedTitle: "Failed to delete";
6626
- readonly failed: "Could not delete: {error}";
6627
7036
  };
6628
7037
  readonly rename: {
6629
7038
  readonly failed: "Could not rename {name}";
@@ -6665,7 +7074,20 @@ declare const en: {
6665
7074
  readonly unreadable: {
6666
7075
  readonly category: "This category could not be read. Its file is unchanged.";
6667
7076
  readonly notSaved: "Changes are not being saved";
6668
- readonly notSavedDetail: "{file} could not be read. Nothing is written over it.";
7077
+ readonly notSavedDetail: "The {category} category could not be read. Nothing is written over it.";
7078
+ };
7079
+ readonly reference: {
7080
+ readonly missing: "This asset is no longer in this project.";
7081
+ readonly unreadable: "This asset could not be read.";
7082
+ readonly unreadableNamed: "\u201C{asset}\u201D could not be read.";
7083
+ readonly notAsset: "This value is not an asset.";
7084
+ readonly withReason: "{headline} {reason}";
7085
+ readonly reason: {
7086
+ readonly fileMissing: "Its file is missing from the project folder.";
7087
+ readonly accessDenied: "Studio is not allowed to read its file.";
7088
+ readonly undecodable: "Its file is damaged or is not in a format Studio can open.";
7089
+ readonly newerVersion: "Its file was saved by a newer version of NarraLeaf Studio.";
7090
+ };
6669
7091
  };
6670
7092
  readonly overview: {
6671
7093
  readonly loading: "Reading the asset library\u2026";
@@ -6818,7 +7240,10 @@ declare const en: {
6818
7240
  readonly noneFound: "No {family} models in this folder.";
6819
7241
  readonly noneFoundHint: "Check the kind selected above, then the folder. It must be the folder the exporter wrote.";
6820
7242
  readonly entry: "Entry";
6821
- readonly fileSummary: "{count} files \u00B7 {size}";
7243
+ readonly fileSummary: {
7244
+ readonly one: "{count} file \u00B7 {size}";
7245
+ readonly other: "{count} files \u00B7 {size}";
7246
+ };
6822
7247
  readonly selectAll: "Select all";
6823
7248
  readonly selectNone: "Select none";
6824
7249
  readonly importAction: "Import";
@@ -6867,6 +7292,7 @@ declare const en: {
6867
7292
  readonly locale: "Language";
6868
7293
  readonly release: "Variant";
6869
7294
  };
7295
+ readonly deletedVariant: "Deleted variant";
6870
7296
  readonly variantCount: {
6871
7297
  readonly one: "{count} variant";
6872
7298
  readonly other: "{count} variants";
@@ -6947,9 +7373,19 @@ declare const en: {
6947
7373
  readonly one: "Exported {count} file.";
6948
7374
  readonly other: "Exported {count} files.";
6949
7375
  };
6950
- readonly partial: "Exported {exported} files, {failed} could not be exported.";
7376
+ readonly partialCounts: "Exported {exported}, {failed} could not be exported.";
7377
+ readonly fileCount: {
7378
+ readonly one: "{count} file";
7379
+ readonly other: "{count} files";
7380
+ };
6951
7381
  readonly partialTitle: "Some files were not exported";
6952
7382
  readonly failed: "Export failed: {error}";
7383
+ readonly reason: {
7384
+ readonly permissionDenied: "Access was denied.";
7385
+ readonly sourceMissing: "The file is missing from the project folder.";
7386
+ readonly diskFull: "The disk is full.";
7387
+ readonly copyFailed: "The file could not be copied.";
7388
+ };
6953
7389
  };
6954
7390
  readonly selector: {
6955
7391
  readonly selectType: "Select {type}";
@@ -6973,8 +7409,19 @@ declare const en: {
6973
7409
  readonly categoryMapping: "Tag Category Mapping";
6974
7410
  readonly exampleFilename: "Example Filename: {filename}";
6975
7411
  readonly categoryPlaceholder: "Tag Category (e.g.: char, emo)";
6976
- readonly moreFiles: "\u2026 and {count} more files";
6977
- readonly summary: "Will add a total of {tags} tags to {files} files";
7412
+ readonly moreFiles: {
7413
+ readonly one: "\u2026 and {count} more file";
7414
+ readonly other: "\u2026 and {count} more files";
7415
+ };
7416
+ readonly tagCounts: "Will add a total of {tags} to {files}";
7417
+ readonly tagCount: {
7418
+ readonly one: "{count} tag";
7419
+ readonly other: "{count} tags";
7420
+ };
7421
+ readonly fileCount: {
7422
+ readonly one: "{count} file";
7423
+ readonly other: "{count} files";
7424
+ };
6978
7425
  readonly applying: "Applying\u2026";
6979
7426
  readonly applyTags: "Apply Tags";
6980
7427
  readonly parseFailedTitle: "Magic Tags parsing failed";
@@ -7006,6 +7453,35 @@ declare const en: {
7006
7453
  readonly markLoop: "Set loop point at playhead";
7007
7454
  readonly markOut: "Set out point at playhead";
7008
7455
  readonly channels: "{count} ch";
7456
+ readonly seam: "Loop seam";
7457
+ readonly auditionSeam: "Audition seam";
7458
+ readonly auditionSeamTip: "Plays {seconds} seconds either side of the seam.";
7459
+ readonly clearMarkers: "Clear all markers";
7460
+ readonly gain: "Gain";
7461
+ readonly gainTip: "Lowers this clip's volume in the game. It cannot raise it.";
7462
+ readonly align: "Align loudness";
7463
+ readonly alignTip: "Sets the gain from the measured loudness, so every aligned clip plays at the same level. A clip that is already quieter keeps its level.";
7464
+ readonly seamEnd: {
7465
+ readonly out: "Out point";
7466
+ readonly clipEnd: "End of clip";
7467
+ };
7468
+ readonly seamStart: {
7469
+ readonly loop: "Loop point";
7470
+ readonly in: "In point";
7471
+ readonly clipStart: "Start of clip";
7472
+ };
7473
+ readonly levels: "Levels";
7474
+ readonly peak: "Peak";
7475
+ readonly loudness: "Loudness";
7476
+ readonly leadingSilence: "Leading silence";
7477
+ readonly trailingSilence: "Trailing silence";
7478
+ readonly clipping: "Clipping";
7479
+ readonly clippingNone: "None";
7480
+ readonly clippingCount: {
7481
+ readonly one: "{count} place";
7482
+ readonly other: "{count} places";
7483
+ };
7484
+ readonly seconds: "{value} s";
7009
7485
  };
7010
7486
  readonly keybindings: {
7011
7487
  readonly playPause: "Play or pause";
@@ -7025,6 +7501,7 @@ declare const en: {
7025
7501
  readonly clearIn: "Clear in point";
7026
7502
  readonly clearLoop: "Clear loop point";
7027
7503
  readonly clearOut: "Clear out point";
7504
+ readonly clearMarkers: "Clear all markers";
7028
7505
  readonly undo: "Undo marker change";
7029
7506
  readonly redo: "Redo marker change";
7030
7507
  readonly selectAll: "Select whole clip";
@@ -7032,6 +7509,40 @@ declare const en: {
7032
7509
  readonly zoomIn: "Zoom in";
7033
7510
  readonly zoomOut: "Zoom out";
7034
7511
  readonly zoomFit: "Fit whole clip";
7512
+ readonly auditionSeam: "Audition loop seam";
7513
+ };
7514
+ };
7515
+ readonly video: {
7516
+ readonly loading: "Loading video\u2026";
7517
+ readonly loadError: "Failed to load video";
7518
+ readonly editor: {
7519
+ readonly previousFrame: "Previous frame";
7520
+ readonly nextFrame: "Next frame";
7521
+ readonly speed: "Playback speed";
7522
+ readonly zoom: "Zoom";
7523
+ readonly zoomFit: "Fit";
7524
+ readonly saveFrame: "Save frame as image";
7525
+ readonly frameSaved: "Frame saved as {name}";
7526
+ readonly frameSaveFailed: "The frame could not be saved";
7527
+ readonly open: "Open";
7528
+ readonly frame: "Frame {frame} of {total}";
7529
+ readonly transparent: "Transparent";
7530
+ };
7531
+ readonly keybindings: {
7532
+ readonly playPause: "Play or pause";
7533
+ readonly auditionSeam: "Preview loop seam";
7534
+ readonly toStart: "Go to start";
7535
+ readonly toEnd: "Go to end";
7536
+ readonly previousFrame: "Previous frame";
7537
+ readonly nextFrame: "Next frame";
7538
+ readonly backSecond: "Back one second";
7539
+ readonly forwardSecond: "Forward one second";
7540
+ readonly loop: "Toggle loop";
7541
+ readonly selectAll: "Select whole clip";
7542
+ readonly clearSelection: "Clear selection";
7543
+ readonly zoomIn: "Zoom timeline in";
7544
+ readonly zoomOut: "Zoom timeline out";
7545
+ readonly zoomFit: "Fit whole clip in timeline";
7035
7546
  };
7036
7547
  };
7037
7548
  readonly image: {
@@ -7240,6 +7751,8 @@ declare const en: {
7240
7751
  readonly contextListItemHint: " It reads the list row it runs for, and nothing here draws one.";
7241
7752
  readonly noRuntime: "Node \"{node}\": no runtime for type \"{type}\".";
7242
7753
  readonly inputMissing: "\"{node}\" has nothing connected to \"{pin}\".";
7754
+ readonly stepLimit: "\"{head}\" stopped at \"{node}\" after {steps} steps without a wait.";
7755
+ readonly stepLimitGlobal: "\"{head}\" in the global blueprint stopped at \"{node}\" after {steps} steps without a wait.";
7243
7756
  readonly unknownType: "Node \"{node}\": unknown type \"{type}\". Its plugin may be uninstalled or disabled.";
7244
7757
  readonly variableIdInvalid: "Node \"{node}\": pick a variable.";
7245
7758
  readonly persistentVariableIdInvalid: "Node \"{node}\": pick a persistent variable.";
@@ -7265,6 +7778,75 @@ declare const en: {
7265
7778
  readonly missingField: "Missing field \"{id}\".";
7266
7779
  };
7267
7780
  };
7781
+ readonly runtimeError: {
7782
+ readonly needsHost: "This node runs only in a running game. Open Dev Mode to run it.";
7783
+ readonly needsHostScript: "Scripts run only in a running game. Open Dev Mode to run them.";
7784
+ readonly needsGame: "\u201C{node}\u201D needs a running game.";
7785
+ readonly needsGameAny: "This node needs a running game.";
7786
+ readonly needsSaves: "Saves are not available here.";
7787
+ readonly needsWindow: "The game window is not available here.";
7788
+ readonly needsLayers: "Layers are not available here.";
7789
+ readonly needsStory: "This node runs only inside a story.";
7790
+ readonly needsChoiceRow: "\u201C{node}\u201D runs only inside a choice list row.";
7791
+ readonly needsCloseRequest: "\u201C{node}\u201D runs only below \u201C{head}\u201D.";
7792
+ readonly cannotRunHere: "This node cannot run here.";
7793
+ readonly pickStory: "\u201C{node}\u201D: pick a story.";
7794
+ readonly pickScene: "\u201C{node}\u201D: pick a scene.";
7795
+ readonly pickPage: "\u201C{node}\u201D: pick a page.";
7796
+ readonly pickFunction: "\u201C{node}\u201D: pick a function.";
7797
+ readonly pickTrack: "\u201C{node}\u201D: pick a track.";
7798
+ readonly pickClip: "\u201C{node}\u201D: pick a clip.";
7799
+ readonly pickField: "\u201C{node}\u201D: pick a field.";
7800
+ readonly pickSceneVariable: "This node has no scene variable selected.";
7801
+ readonly pickSavedVariable: "This node has no saved variable selected.";
7802
+ readonly pickPersistentVariable: "This node has no persistent variable selected.";
7803
+ readonly noPage: "No page is selected.";
7804
+ readonly noSave: "This node has no save to act on.";
7805
+ readonly inputEmpty: "\u201C{node}\u201D: \u201C{pin}\u201D is empty.";
7806
+ readonly valueNotNumber: "\u201C{name}\u201D must be a number.";
7807
+ readonly valueNotBoolean: "\u201C{name}\u201D must be a boolean.";
7808
+ readonly valueAbove: "\u201C{name}\u201D must be greater than {min}.";
7809
+ readonly valueAtLeast: "\u201C{name}\u201D must be {min} or greater.";
7810
+ readonly valueNotIndex: "\u201C{name}\u201D must be a whole number, 0 or greater.";
7811
+ readonly voiceEndModeInvalid: "\u201C{name}\u201D must be \u201Cfade\u201D, \u201Cstop\u201D or \u201Cnone\u201D.";
7812
+ readonly preferenceUnknown: "\u201C{key}\u201D is not a game preference.";
7813
+ readonly noLanguages: "This project has no languages configured.";
7814
+ readonly unknownLanguage: "\u201C{language}\u201D is not one of this project's languages.";
7815
+ readonly noVoiceLanguages: "This project has no voice languages configured.";
7816
+ readonly unknownVoiceLanguage: "\u201C{language}\u201D is not one of this project's voice languages.";
7817
+ readonly noElement: "This node has no element to act on.";
7818
+ readonly elementWrongKind: "This node acts only on widgets of type {kind}.";
7819
+ readonly elementUnsupported: "This node cannot act on this kind of widget.";
7820
+ readonly elementOutOfScope: "This node can only act on elements of its own page or component.";
7821
+ readonly elementNotFound: "The element was not found on this page.";
7822
+ readonly widgetWrongKind: "\u201C{element}\u201D is not a widget of type {kind}.";
7823
+ readonly unnamedWidget: "Unnamed widget";
7824
+ readonly noVariants: "\u201C{element}\u201D has no variants.";
7825
+ readonly unknownVariant: "\u201C{element}\u201D has no such variant.";
7826
+ readonly pageNotFound: "The page was not found in this project.";
7827
+ readonly notALayer: "\u201C{node}\u201D: this page is not a layer, so nothing was closed.";
7828
+ readonly persistentVariableMissing: "The persistent variable this node uses no longer exists.";
7829
+ readonly persistentVariableNoKey: "The persistent variable \u201C{name}\u201D has no storage key.";
7830
+ readonly slotIsRunningGame: "This node needs a save in storage. The slot it received is the running game, which only \u201C{node}\u201D can inherit from.";
7831
+ readonly saveNotStored: "The save named by \u201C{pin}\u201D is not in storage.";
7832
+ readonly captureNotHeld: "\u201C{pin}\u201D names a captured game this run does not hold. Run \u201C{capture}\u201D in the same chain as \u201C{start}\u201D.";
7833
+ readonly captureRepeated: "\u201C{node}\u201D has already captured this run several times. Capture it once, not once per loop.";
7834
+ readonly noGameToCapture: "\u201C{node}\u201D: there is no running game to capture.";
7835
+ readonly responseGone: "\u201C{node}\u201D: the response is no longer available. A response can only be read by the run that fetched it.";
7836
+ readonly pureOnExecPath: "This node only produces values and cannot run in sequence.";
7837
+ readonly nodeMissing: "The blueprint refers to a node that no longer exists.";
7838
+ readonly nodeTypeMissing: "This node is not available. Its plugin may be uninstalled or disabled.";
7839
+ readonly stepLimitSync: "This blueprint stopped after {steps} steps without finishing.";
7840
+ readonly asyncInSyncGraph: "A node that waits cannot run here.";
7841
+ readonly valueNodeUnavailable: "A node in this Blueprint Value is not available. Its plugin may be uninstalled or disabled.";
7842
+ readonly valueNodeNotAllowed: "This Blueprint Value contains a node that is not allowed in a Blueprint Value.";
7843
+ readonly fnDepth: "Function calls went deeper than {depth} levels and were stopped.";
7844
+ readonly fnMissing: "The function this node calls no longer exists.";
7845
+ readonly fnOutOfScope: "The function \u201C{name}\u201D is not available in this scope.";
7846
+ readonly scriptLoadFailed: "\u201C{file}\u201D could not be loaded: {detail}";
7847
+ readonly scriptExportsUncalled: "\u201C{file}\u201D exports {exported}, none of which this layer calls. Handlers it calls: {names}.";
7848
+ readonly scriptExportsNoHandler: "\u201C{file}\u201D exports no handler this layer calls. Handlers it calls: {names}.";
7849
+ };
7268
7850
  readonly variableScope: {
7269
7851
  readonly page: "Page";
7270
7852
  readonly blueprint: "Blueprint";
@@ -8641,9 +9223,16 @@ declare const en: {
8641
9223
  readonly title: "Import PSD";
8642
9224
  readonly choose: "Choose a PSD\u2026";
8643
9225
  readonly canvas: "Document size";
8644
- readonly cost: "{layers} layers \u00B7 ~{megabytes} MB";
9226
+ readonly costCounts: "{layers} \u00B7 ~{megabytes} MB";
9227
+ readonly layerCount: {
9228
+ readonly one: "{count} layer";
9229
+ readonly other: "{count} layers";
9230
+ };
8645
9231
  readonly mapping: "Layers";
8646
- readonly axis: "axis, {count} tags";
9232
+ readonly axis: {
9233
+ readonly one: "axis, {count} tag";
9234
+ readonly other: "axis, {count} tags";
9235
+ };
8647
9236
  readonly blends: "Blend modes the engine cannot reproduce";
8648
9237
  readonly merge: "Merge down";
8649
9238
  readonly skip: "Skip";
@@ -8657,6 +9246,7 @@ declare const en: {
8657
9246
  readonly import: "Import";
8658
9247
  readonly importing: "Importing\u2026";
8659
9248
  readonly failed: "Import failed";
9249
+ readonly readFailed: "This PSD could not be read.";
8660
9250
  readonly reason: {
8661
9251
  readonly hidden: "hidden in Photoshop";
8662
9252
  readonly blendSkipped: "skipped";
@@ -8867,7 +9457,6 @@ declare const en: {
8867
9457
  readonly projectDefault: "Project default font";
8868
9458
  readonly projectSection: "Project";
8869
9459
  readonly choose: "Choose font";
8870
- readonly loadError: "The font could not be loaded ({error}). The preview uses a fallback until the asset is valid.";
8871
9460
  readonly select: "Select Font";
8872
9461
  };
8873
9462
  readonly imageFill: {
@@ -8882,7 +9471,6 @@ declare const en: {
8882
9471
  readonly selectHint: "Asset browser opens on click";
8883
9472
  readonly changeImage: "Change image";
8884
9473
  readonly openEditor: "Click to open editor";
8885
- readonly resolveError: "The image asset could not be resolved ({error}). The preview is inaccurate until it exists.";
8886
9474
  readonly selectFillImage: "Select Fill Image";
8887
9475
  readonly mode: {
8888
9476
  readonly cover: "Cover";
@@ -8907,6 +9495,7 @@ declare const en: {
8907
9495
  readonly none: "Not referenced anywhere";
8908
9496
  readonly unknown: "Usage could not be determined";
8909
9497
  readonly unknownDetail: "{location} could not be read";
9498
+ readonly unknownComputed: "{location} picks its asset by a name assembled at run time";
8910
9499
  readonly count: {
8911
9500
  readonly one: "{count} reference";
8912
9501
  readonly other: "{count} references";
@@ -8920,6 +9509,7 @@ declare const en: {
8920
9509
  readonly character: "Character";
8921
9510
  readonly voice: "Voice";
8922
9511
  readonly design: "Design";
9512
+ readonly plugin: "Plugin";
8923
9513
  };
8924
9514
  };
8925
9515
  readonly tags: {
@@ -8939,6 +9529,7 @@ declare const en: {
8939
9529
  readonly loadAsset: "Failed to load asset";
8940
9530
  readonly deleteFailed: "Failed to delete thumbnail";
8941
9531
  readonly saveFailed: "Failed to save thumbnail";
9532
+ readonly saveFailedWithReason: "Failed to save thumbnail. {reason}";
8942
9533
  readonly unknown: "Unknown error";
8943
9534
  };
8944
9535
  };
@@ -9449,7 +10040,10 @@ declare const en: {
9449
10040
  readonly one: "{count} linked instance will show as missing until unlinked or replaced.";
9450
10041
  readonly other: "{count} linked instances will show as missing until unlinked or replaced.";
9451
10042
  };
9452
- readonly refs: "{count} refs";
10043
+ readonly refs: {
10044
+ readonly one: "{count} ref";
10045
+ readonly other: "{count} refs";
10046
+ };
9453
10047
  };
9454
10048
  readonly canvas: {
9455
10049
  readonly unknownWidget: "Unknown widget";
@@ -9815,6 +10409,7 @@ declare const en: {
9815
10409
  readonly missingPage: "Missing Page";
9816
10410
  readonly targetNotPage: "Target is not a Page";
9817
10411
  readonly pageLoopBlocked: "Page loop blocked";
10412
+ readonly leadsBackHere: "leads back here";
9818
10413
  readonly previewUnavailable: "Page preview unavailable";
9819
10414
  readonly pageRootMissing: "Page root missing";
9820
10415
  };
@@ -10438,6 +11033,13 @@ declare const en: {
10438
11033
  readonly storyDescription: "Story scene preview diagnostics and warnings";
10439
11034
  readonly storageDescription: "Project file writes: failed saves, retries, and recoveries";
10440
11035
  };
11036
+ readonly sources: {
11037
+ readonly blueprintLog: "Blueprint Log";
11038
+ };
11039
+ readonly devModeStatus: {
11040
+ readonly idle: "Stopped";
11041
+ readonly error: "Stopped on an error";
11042
+ };
10441
11043
  };
10442
11044
  readonly project: {
10443
11045
  readonly nav: {
@@ -10518,6 +11120,10 @@ declare const en: {
10518
11120
  readonly live: "The project's own settings are read-only during a live session. What is still editable here is what the session carries.";
10519
11121
  readonly frozen: "The project's own settings are read-only while the project is frozen.";
10520
11122
  };
11123
+ readonly writeFailed: {
11124
+ readonly plain: "Could not save the project file.";
11125
+ readonly withReason: "Could not save the project file. {reason}";
11126
+ };
10521
11127
  readonly details: {
10522
11128
  readonly nameLabel: "Application Name";
10523
11129
  readonly namePlaceholder: "Application name";
@@ -10631,6 +11237,10 @@ declare const en: {
10631
11237
  readonly clearBackground: "Keep transparency";
10632
11238
  readonly transparent: "None";
10633
11239
  readonly icnsPreview: "ICNS preview";
11240
+ readonly pickFailed: "The file picker could not be opened.";
11241
+ readonly unsupported: "\u201C{name}\u201D cannot be used as an icon.";
11242
+ readonly readFailed: "\u201C{name}\u201D could not be read.";
11243
+ readonly failed: "Could not update the icons.";
10634
11244
  readonly target: {
10635
11245
  readonly macos: "macOS";
10636
11246
  readonly windows: "Windows";
@@ -10908,8 +11518,34 @@ declare const en: {
10908
11518
  readonly scanning: "Scanning project\u2026";
10909
11519
  readonly empty: "No plugin dependencies.";
10910
11520
  readonly banner: {
10911
- readonly blocked: "Some plugins are not loaded for this project. Their installed version is incompatible. Update or reinstall them.";
10912
- readonly warnings: "A plugin is outdated, or an optional dependency is unavailable.";
11521
+ readonly missing: {
11522
+ readonly one: "A plugin this project uses is not installed. Install it from the plugins panel.";
11523
+ readonly other: "{count} plugins this project uses are not installed. Install them from the plugins panel.";
11524
+ };
11525
+ readonly held: {
11526
+ readonly one: "A plugin is off for this project. Its installed version is incompatible with the one this project was authored against. Click Rescan to use the installed version.";
11527
+ readonly other: "{count} plugins are off for this project. Their installed versions are incompatible with the ones this project was authored against. Click Rescan to use the installed versions.";
11528
+ };
11529
+ readonly needsAuthorization: {
11530
+ readonly one: "A plugin this project uses needs authorization. Authorize it in the plugins panel.";
11531
+ readonly other: "{count} plugins this project uses need authorization. Authorize them in the plugins panel.";
11532
+ };
11533
+ readonly disabled: {
11534
+ readonly one: "A plugin this project uses is disabled. Enable it in the plugins panel.";
11535
+ readonly other: "{count} plugins this project uses are disabled. Enable them in the plugins panel.";
11536
+ };
11537
+ readonly failed: {
11538
+ readonly one: "A plugin this project uses failed to load. The plugins panel shows the error.";
11539
+ readonly other: "{count} plugins this project uses failed to load. The plugins panel shows the errors.";
11540
+ };
11541
+ readonly outdated: {
11542
+ readonly one: "A plugin is older than the version this project was authored against.";
11543
+ readonly other: "{count} plugins are older than the versions this project was authored against.";
11544
+ };
11545
+ readonly incompatible: {
11546
+ readonly one: "A plugin is installed at a version incompatible with the one this project was authored against.";
11547
+ readonly other: "{count} plugins are installed at versions incompatible with the ones this project was authored against.";
11548
+ };
10913
11549
  };
10914
11550
  readonly status: {
10915
11551
  readonly ready: "Ready";
@@ -10922,7 +11558,6 @@ declare const en: {
10922
11558
  readonly meta: {
10923
11559
  readonly requires: "Requires {version}";
10924
11560
  readonly installed: "Installed {version}";
10925
- readonly notInstalled: "not installed";
10926
11561
  readonly builtIn: "Built-in";
10927
11562
  readonly dataOnly: "data only";
10928
11563
  };
@@ -11071,6 +11706,7 @@ declare const en: {
11071
11706
  readonly ownerLabel: "Owner";
11072
11707
  readonly contributorsLabel: "Contributors";
11073
11708
  readonly visitWebsite: "Visit website";
11709
+ readonly openSourceLicenses: "Open source licenses";
11074
11710
  };
11075
11711
  readonly actions: {
11076
11712
  readonly devMode: {
@@ -11146,7 +11782,17 @@ declare const en: {
11146
11782
  };
11147
11783
  readonly export: {
11148
11784
  readonly chooseFolder: "Choose a folder for the exported project package.";
11149
- readonly failed: "Failed to export project.";
11785
+ readonly failed: "Could not export the project.";
11786
+ readonly failedWithReason: "{headline} {reason}";
11787
+ readonly reason: {
11788
+ readonly folderProtected: "The chosen folder is inside Studio's own storage. Choose another folder.";
11789
+ readonly folderReadOnly: "Studio is not allowed to write to the chosen folder.";
11790
+ readonly folderMissing: "The chosen folder no longer exists.";
11791
+ readonly diskFull: "The disk is full.";
11792
+ readonly projectUnreadable: "Studio is not allowed to read some of the project's files.";
11793
+ readonly projectFileBusy: "A file in the project is in use by another program.";
11794
+ readonly projectChanged: "A file in the project was moved or deleted during the export. Export again.";
11795
+ };
11150
11796
  readonly success: {
11151
11797
  readonly one: "Exported project package with {count} file.";
11152
11798
  readonly other: "Exported project package with {count} files.";
@@ -11285,6 +11931,14 @@ declare const en: {
11285
11931
  readonly generic: "The file could not be unpacked.";
11286
11932
  readonly notAProjectTitle: "This is not a NarraLeaf Studio project";
11287
11933
  readonly notAProject: "Unpacking finished, but the result holds no Studio project file. The unpacked contents are in {path}";
11934
+ readonly notAPackage: "The file is not a NarraLeaf Studio project package.";
11935
+ readonly newerVersion: "The package was made by a newer version of NarraLeaf Studio. Update Studio to import it.";
11936
+ readonly damaged: "The package is damaged or incomplete.";
11937
+ readonly packageMissing: "The package file no longer exists.";
11938
+ readonly packageUnreadable: "Studio is not allowed to read the package file.";
11939
+ readonly folderProtected: "The chosen folder is inside Studio's own storage. Choose another folder.";
11940
+ readonly diskFull: "The disk is full.";
11941
+ readonly leftBehind: "Part of the unpacked contents could not be removed from the folder. Empty the folder before trying again.";
11288
11942
  };
11289
11943
  };
11290
11944
  readonly source: {
@@ -11348,21 +12002,26 @@ declare const en: {
11348
12002
  readonly openInStudioFailed: "No workspace window is open for this project.";
11349
12003
  readonly issues: {
11350
12004
  readonly title: "Issues";
11351
- readonly empty: "Nothing has failed";
12005
+ readonly empty: "Nothing has failed in this run";
11352
12006
  readonly sessionFailure: "Session failed to start";
11353
12007
  readonly atLine: "Line {line} \u00B7 {scene}";
11354
12008
  readonly inScene: "In {scene}";
11355
12009
  readonly onSurface: "On {surface}";
12010
+ readonly onSurfaceGone: "On a page no longer in this project";
11356
12011
  readonly onPlugin: "Plugin {plugin}";
11357
12012
  readonly pluginNotDeclared: "{plugin} is installed but not among this project's plugin dependencies. It does not run in this session and is not included in a build. Rescan them in Project \u25B8 App.";
11358
12013
  readonly pluginUnusable: "{plugin} is among this project's plugin dependencies and cannot be loaded. It does not run in this session and is not included in a build. Check its status in Project \u25B8 App.";
11359
12014
  readonly pluginEntryFailed: "{plugin} failed to load: {error}. Its blueprint nodes and widgets are not registered in this session.";
11360
12015
  readonly pluginListFailed: "The runtime plugin list could not be read: {error}. No plugin runs in this session.";
12016
+ readonly assetMissing: "\u201C{property}\u201D on \u201C{element}\u201D refers to an asset that is no longer in this project.";
12017
+ readonly assetUnreadable: "\u201C{property}\u201D on \u201C{element}\u201D refers to an asset that could not be read.";
12018
+ readonly assetUnreadableNamed: "\u201C{property}\u201D on \u201C{element}\u201D refers to \u201C{asset}\u201D, which could not be read.";
12019
+ readonly assetNotAsset: "\u201C{property}\u201D on \u201C{element}\u201D is set to a value that is not an asset.";
11361
12020
  readonly noLocation: "Could not be traced to a line";
11362
12021
  readonly viaPlayHead: "where playback was";
11363
12022
  readonly stack: "Stack";
11364
12023
  readonly dismissAll: "Dismiss all ({count})";
11365
- readonly summary: "{errors} errors \u00B7 {warnings} warnings";
12024
+ readonly counts: "{errors} \u00B7 {warnings} in this run";
11366
12025
  };
11367
12026
  readonly devtools: {
11368
12027
  readonly title: "Interface";
@@ -11589,6 +12248,7 @@ declare const en: {
11589
12248
  readonly node: "Runs the plugin's own code as part of the game";
11590
12249
  };
11591
12250
  readonly buildDependencyHosts: "Downloads from {hosts}";
12251
+ readonly studioApi: "Studio API: {capability}";
11592
12252
  readonly runtimeCapability: {
11593
12253
  readonly store: "Store its own data alongside the player's saves";
11594
12254
  readonly events: "Observe game progress (scenes, dialogue, choices, saves)";
@@ -11602,6 +12262,7 @@ declare const en: {
11602
12262
  readonly menu: "Provide the game's menu bar";
11603
12263
  readonly storyCompile: "Take part in compiling stories, and add actions to lines it did not write";
11604
12264
  readonly diagnostics: "Read how much memory the game\u2019s caches are using";
12265
+ readonly processMemory: "Read how much of the computer\u2019s memory the game is using";
11605
12266
  };
11606
12267
  };
11607
12268
  readonly button: {
@@ -11634,7 +12295,7 @@ declare const en: {
11634
12295
  readonly projectTrust: {
11635
12296
  readonly window: "Project Trust";
11636
12297
  readonly title: "Trust this project?";
11637
- readonly untrusted: "Studio did not create this project. Until it is trusted, it can be edited but not run, previewed, built or tested.";
12298
+ readonly untrusted: "Studio did not create this project. Until it is trusted, it can be edited but not run, previewed, built, tested or synced with its server.";
11638
12299
  readonly meaning: "Once trusted, the code this project ships runs on this machine.";
11639
12300
  readonly later: "This can be changed under Trusted projects in Settings.";
11640
12301
  readonly confirm: "Trust";
@@ -11643,6 +12304,19 @@ declare const en: {
11643
12304
  readonly load: "The project details could not be read.";
11644
12305
  };
11645
12306
  };
12307
+ readonly serverSession: {
12308
+ readonly window: "Server Sign-in";
12309
+ readonly title: "Use this sign-in for this project?";
12310
+ readonly signedInAs: "Signed in as {name}";
12311
+ readonly unused: "This project has not used this sign-in before.";
12312
+ readonly meaning: "Versions from this project are sent and received as {name}.";
12313
+ readonly later: "This can be changed under NarraLeaf Team in the project.";
12314
+ readonly confirm: "Use sign-in";
12315
+ readonly cancel: "Don't use";
12316
+ readonly error: {
12317
+ readonly load: "The sign-in details could not be read.";
12318
+ };
12319
+ };
11646
12320
  readonly plugins: {
11647
12321
  readonly installLocal: "Install from folder";
11648
12322
  readonly search: {
@@ -11703,6 +12377,8 @@ declare const en: {
11703
12377
  readonly uninstalled: "Plugin uninstalled.";
11704
12378
  readonly reloading: "Reloading plugin\u2026";
11705
12379
  readonly reloaded: "Plugin reloaded.";
12380
+ readonly starting: "Starting plugin\u2026";
12381
+ readonly started: "Plugin started.";
11706
12382
  };
11707
12383
  readonly error: {
11708
12384
  readonly load: "Failed to load plugins";
@@ -11726,6 +12402,7 @@ declare const en: {
11726
12402
  readonly authorized: "Authorized";
11727
12403
  readonly notInRegistry: "Not in the registry";
11728
12404
  readonly noCompatibleVersion: "No compatible version";
12405
+ readonly rescanInProject: "Click Rescan in Project \u25B8 App to use the installed version.";
11729
12406
  readonly task: {
11730
12407
  readonly running: "Installing dependencies\u2026";
11731
12408
  readonly done: "Dependencies installed.";
@@ -11740,7 +12417,7 @@ declare const en: {
11740
12417
  readonly runtimeOnly: "Game runtime only";
11741
12418
  readonly runtimeOnlyHint: "This plugin only extends the running game. It contributes nothing to the editor.";
11742
12419
  readonly suppressed: "Off for this project";
11743
- readonly suppressedHint: "The installed version is incompatible with the one this project was authored against. Update it, or update the project's dependency table from Project \u25B8 App.";
12420
+ readonly suppressedHint: "The installed version is incompatible with the one this project was authored against. Click Rescan in Project \u25B8 App to use the installed version.";
11744
12421
  readonly failed: "Failed to load";
11745
12422
  };
11746
12423
  readonly pendingReopen: "Takes effect the next time this project opens.";
@@ -12179,7 +12856,6 @@ declare const en: {
12179
12856
  readonly "build-dependency-unavailable": string;
12180
12857
  readonly "sidecar-target-missing": "{plugin} ships no {sidecar} program for {platform}, so anything it provides is missing from that build.";
12181
12858
  readonly "sidecar-crossbuild-exec-bit": string;
12182
- readonly "encryption-key-unavailable": "Asset protection is on, but its key could not be read.";
12183
12859
  readonly "web-unprotected": "Asset protection does not apply to the web export; its files ship unprotected.";
12184
12860
  readonly "mobile-unprotected": "Asset protection does not apply to Android or iOS packages.";
12185
12861
  readonly "progress-carry-unsupported": string;
@@ -12293,10 +12969,30 @@ declare const en: {
12293
12969
  };
12294
12970
  readonly contentDropped: "{scene} in {story} is not in this build.";
12295
12971
  readonly contentCoverageGap: "{location} could not be read, so what the {variant} build leaves out cannot be decided.";
12296
- readonly contentComputedPinGap: "{location} receives its asset from a computed value, so this build cannot tell which asset it needs.";
12297
12972
  readonly contentComputedPinSummary: {
12298
- readonly one: "Build stopped: {count} pin receives its asset from a computed value. Select the asset on the pin. See the console.";
12299
- readonly other: "Build stopped: {count} pins receive their asset from a computed value. Select the asset on each pin. See the console.";
12973
+ readonly one: "Build stopped: {count} asset name is assembled at run time. See the console.";
12974
+ readonly other: "Build stopped: {count} asset names are assembled at run time. See the console.";
12975
+ };
12976
+ readonly contentUnloadedNodeSummary: {
12977
+ readonly one: "Build stopped: {count} asset name comes from a node type that is not loaded. See the console.";
12978
+ readonly other: "Build stopped: {count} asset names come from node types that are not loaded. See the console.";
12979
+ };
12980
+ readonly scriptsNotCompiled: {
12981
+ readonly one: "{count} script could not be compiled.";
12982
+ readonly other: "{count} scripts could not be compiled.";
12983
+ };
12984
+ readonly assetSet: {
12985
+ readonly inCharacters: "a character";
12986
+ readonly inInterface: "the interface";
12987
+ readonly inBlueprint: "a blueprint";
12988
+ readonly language: "the language {name}";
12989
+ readonly variant: "the variant {name}";
12990
+ readonly unfilled: "Asset set {set}, used in {location}, has no file for {value}.";
12991
+ readonly noLanguage: "Asset set {set}, used in {location}, has no file for the project's language.";
12992
+ readonly ambiguous: "Asset set {set}, used in {location}, has more than one file for {value}.";
12993
+ readonly nested: "Asset set {set}, used in {location}, has a set under one of its values. A build does not resolve nested sets.";
12994
+ readonly noValues: "Asset set {set}, used in {location}, declares no variants.";
12995
+ readonly variantUnset: "Asset set {set}, used in {location}, varies by variant, and {variant} does not say which art it uses. Choose it under Project \u25B8 App \u25B8 Build variants.";
12300
12996
  };
12301
12997
  readonly contentCoverageWholeProject: "The project";
12302
12998
  readonly contentCoverageSummary: {
@@ -12418,7 +13114,10 @@ declare const en: {
12418
13114
  readonly empty: "No builds recorded";
12419
13115
  readonly emptyHint: "Builds run for this project are listed here.";
12420
13116
  readonly logEmpty: "This build recorded no output.";
12421
- readonly logOmitted: "The first {count} lines were dropped to keep the record small.";
13117
+ readonly logOmitted: {
13118
+ readonly one: "The first {count} line was dropped to keep the record small.";
13119
+ readonly other: "The first {count} lines were dropped to keep the record small.";
13120
+ };
12422
13121
  };
12423
13122
  readonly structure: {
12424
13123
  readonly title: "Structure";
@@ -12666,6 +13365,14 @@ declare const en: {
12666
13365
  readonly description: "A node whose type the project cannot load";
12667
13366
  readonly message: "{type} is not loaded, so this node will not run in the game";
12668
13367
  };
13368
+ readonly blueprintAssembledAssetName: {
13369
+ readonly title: "Asset name assembled at run time";
13370
+ readonly description: "An asset is chosen by a name the game puts together while it runs, and the game package does not carry it";
13371
+ readonly message: "\"{pin}\" on \"{node}\" receives an asset name assembled at run time (from \"{origin}\"). A game package carries only the assets whose names are written in the project, so nothing will be there in the released game. Choose the asset in the asset picker, or read one already chosen from a list row or a variable";
13372
+ readonly messageBinding: "\"{prop}\" on \"{element}\" is bound to an asset name assembled at run time (from \"{origin}\"). A game package carries only the assets whose names are written in the project, so nothing will be there in the released game. Choose the asset in the asset picker, or read one already chosen from a list row or a variable";
13373
+ readonly messageUnloadedNode: "\"{pin}\" on \"{node}\" receives an asset name from {origin}, which is not loaded, so the asset it names will not be in the released game. Install or switch on the plugin that provides this node type, then build again";
13374
+ readonly messageUnloadedNodeBinding: "\"{prop}\" on \"{element}\" is bound to an asset name from {origin}, which is not loaded, so the asset it names will not be in the released game. Install or switch on the plugin that provides this node type, then build again";
13375
+ };
12669
13376
  readonly uiUnlocalizedText: {
12670
13377
  readonly title: "Unlocalized text";
12671
13378
  readonly description: "Text written straight onto a widget in a project that has a second language";
@@ -12696,6 +13403,11 @@ declare const en: {
12696
13403
  readonly description: "A Page widget embedding a page the project does not have";
12697
13404
  readonly message: "This Page widget embeds a page the project does not have";
12698
13405
  };
13406
+ readonly uiFrameLoop: {
13407
+ readonly title: "Circular embedded page";
13408
+ readonly description: "A Page widget embedding a page that leads back to it";
13409
+ readonly message: "This Page widget embeds a page that leads back to it";
13410
+ };
12699
13411
  readonly uiListItemFieldMissing: {
12700
13412
  readonly title: "Missing item field";
12701
13413
  readonly description: "A widget bound to an item field the list drawing it does not declare";
@@ -12744,7 +13456,11 @@ declare const en: {
12744
13456
  readonly variablesReadNeverWritten: {
12745
13457
  readonly title: "Condition nothing can change";
12746
13458
  readonly description: "A variable a condition tests, that nothing in the project ever assigns";
12747
- readonly message: "{variable} is tested by {count} condition(s) but nothing ever sets it";
13459
+ readonly message: "{variable} is tested by {conditions} but nothing ever sets it";
13460
+ readonly conditionCount: {
13461
+ readonly one: "{count} condition";
13462
+ readonly other: "{count} conditions";
13463
+ };
12748
13464
  };
12749
13465
  readonly variablesRandomOutsideAssignment: {
12750
13466
  readonly title: "Random outside an assignment";
@@ -12781,7 +13497,11 @@ declare const en: {
12781
13497
  readonly localizationOrphan: {
12782
13498
  readonly title: "Orphan translation";
12783
13499
  readonly description: "A translation whose line no longer exists";
12784
- readonly message: "{count} {locale} translations have no line";
13500
+ readonly message: "{translations} with no line";
13501
+ readonly translationCount: {
13502
+ readonly one: "{count} {locale} translation";
13503
+ readonly other: "{count} {locale} translations";
13504
+ };
12785
13505
  };
12786
13506
  readonly voiceMissing: {
12787
13507
  readonly title: "Missing voice";
@@ -12796,7 +13516,11 @@ declare const en: {
12796
13516
  readonly voiceOrphan: {
12797
13517
  readonly title: "Orphan voice";
12798
13518
  readonly description: "A recording whose line no longer exists";
12799
- readonly message: "{count} {locale} recordings have no line";
13519
+ readonly message: "{recordings} with no line";
13520
+ readonly recordingCount: {
13521
+ readonly one: "{count} {locale} recording";
13522
+ readonly other: "{count} {locale} recordings";
13523
+ };
12800
13524
  };
12801
13525
  readonly brandBrokenLink: {
12802
13526
  readonly title: "Broken color link";
@@ -12808,10 +13532,18 @@ declare const en: {
12808
13532
  readonly typographyGlyphCoverage: {
12809
13533
  readonly title: "Missing glyphs";
12810
13534
  readonly description: "Text using characters no font of the project can draw";
12811
- readonly message: "No project font can draw \u201C{character}\u201D ({count} times)";
12812
- readonly messageInLanguage: "No project font can draw \u201C{character}\u201D in {language} ({count} times)";
12813
- readonly messageMore: "{count} more characters no project font can draw";
12814
- readonly messageMoreInLanguage: "{count} more characters no project font can draw in {language}";
13535
+ readonly message: "No project font can draw \u201C{character}\u201D ({occurrences})";
13536
+ readonly occurrenceCount: {
13537
+ readonly one: "{count} time";
13538
+ readonly other: "{count} times";
13539
+ };
13540
+ readonly messageInLanguage: "No project font can draw \u201C{character}\u201D in {language} ({occurrences})";
13541
+ readonly messageMore: "{characters} no project font can draw";
13542
+ readonly moreCharacterCount: {
13543
+ readonly one: "{count} more character";
13544
+ readonly other: "{count} more characters";
13545
+ };
13546
+ readonly messageMoreInLanguage: "{characters} no project font can draw in {language}";
12815
13547
  readonly messageUnreadable: "{font} could not be read, so glyph coverage was not checked";
12816
13548
  readonly messageUnloadable: "{font} is a .{format} font, which the game cannot draw with";
12817
13549
  };
@@ -12851,7 +13583,7 @@ declare const en: {
12851
13583
  readonly title: "Problems";
12852
13584
  readonly empty: "No problems found";
12853
13585
  readonly running: "Checking\u2026";
12854
- readonly summary: "{errors} errors, {warnings} warnings, {infos} info";
13586
+ readonly counts: "{errors}, {warnings}, {infos}";
12855
13587
  readonly filtered: "{shown} of {total}";
12856
13588
  readonly rerun: "Run again";
12857
13589
  readonly filterAll: "All";
@@ -12861,6 +13593,7 @@ declare const en: {
12861
13593
  readonly expand: "Expand";
12862
13594
  readonly collapseAll: "Collapse all";
12863
13595
  readonly expandAll: "Expand all";
13596
+ readonly showAll: "Show all {count}";
12864
13597
  readonly lineAria: "Line {line}";
12865
13598
  readonly findPlaceholder: "Find in problems";
12866
13599
  };
@@ -12869,13 +13602,20 @@ declare const en: {
12869
13602
  readonly category: "Lint";
12870
13603
  };
12871
13604
  readonly console: {
13605
+ readonly channel: "Lint";
13606
+ readonly channelDescription: "Project checks and the problems they find";
12872
13607
  readonly started: "Check started";
12873
- readonly finished: "{errors} errors, {warnings} warnings in {duration}";
13608
+ readonly finishedCounts: "{errors}, {warnings} in {duration}";
12874
13609
  readonly finding: "{location} {message} ({rule})";
13610
+ readonly byRule: "Findings by rule";
13611
+ readonly ruleCount: "{rule}: {count}";
12875
13612
  };
12876
13613
  readonly build: {
12877
13614
  readonly started: "Checking the project\u2026";
12878
- readonly blocked: "Build stopped by {count} problems";
13615
+ readonly blocked: {
13616
+ readonly one: "Build stopped by {count} problem";
13617
+ readonly other: "Build stopped by {count} problems";
13618
+ };
12879
13619
  readonly blockedHint: "Change this in Project \u25B8 Project \u25B8 Check before building";
12880
13620
  readonly skipped: "Project check skipped";
12881
13621
  };
@@ -12921,6 +13661,20 @@ declare const en: {
12921
13661
  readonly element: "Item {index}";
12922
13662
  readonly root: "The document itself";
12923
13663
  };
13664
+ readonly units: {
13665
+ readonly rows: {
13666
+ readonly one: "{count} row";
13667
+ readonly other: "{count} rows";
13668
+ };
13669
+ readonly elements: {
13670
+ readonly one: "{count} element";
13671
+ readonly other: "{count} elements";
13672
+ };
13673
+ readonly nodes: {
13674
+ readonly one: "{count} node";
13675
+ readonly other: "{count} nodes";
13676
+ };
13677
+ };
12924
13678
  readonly count: {
12925
13679
  readonly appTags: "Build variants";
12926
13680
  readonly dlc: "DLC";
@@ -12959,8 +13713,8 @@ declare const en: {
12959
13713
  readonly chapterRenamed: "Chapter renamed";
12960
13714
  readonly chapterScenes: "Scene list changed";
12961
13715
  readonly chapterOrder: "Chapters reordered";
12962
- readonly sceneAdded: "Scene added ({blocks} rows)";
12963
- readonly sceneRemoved: "Scene removed ({blocks} rows)";
13716
+ readonly sceneAdded: "Scene added ({rowCount})";
13717
+ readonly sceneRemoved: "Scene removed ({rowCount})";
12964
13718
  readonly sceneChanged: "Scene changed";
12965
13719
  readonly sceneRenamed: "Renamed";
12966
13720
  readonly sceneField: "Scene {field}";
@@ -13039,16 +13793,16 @@ declare const en: {
13039
13793
  };
13040
13794
  readonly uiDocument: {
13041
13795
  readonly renamed: "Interface renamed";
13042
- readonly surfaceAdded: "Surface added ({elements} elements)";
13043
- readonly surfaceRemoved: "Surface removed ({elements} elements)";
13796
+ readonly surfaceAdded: "Surface added ({elementCount})";
13797
+ readonly surfaceRemoved: "Surface removed ({elementCount})";
13044
13798
  readonly surfaceChanged: "Surface changed";
13045
13799
  readonly surfaceRenamed: "Renamed";
13046
13800
  readonly surfaceDesignSize: "Design size ({fromWidth}\u00D7{fromHeight} \u2192 {toWidth}\u00D7{toHeight})";
13047
13801
  readonly surfaceSettings: "Background or page animation changed";
13048
13802
  readonly surfaceRoot: "Root element changed";
13049
13803
  readonly surfaceField: "{field} changed";
13050
- readonly componentAdded: "Component added ({elements} elements)";
13051
- readonly componentRemoved: "Component removed ({elements} elements)";
13804
+ readonly componentAdded: "Component added ({elementCount})";
13805
+ readonly componentRemoved: "Component removed ({elementCount})";
13052
13806
  readonly componentChanged: "Component changed";
13053
13807
  readonly componentRenamed: "Renamed";
13054
13808
  readonly componentField: "{field} changed";
@@ -13068,8 +13822,8 @@ declare const en: {
13068
13822
  };
13069
13823
  readonly uiGraphs: {
13070
13824
  readonly ownerRecord: "Active blueprint changed";
13071
- readonly blueprintAdded: "Blueprint added ({nodes} nodes)";
13072
- readonly blueprintRemoved: "Blueprint removed ({nodes} nodes)";
13825
+ readonly blueprintAdded: "Blueprint added ({nodeCount})";
13826
+ readonly blueprintRemoved: "Blueprint removed ({nodeCount})";
13073
13827
  readonly blueprintChanged: "Blueprint changed";
13074
13828
  readonly blueprintRenamed: "Renamed";
13075
13829
  readonly blueprintSource: "Script file changed";
@@ -13077,8 +13831,8 @@ declare const en: {
13077
13831
  readonly scriptRemoved: "Script removed";
13078
13832
  readonly scriptChanged: "Script changed";
13079
13833
  readonly blueprintField: "{field} changed";
13080
- readonly graphAdded: "Graph added ({nodes} nodes)";
13081
- readonly graphRemoved: "Graph removed ({nodes} nodes)";
13834
+ readonly graphAdded: "Graph added ({nodeCount})";
13835
+ readonly graphRemoved: "Graph removed ({nodeCount})";
13082
13836
  readonly graphChanged: "Graph changed";
13083
13837
  readonly graphRenamed: "Renamed";
13084
13838
  readonly graphField: "{field} changed";
@@ -13356,7 +14110,17 @@ declare const en: {
13356
14110
  readonly assetsOrder: "Asset order";
13357
14111
  readonly characters: "Cast";
13358
14112
  readonly assetContent: "Asset file";
14113
+ readonly assetOfType: {
14114
+ readonly image: "Image asset";
14115
+ readonly audio: "Audio asset";
14116
+ readonly video: "Video asset";
14117
+ readonly json: "JSON asset";
14118
+ readonly font: "Font asset";
14119
+ readonly model: "Model asset";
14120
+ readonly other: "Other asset";
14121
+ };
13359
14122
  readonly qualified: "{name} ({qualifier})";
14123
+ readonly numbered: "{name} {index}";
13360
14124
  };
13361
14125
  readonly category: {
13362
14126
  readonly story: "Story";
@@ -13399,7 +14163,10 @@ declare const en: {
13399
14163
  readonly emptyWorkingTree: "Nothing has changed since the last version";
13400
14164
  readonly readFailure: "This comparison could not be read: {error}";
13401
14165
  readonly incomplete: "{shown} of {total} changed documents were compared.";
13402
- readonly documentsOmitted: "{count} more documents are not listed here.";
14166
+ readonly documentsOmitted: {
14167
+ readonly one: "{count} more document is not listed here.";
14168
+ readonly other: "{count} more documents are not listed here.";
14169
+ };
13403
14170
  readonly unavailable: "Version control is not available in this project.";
13404
14171
  };
13405
14172
  readonly split: {
@@ -13433,7 +14200,10 @@ declare const en: {
13433
14200
  readonly takeTheirs: "Keep theirs";
13434
14201
  readonly takeAllMine: "Keep mine everywhere";
13435
14202
  readonly takeAllTheirs: "Keep theirs everywhere";
13436
- readonly rowsOmitted: "{count} more files are not listed here. Use the two links above to choose for all of them.";
14203
+ readonly rowsOmitted: {
14204
+ readonly one: "{count} more file is not listed here. Use the two links above to choose for all of them.";
14205
+ readonly other: "{count} more files are not listed here. Use the two links above to choose for all of them.";
14206
+ };
13437
14207
  readonly fileList: "Conflicted files";
13438
14208
  readonly decision: "Which side to keep";
13439
14209
  readonly pending: "No side chosen";
@@ -13555,7 +14325,7 @@ declare const en: {
13555
14325
  readonly rerun: "Run again";
13556
14326
  readonly severityFilter: "Severity";
13557
14327
  readonly filterAll: "All";
13558
- readonly findings: "{errors} errors, {warnings} warnings, {infos} info";
14328
+ readonly findingCounts: "{errors}, {warnings}, {infos}";
13559
14329
  readonly durationSeconds: "{seconds}s";
13560
14330
  readonly durationMinutes: "{minutes}m {seconds}s";
13561
14331
  };
@@ -13567,6 +14337,7 @@ declare const en: {
13567
14337
  };
13568
14338
  readonly console: {
13569
14339
  readonly channel: "Test";
14340
+ readonly channelDescription: "Test runs and their verdicts";
13570
14341
  readonly started: "{title} started";
13571
14342
  readonly finished: "{title} {status} in {duration}";
13572
14343
  readonly finding: "{severity} {message}";
@@ -13578,13 +14349,25 @@ declare const en: {
13578
14349
  readonly cancelled: "{title} cancelled";
13579
14350
  readonly errored: "{title} could not run";
13580
14351
  };
14352
+ readonly entryPoint: {
14353
+ readonly target: {
14354
+ readonly story: "story";
14355
+ readonly scene: "scene";
14356
+ };
14357
+ readonly assembled: "Start Game in \"{blueprint}\" takes a {target} assembled at run time (from \"{origin}\")";
14358
+ readonly unreadNode: "Start Game in \"{blueprint}\" takes its {target} from \"{origin}\", whose data this test does not read";
14359
+ readonly unreadPluginData: "Start Game in \"{blueprint}\" takes its {target} from \"{origin}\", and the {plugin} data in this project cannot be read";
14360
+ readonly engineRows: "Start Game in \"{blueprint}\" takes its {target} from the rows of \"{list}\", which the story fills while it plays";
14361
+ readonly undeclaredVariable: "Start Game in \"{blueprint}\" takes its {target} from a variable the project does not declare";
14362
+ readonly unreadable: "Start Game in \"{blueprint}\" takes its {target} from a value this test cannot follow";
14363
+ };
13581
14364
  readonly builtin: {
13582
14365
  readonly projectDiagnostics: {
13583
14366
  readonly title: "Project diagnostics";
13584
14367
  readonly description: "All project check rules, run as one test";
13585
14368
  readonly summary: {
13586
14369
  readonly passed: "No problems found";
13587
- readonly failed: "{errors} errors, {warnings} warnings";
14370
+ readonly failed: "Problems: errors {errors}, warnings {warnings}";
13588
14371
  };
13589
14372
  };
13590
14373
  readonly walkthrough: {
@@ -13599,7 +14382,7 @@ declare const en: {
13599
14382
  };
13600
14383
  };
13601
14384
  readonly log: {
13602
- readonly planned: "Route planned: {scenes} scenes, {decisions} decisions";
14385
+ readonly planned: "Route planned: scenes {scenes}, decisions {decisions}";
13603
14386
  readonly choosing: "{scene}: choosing \"{option}\"";
13604
14387
  readonly improvised: "Answered an unplanned choice with \"{option}\"";
13605
14388
  };
@@ -13610,8 +14393,8 @@ declare const en: {
13610
14393
  readonly optionMissing: "{scene} did not offer \"{option}\", so this route is not walkable";
13611
14394
  readonly otherEnding: "Reached {reached} instead of {ending}";
13612
14395
  readonly endedWithoutEnding: "The story ended without reaching {ending}";
13613
- readonly stalled: "Stopped advancing after {steps} steps without reaching {ending}";
13614
- readonly cancelled: "Cancelled after {steps} steps";
14396
+ readonly stalled: "Stopped advancing without reaching {ending}. Steps taken: {steps}";
14397
+ readonly cancelled: "Cancelled. Steps taken: {steps}";
13615
14398
  readonly exit: {
13616
14399
  readonly closed: "The game closed before reaching {ending}";
13617
14400
  readonly stopped: "The game was stopped before reaching {ending}";
@@ -13628,7 +14411,7 @@ declare const en: {
13628
14411
  readonly description: "Whether every scene, option and ending can be reached once conditions are read";
13629
14412
  readonly skipped: {
13630
14413
  readonly noEntryPoint: "No story marks where play begins";
13631
- readonly undecidableEntry: "A Start Story node picks its scene while the game runs, so where play begins cannot be read";
14414
+ readonly undecidableEntry: "Where play begins cannot be read from Start Game in \"{blueprint}\"";
13632
14415
  readonly storiesUnread: "A story could not be read";
13633
14416
  };
13634
14417
  readonly finding: {
@@ -13649,7 +14432,7 @@ declare const en: {
13649
14432
  readonly skipped: {
13650
14433
  readonly noEndings: "No story with an entry point marks an /ending";
13651
14434
  readonly noEntryPoint: "No story marks where play begins";
13652
- readonly undecidableEntry: "A Start Story node picks its scene while the game runs, so where play begins cannot be read";
14435
+ readonly undecidableEntry: "Where play begins cannot be read from Start Game in \"{blueprint}\"";
13653
14436
  readonly storiesUnread: "A story could not be read";
13654
14437
  };
13655
14438
  readonly finding: {
@@ -13809,6 +14592,10 @@ declare const en: {
13809
14592
  readonly title: "Trimming and looping a clip";
13810
14593
  readonly body: string;
13811
14594
  };
14595
+ readonly videoClips: {
14596
+ readonly title: "Inspecting a video";
14597
+ readonly body: string;
14598
+ };
13812
14599
  readonly voice: {
13813
14600
  readonly title: "Voice-over";
13814
14601
  readonly body: string;
@@ -14134,6 +14921,42 @@ declare const en: {
14134
14921
  readonly engine: "{error}";
14135
14922
  };
14136
14923
  };
14924
+ readonly run: {
14925
+ readonly voicePlayFailed: "The voice recording of this line could not be played.";
14926
+ readonly choiceVoicePlayFailed: "The voice recording of this option could not be played.";
14927
+ readonly savedVariableUndeclared: "\u201C{node}\u201D: the running story does not declare this saved variable.";
14928
+ readonly valueNotSerializable: "\u201C{node}\u201D: a saved variable can only hold a value that fits in a save file.";
14929
+ readonly variablesNotSerializable: "Saved and persistent variables can only hold values that fit in a save file.";
14930
+ readonly persistenceUnavailable: "Persistent variables are not available here.";
14931
+ readonly storyMissing: "\u201C{node}\u201D: the story it names is no longer in this project.";
14932
+ readonly sceneMissing: "\u201C{node}\u201D: the scene it names is no longer in this project.";
14933
+ readonly noChoiceMenu: "\u201C{node}\u201D: no menu is showing.";
14934
+ readonly noChoiceAtIndex: "\u201C{node}\u201D: the menu has no option {index}.";
14935
+ readonly saveCaptureFailed: "\u201C{node}\u201D: the screenshot for this save could not be taken. The save was written without one.";
14936
+ readonly relaunchUnavailable: "There is no running story to start again.";
14937
+ readonly storyNotRestartable: "The story that was running could not be started again.";
14938
+ readonly saveStoryMissing: "The story this save belongs to is not in this build.";
14939
+ readonly storyCannotStart: "A story cannot be started here.";
14940
+ readonly noGameToResume: "No game came up to resume into.";
14941
+ readonly resume: {
14942
+ readonly relaunchRowGone: "The row this run started from no longer exists; the scene started from its first row.";
14943
+ readonly sceneGone: "The scene being played no longer exists; the story started from the beginning.";
14944
+ readonly storyGone: "The story being played no longer exists; the game started from the beginning.";
14945
+ readonly rowGoneBefore: "The row being played no longer exists; play resumed from the row before it.";
14946
+ readonly rowGoneSceneStart: "The row being played no longer exists; play resumed from the start of the scene.";
14947
+ };
14948
+ readonly language: {
14949
+ readonly noRestart: "The language changed during a playthrough and the game cannot restart here. Text on screen, the backlog and any voice already playing stay in the previous language.";
14950
+ readonly restartFresh: "The language changed; the game restarts without keeping the playthrough.";
14951
+ readonly saveFailed: "The language changed, but the playthrough could not be saved, so the game did not restart: {error}";
14952
+ readonly parked: "The playthrough was set aside for the language change; the game restarts.";
14953
+ readonly restoring: "A playthrough set aside for a language change is being restored.";
14954
+ readonly resumeFailed: "The playthrough could not be resumed after the language change: {error}";
14955
+ readonly resumeRefused: "The playthrough set aside for the language change was not accepted; it is still stored.";
14956
+ readonly restored: "The playthrough set aside for the language change was restored.";
14957
+ readonly returnFailed: "The game could not return to its start after the language change: {error}";
14958
+ };
14959
+ };
14137
14960
  readonly crash: {
14138
14961
  readonly title: "The game stopped working";
14139
14962
  readonly detail: "Saved games are not affected. Restarting reopens the game at its title screen.";
@@ -14434,6 +15257,11 @@ export type SearchJumpTarget = {
14434
15257
  } | {
14435
15258
  kind: "uiSurface";
14436
15259
  surfaceId: string;
15260
+ }
15261
+ /** A component definition: its own editor tab, the one the component library opens. */
15262
+ | {
15263
+ kind: "uiComponent";
15264
+ componentId: string;
14437
15265
  } | {
14438
15266
  kind: "asset";
14439
15267
  assetId: string;
@@ -14618,465 +15446,137 @@ export declare const AssetExtensions: {
14618
15446
  model: string[];
14619
15447
  other: string[];
14620
15448
  };
14621
- type BlueprintAssetPinKind = "image" | "audio" | "font";
14622
- type BlueprintAssetPinRef = {
14623
- kind: BlueprintAssetPinKind;
14624
- paramKey?: string;
15449
+ type ImageFillMode = "cover" | "contain" | "stretch" | "crop" | "tile";
15450
+ interface ImageFillCropPlacement {
15451
+ leftPct: number;
15452
+ topPct: number;
15453
+ widthPct: number;
15454
+ heightPct: number;
15455
+ }
15456
+ interface ImageFill {
15457
+ mode: ImageFillMode;
15458
+ assetId?: string | null;
15459
+ cropPlacement?: ImageFillCropPlacement;
15460
+ }
15461
+ type UIListItemScope = {
15462
+ item: unknown;
15463
+ index: number;
15464
+ count: number;
15465
+ key: string;
14625
15466
  /**
14626
- * Where the pin's value comes from.
15467
+ * Whether this is the selected row.
14627
15468
  *
14628
- * `"stored"` (the default) means the document holds it: the id sits under `paramKey`, and it is
14629
- * a reference the reverse-lookup index reads and reports.
15469
+ * On the scope because it belongs to the row rather than to any one widget in it: everything in
15470
+ * a selected row reads as selected, and each widget decides for itself whether it has an
15471
+ * appearance row that says so.
15472
+ */
15473
+ selected?: boolean;
15474
+ /**
15475
+ * The shape the owning list declared, carried rather than looked up.
14630
15476
  *
14631
- * `"published"` means the host resolves it while the game runs, and **the pin provably cannot
14632
- * carry a library asset id**. Nothing is stored to read, and an edge out of such a pin is not a
14633
- * reference the index is failing to see - so it must not be reported as one. Marking a pin this
14634
- * way is a claim about what can flow through it: it needs an argument at the declaration, not a
14635
- * guess, because a wrong one hides a real reference.
15477
+ * A field binding is resolved deep inside the element merge, which holds an element and a scope
15478
+ * and no document. Putting the struct on the scope keeps that read a read - the list already
15479
+ * resolved it once for its own columns and keys.
14636
15480
  */
14637
- origin?: "stored" | "published";
15481
+ struct?: UIStructDef | null;
14638
15482
  };
14639
- interface ProjectProps {
14640
- projectPath: string;
14641
- }
14642
- declare class Porject {
14643
- private config;
14644
- constructor(config: ProjectProps);
14645
- getFileName(dest: string[]): string;
14646
- getTargetPath(dest: string[]): string;
14647
- isDir(dest: string[]): boolean;
14648
- resolve(...paths: (Readonly<string[]> | string)[]): string;
14649
- getConfig(): ProjectProps;
15483
+ type UIElementSelection = {
15484
+ editor: "ui";
15485
+ surfaceId: UISurfaceId;
15486
+ elementIds: UIElementId[];
15487
+ primaryId?: UIElementId;
15488
+ };
15489
+ export declare enum AssetSource {
15490
+ Local = "local",
15491
+ Remote = "remote"
14650
15492
  }
15493
+ type AssetResolveMeta<Source extends AssetSource> = Source extends AssetSource.Local ? {} : Source extends AssetSource.Remote ? {
15494
+ /** The address the author gave, verbatim. The only field a refresh sends a request to. */
15495
+ url: string;
15496
+ /** When the stored snapshot was taken, ISO 8601. */
15497
+ fetchedAt: string;
15498
+ /**
15499
+ * The server's validators for the stored snapshot, when it offered any. Sent back as
15500
+ * `If-None-Match` / `If-Modified-Since`, so refreshing an unchanged asset transfers no bytes.
15501
+ */
15502
+ etag?: string;
15503
+ lastModified?: string;
15504
+ /** The response's declared media type. Diagnostic only - the format gate reads the bytes. */
15505
+ contentType?: string;
15506
+ /**
15507
+ * Was to be the runtime cache lifetime, back when a remote asset was going to be fetched by the
15508
+ * shipped game. It never was: the runtime asset manifest carries no URL, so a packaged game has
15509
+ * only the baked bytes, and nothing has ever read this. Kept so records that carry it still
15510
+ * open; the next write drops it. Never write it.
15511
+ *
15512
+ * @deprecated
15513
+ */
15514
+ lifetime?: number;
15515
+ } : never;
14651
15516
  /**
14652
- * The test pipeline - the vocabulary.
14653
- *
14654
- * "Test" here means a check an author runs against *their game*: does it reach an ending, does it
14655
- * survive with no network, does a plugin's feature still behave. It is not vitest; nothing in this
14656
- * file has anything to do with the repo's own unit tests.
14657
- *
14658
- * A test is a definition object in a registry that both Studio's built-in modules and plugins
14659
- * populate (see `registry.ts`), started from Run > Test. Five decisions are load-bearing and worth
14660
- * knowing before editing anything here:
14661
- *
14662
- * - **A test may only say passed / failed / skipped.** `cancelled` and `errored` are verdicts the
14663
- * *host* reaches about a test, and a test that could claim them could lie about being killed.
14664
- * Cancellation arrives as `ctx.signal`; a test that wants "the author aborted me" to count as a
14665
- * failure is free to catch it and return `failed`, which is how a windowed test whose contract is
14666
- * "close the window when satisfied" expresses termination.
14667
- * - **Undeclared capabilities are absent, not throwing.** `requires` is the whole truth about what
14668
- * a test can reach: `ctx.game` is `undefined` unless `game.launch` was declared, exactly as
14669
- * `app.game`'s domains work for runtime plugins. What the picker lists and what the test can do
14670
- * are the same set by construction. `parameters` obeys the same rule from the other direction:
14671
- * `ctx.parameters` carries a value for every id the test *declared* and for no other, so it is a
14672
- * resolved set rather than a free-form bag the picker could put anything into.
14673
- * - **Tests never build prose.** Every author-visible string is a `TestText` - an i18n key for
14674
- * Studio's own tests, a literal for a plugin's (a plugin has no `TranslationKey`s, and its own
14675
- * translator produces plain strings). The console channel and the report tab each render it.
14676
- * - **Severity is on the finding, unlike lint.** A lint rule defers severity to the project's
14677
- * config; a test has no such table, and its verdict is stated separately by `run`. A finding here
14678
- * is evidence for a verdict the test already reached, so it carries its own weight.
14679
- * - **`presentation` is a declaration, not a mechanism.** Whether a window appears is a consequence
14680
- * of asking the host for a game session. The field is what the picker badges and what lets the
14681
- * author know a window is about to open; a headless test that launches one is a host error.
14682
- */
14683
- /**
14684
- * Bumped when a change here would break an already-published plugin.
14685
- *
14686
- * Recorded on every run record so a report kept across an upgrade still says which contract produced
14687
- * it, and surfaced to plugins so a definition can refuse a host older than it needs.
14688
- *
14689
- * **2** added parameters: a test may declare values the author supplies before pressing Start
14690
- * (`TestDefinition.parameters`), and every run context now carries `parameters` - a required member,
14691
- * which is what makes this a break rather than an addition. A definition compiled against 1 keeps
14692
- * working unchanged, since declaring none resolves to `{}`.
14693
- */
14694
- export declare const TEST_PROTOCOL_VERSION = 2;
14695
- /**
14696
- * Stable identifier. Studio's own tests are `narraleaf-studio:<slug>`; a plugin's must be prefixed
14697
- * with its plugin id, which the host enforces at registration rather than trusting.
14698
- */
14699
- export type TestId = string;
14700
- /** Grouping in the picker. `custom` is where a plugin lands when it claims nothing else. */
14701
- export type TestCategory = "integrity" | "runtime" | "compatibility" | "custom";
14702
- /**
14703
- * Does this test put a game window on screen?
14704
- *
14705
- * `headless` is the interesting half: a test with no window can run while the author keeps working,
14706
- * and is the only kind that can run on a frozen workspace.
14707
- */
14708
- export type TestPresentation = "headless" | "windowed";
14709
- /**
14710
- * What the host lends a test. Anything not declared is absent from `TestRunContext`.
14711
- *
14712
- * Deliberately coarse and short. A capability is added here only when a test genuinely cannot be
14713
- * written without it - the failure mode this vocabulary exists to prevent is the one where a narrow
14714
- * API is nominally narrow but hands out a reference that reaches everything.
14715
- */
14716
- export type TestCapability =
14717
- /** Read the project's stories and scenes. Read-only by construction: there is no write half. */
14718
- "project.read"
14719
- /** Launch, observe and stop a game process. Implies a window unless the launch asks otherwise. */
14720
- | "game.launch";
14721
- /**
14722
- * An author-visible string.
14723
- *
14724
- * Two shapes rather than one, because the two producers genuinely differ: Studio's tests name a key
14725
- * in its catalogue and must re-render when the editor language changes, while a plugin has no key
14726
- * space of its own and hands over whatever its own translator already produced.
14727
- */
14728
- export type TestText = {
14729
- key: TranslationKey;
14730
- params?: Record<string, string | number>;
14731
- text?: undefined;
14732
- } | {
14733
- text: string;
14734
- key?: undefined;
14735
- };
14736
- /** Same ladder as the workspace console, so a test log line needs no translation at the boundary. */
14737
- export type TestLogLevel = GameTestLogLevel;
14738
- /**
14739
- * Progress, or `null` to go back to indeterminate.
14740
- *
14741
- * `total` is optional because most interesting tests genuinely do not know it up front (how many
14742
- * routes a story has is what walking it discovers). Omitting it renders the same indeterminate bar
14743
- * the build uses rather than a fake fraction.
14744
- */
14745
- export type TestProgress = {
14746
- completed: number;
14747
- total?: number;
14748
- label?: TestText;
14749
- };
14750
- export type TestFindingSeverity = "error" | "warning" | "info";
14751
- /** One piece of evidence. `target` reuses the global-search navigation layer, so the report tab's click-to-jump is existing machinery. */
14752
- export type TestFinding = {
14753
- severity: TestFindingSeverity;
14754
- message: TestText;
14755
- target?: SearchJumpTarget;
14756
- };
14757
- /**
14758
- * What `run` may return. Note the absence of `cancelled` and `errored`: see the header.
14759
- *
14760
- * `skipped` exists so a test can decline for a reason it only discovers while running (a project
14761
- * with no localisation configured, say) without pretending to have passed. A test that can tell
14762
- * before it starts should say so from `checkAvailability` instead, so the picker greys it out.
15517
+ * Asset interface with user metadata
15518
+ * Stored in assets metadata files
14763
15519
  */
14764
- export type TestVerdict = {
14765
- status: "passed";
14766
- summary?: TestText;
14767
- } | {
14768
- status: "failed";
14769
- summary: TestText;
14770
- } | {
14771
- status: "skipped";
14772
- summary: TestText;
14773
- };
14774
- export type TestStoryRef = {
14775
- id: string;
14776
- name: string;
14777
- };
14778
- export type TestSceneRef = {
15520
+ export interface Asset<Type extends AssetType = AssetType, Source extends AssetSource = AssetSource> {
14779
15521
  id: string;
15522
+ type: Type;
14780
15523
  name: string;
14781
- };
14782
- /**
14783
- * `project.read`.
14784
- *
14785
- * Intentionally thin in v1 - it is the read half of the story service a plugin already has, and no
14786
- * more. This is the protocol's designated extension point: a test that needs the scene *graph*
14787
- * (route reachability, dead ends) needs a shape added here, and adding it is a protocol change with
14788
- * a version bump rather than something a test reaches around.
14789
- *
14790
- * Studio's own built-in tests do not go through this handle at all; they are registered from inside
14791
- * the workspace and close over its context directly, the same way `builtInPanels` differ from
14792
- * `services.ui.panels`. This handle exists so a *plugin* has a bounded way in.
14793
- */
14794
- export type TestProjectHandle = {
14795
- readonly projectPath: string;
14796
- listStories(): Promise<TestStoryRef[]>;
14797
- listScenes(storyId: string): Promise<TestSceneRef[]>;
14798
- };
14799
- /**
14800
- * How a game process ended, and what it said on the way out.
14801
- *
14802
- * These are the wire shapes from `@shared/types/gameTest`, re-exported under the names an author
14803
- * touches. The distinction they carry is the whole reason the pipeline needed main-process work:
14804
- * before it, a crashed preview and an author closing the window were the same event to Studio (an
14805
- * exit logged at `verbose`, status polled back to `idle`). A test whose pass condition is "the
14806
- * author closed the window" cannot be written without it.
14807
- */
14808
- export type TestGameExitReason = GameTestExitReason;
14809
- export type TestGameExit = GameTestExit;
14810
- export type TestGameEvent = GameTestEvent;
14811
- type TestGameCommand = GameTestCommand;
14812
- export type TestGameLaunchOptions = {
15524
+ hash: string;
15525
+ ext?: string;
15526
+ source: Source;
15527
+ meta: AssetResolveMeta<Source>;
15528
+ tags: string[];
15529
+ description: string;
15530
+ groupId?: string;
14813
15531
  /**
14814
- * `"blocked"` starts the game with no network access at all - the host applies it to the
14815
- * launched process, so a game that reaches for the network fails there rather than in a mock.
15532
+ * Editor-authored data that rides with the asset record. Persisted in the assets metadata,
15533
+ * which is a project file under version control - so this is for things the *author* decided
15534
+ * (and would want to keep and share), never for derived caches. Anything recomputable belongs
15535
+ * under {@link ProjectNameConvention.EditorThumbnailCache}'s neighbours in `editor/cache/`.
14816
15536
  */
14817
- network?: "allow" | "blocked";
14818
- };
14819
- export type TestGameSession = {
14820
- readonly id: string;
14821
- /** Returns an unsubscribe. Events emitted before the first listener are replayed to it. */
14822
- onEvent(listener: (event: TestGameEvent) => void): () => void;
15537
+ extras?: AssetExtras;
15538
+ }
15539
+ type AssetAudioLoop = StoredAudioClipRegion;
15540
+ interface AssetExtras {
15541
+ /** Audio only: the loop region shown and edited by the audio preview. */
15542
+ audioLoop?: AssetAudioLoop;
15543
+ /** Audio only: the gain set in the audio preview, to balance loudness across clips. */
15544
+ audioGain?: StoredAudioGain;
14823
15545
  /**
14824
- * Ask the game to do something: start a story, advance a line, pick an option.
15546
+ * Model bundles only: the entry file the author chose, relative to the bundle root.
14825
15547
  *
14826
- * Resolves `true` when the command reached the game and `false` when it could not - a session
14827
- * that has already exited, or one whose game never opened its control channel. It never says the
14828
- * game *did* it: the game is a separate process, and what happened comes back through
14829
- * {@link onEvent} like everything else it says. A test that needs to know waits for the
14830
- * observation, and treats one that never arrives as the answer it is.
14831
- */
14832
- sendCommand(command: TestGameCommand): Promise<boolean>;
14833
- /** Resolves once the process is gone, whatever the reason. Safe to call more than once. */
14834
- waitForExit(): Promise<TestGameExit>;
14835
- /** Graceful shutdown, then force. Resolves when the process is gone. */
14836
- stop(): Promise<void>;
14837
- };
14838
- /**
14839
- * `game.launch`.
14840
- *
14841
- * One session at a time per run: the host refuses a second `launch()` while one is alive, because
14842
- * two preview processes contend for the same compiled artifact directory and the second would
14843
- * silently win.
14844
- */
14845
- export type TestGameHandle = {
14846
- launch(options?: TestGameLaunchOptions): Promise<TestGameSession>;
14847
- };
14848
- export type TestRunContext = {
14849
- readonly runId: string;
14850
- readonly protocolVersion: typeof TEST_PROTOCOL_VERSION;
14851
- /**
14852
- * Aborted when the author cancels. A test that ignores it is killed anyway once its promise
14853
- * settles, but its findings are kept - a cancelled run is still evidence.
15548
+ * The only part of a bundle that is persisted rather than re-read from disk, because it is the
15549
+ * only part that is a *decision*. `files` and `size` are observations and are recomputed on
15550
+ * every read; the entry is a guess Studio made from the file tree that the author may have
15551
+ * corrected, and a guess that silently re-runs would undo the correction.
15552
+ *
15553
+ * Absent means "use the detected entry" - so a bundle whose detection is unambiguous carries no
15554
+ * extra state at all, and re-detection keeps working if a later Studio learns a new format.
14854
15555
  */
14855
- readonly signal: AbortSignal;
15556
+ modelEntry?: string;
14856
15557
  /**
14857
- * What the author chose, keyed by parameter id.
15558
+ * Text assets only: the encoding the author said this file is in.
14858
15559
  *
14859
- * Resolved by the host from the declarations, never passed through from the picker: an id the
14860
- * test did not declare is dropped, and a value the declaration cannot account for (an option
14861
- * that has since disappeared) falls back to the default. So a test reads its own vocabulary and
14862
- * nothing else, and `{}` for a test that declares none.
15560
+ * A property of the *file*, not of the window, which is why it rides the record into version
15561
+ * control rather than sitting in session state: the point of a shared plan file is that the
15562
+ * colleague who opens the GBK spreadsheet next gets it right without having to know. Outranks
15563
+ * the byte-order-mark sniff on open, because an author who said so has said more than a
15564
+ * heuristic can.
15565
+ *
15566
+ * Written only when the author explicitly reopens or saves under an encoding. Absent - the
15567
+ * normal state - means "sniff the BOM, then UTF-8", so merely reading a file never produces a
15568
+ * change to commit.
14863
15569
  */
14864
- readonly parameters: TestParameterValues;
14865
- log(level: TestLogLevel, message: TestText): void;
14866
- progress(progress: TestProgress | null): void;
14867
- report(finding: TestFinding): void;
14868
- /** Present iff `project.read` was declared. */
14869
- readonly project?: TestProjectHandle;
14870
- /** Present iff `game.launch` was declared. */
14871
- readonly game?: TestGameHandle;
14872
- };
14873
- /** Everything `checkAvailability` is allowed to look at - deliberately cheap, it runs on every picker open. */
14874
- export type TestAvailabilityContext = {
14875
- readonly projectPath: string;
15570
+ textEncoding?: TextEncodingId;
14876
15571
  /**
14877
- * Whether a freeze that forbids launching a game is in force - a revision view, a manual
14878
- * freeze, an open merge, recovery mode.
15572
+ * Text assets only: the line ending this file uses.
14879
15573
  *
14880
- * False during a live session even though the workspace IS frozen: what a session shows every
14881
- * participant is the working tree, so a game launched from it runs what everybody is looking at
14882
- * and there is nothing for the refusal to protect.
15574
+ * Recorded for the same reason as {@link textEncoding}, and needed at all because a *new* text
15575
+ * file is zero bytes: there is nothing in the content to detect, so the platform that created it
15576
+ * is the only thing that can answer. For a file that has content, the content wins - see
15577
+ * `resolveLineEnding` - and this is only the fallback.
14883
15578
  */
14884
- readonly frozen: boolean;
14885
- /**
14886
- * Whether this project is one Studio will not run anything for - it arrived from a package or a
14887
- * remote source and nobody has vouched for it yet.
14888
- *
14889
- * Passed in for the reason above and one more of its own: the ledger that answers it lives in
14890
- * the main process, so asking costs a round trip, and everything here is synchronous. The host
14891
- * settles the answer when the workspace comes up and hands the settled copy down.
14892
- *
14893
- * `checkAvailability` will not normally see this true - the host refuses every test before it
14894
- * asks a definition anything, because a run is an execution of the project. It is here because
14895
- * `options` is *not* gated: a list that would cost real work to build can decline to build it
14896
- * for a project that cannot run it.
14897
- */
14898
- readonly distrusted: boolean;
14899
- };
14900
- export type TestAvailability = {
14901
- available: true;
14902
- }
14903
- /** Greys the row out and says why. Not an error: an unavailable test is a normal state. */
14904
- | {
14905
- available: false;
14906
- reason: TestText;
14907
- };
14908
- /**
14909
- * One row of a `select` parameter's list.
14910
- *
14911
- * `value` is what the test reads and what is remembered on disk; `label` is what the author picks
14912
- * from. They are separate because the label is a display string that follows the editor language
14913
- * while the value has to survive a language switch and a Studio upgrade unchanged.
14914
- */
14915
- export type TestParameterOption = {
14916
- value: string;
14917
- label: TestText;
14918
- };
14919
- /** A parameter the author answers by picking from a list. */
14920
- export type TestSelectParameterDefinition = {
14921
- id: string;
14922
- kind: "select";
14923
- label: TestText;
14924
- /** One clause about what the value selects, if it needs one. Not a sentence explaining the UI. */
14925
- description?: TestText;
14926
- /**
14927
- * The list to offer, evaluated when the picker opens.
14928
- *
14929
- * Same contract as `checkAvailability`, and for the same reason: it runs on every open, so keep
14930
- * it synchronous, cheap and free of side effects. It is handed the same context, so a list can
14931
- * depend on the project without the definition holding a workspace of its own.
14932
- *
14933
- * **An empty list is a real answer**, not a failure - a project with no endings yet has nothing
14934
- * to walk to. The host treats it as one: the whole test is greyed out naming this parameter,
14935
- * rather than offering a dropdown with nothing in it and a Start that cannot work.
14936
- */
14937
- options(ctx: TestAvailabilityContext): TestParameterOption[];
14938
- /**
14939
- * Which option to start on. Falls back to the first option when absent, and also when it names
14940
- * an option that is not in the list any more.
14941
- */
14942
- defaultValue?: string;
14943
- };
14944
- /** A parameter the author answers with a switch. */
14945
- export type TestBooleanParameterDefinition = {
14946
- id: string;
14947
- kind: "boolean";
14948
- label: TestText;
14949
- /** One clause about what the value selects, if it needs one. Not a sentence explaining the UI. */
14950
- description?: TestText;
14951
- /** Absent means off. */
14952
- defaultValue?: boolean;
14953
- };
14954
- export type TestParameterDefinition = TestSelectParameterDefinition | TestBooleanParameterDefinition;
14955
- /** What one parameter resolves to. A `select` resolves to the chosen option's `value`. */
14956
- export type TestParameterValue = string | boolean;
14957
- /**
14958
- * Resolved parameter values, keyed by parameter id.
14959
- *
14960
- * Only ids the test declared are present - see the header. A test that declares none is handed an
14961
- * empty object, never `undefined`, so reading `ctx.parameters` needs no guard.
14962
- */
14963
- export type TestParameterValues = Readonly<Record<string, TestParameterValue>>;
14964
- export type TestDefinition = {
14965
- id: TestId;
14966
- title: TestText;
14967
- description?: TestText;
14968
- category?: TestCategory;
14969
- presentation: TestPresentation;
14970
- /** Omitted means "nothing" - a pure computation over what it was given. */
14971
- requires?: readonly TestCapability[];
14972
- /**
14973
- * Values the author supplies before pressing Start, drawn as a row of controls in the picker and
14974
- * handed to `run` as `ctx.parameters`.
14975
- *
14976
- * Omitted means the test needs none, which is every test that existed before this: select it,
14977
- * press Start. Two parameters with the same id are one parameter - the first declaration wins,
14978
- * because the resolved values are keyed by id and two rows writing one key could not both be
14979
- * shown honestly.
14980
- */
14981
- parameters?: readonly TestParameterDefinition[];
14982
- /**
14983
- * Evaluated when the picker opens, so keep it synchronous and cheap. Absent means always
14984
- * available; the host still applies its own gates (a `windowed` test is unavailable while the
14985
- * workspace is frozen, and no test runs at all in a project that is not trusted, no matter what
14986
- * this returns).
14987
- */
14988
- checkAvailability?(ctx: TestAvailabilityContext): TestAvailability;
14989
- run(ctx: TestRunContext): Promise<TestVerdict> | TestVerdict;
14990
- };
14991
- export declare enum AssetSource {
14992
- Local = "local",
14993
- Remote = "remote"
14994
- }
14995
- type AssetResolveMeta<Source extends AssetSource> = Source extends AssetSource.Local ? {} : Source extends AssetSource.Remote ? {
14996
- /** The address the author gave, verbatim. The only field a refresh sends a request to. */
14997
- url: string;
14998
- /** When the stored snapshot was taken, ISO 8601. */
14999
- fetchedAt: string;
15000
- /**
15001
- * The server's validators for the stored snapshot, when it offered any. Sent back as
15002
- * `If-None-Match` / `If-Modified-Since`, so refreshing an unchanged asset transfers no bytes.
15003
- */
15004
- etag?: string;
15005
- lastModified?: string;
15006
- /** The response's declared media type. Diagnostic only - the format gate reads the bytes. */
15007
- contentType?: string;
15008
- /**
15009
- * Was to be the runtime cache lifetime, back when a remote asset was going to be fetched by the
15010
- * shipped game. It never was: the runtime asset manifest carries no URL, so a packaged game has
15011
- * only the baked bytes, and nothing has ever read this. Kept so records that carry it still
15012
- * open; the next write drops it. Never write it.
15013
- *
15014
- * @deprecated
15015
- */
15016
- lifetime?: number;
15017
- } : never;
15018
- /**
15019
- * Asset interface with user metadata
15020
- * Stored in assets metadata files
15021
- */
15022
- export interface Asset<Type extends AssetType = AssetType, Source extends AssetSource = AssetSource> {
15023
- id: string;
15024
- type: Type;
15025
- name: string;
15026
- hash: string;
15027
- ext?: string;
15028
- source: Source;
15029
- meta: AssetResolveMeta<Source>;
15030
- tags: string[];
15031
- description: string;
15032
- groupId?: string;
15033
- /**
15034
- * Editor-authored data that rides with the asset record. Persisted in the assets metadata,
15035
- * which is a project file under version control - so this is for things the *author* decided
15036
- * (and would want to keep and share), never for derived caches. Anything recomputable belongs
15037
- * under {@link ProjectNameConvention.EditorThumbnailCache}'s neighbours in `editor/cache/`.
15038
- */
15039
- extras?: AssetExtras;
15040
- }
15041
- type AssetAudioLoop = AudioClipRegion;
15042
- interface AssetExtras {
15043
- /** Audio only: the loop region shown and edited by the audio preview. */
15044
- audioLoop?: AssetAudioLoop;
15045
- /**
15046
- * Model bundles only: the entry file the author chose, relative to the bundle root.
15047
- *
15048
- * The only part of a bundle that is persisted rather than re-read from disk, because it is the
15049
- * only part that is a *decision*. `files` and `size` are observations and are recomputed on
15050
- * every read; the entry is a guess Studio made from the file tree that the author may have
15051
- * corrected, and a guess that silently re-runs would undo the correction.
15052
- *
15053
- * Absent means "use the detected entry" - so a bundle whose detection is unambiguous carries no
15054
- * extra state at all, and re-detection keeps working if a later Studio learns a new format.
15055
- */
15056
- modelEntry?: string;
15057
- /**
15058
- * Text assets only: the encoding the author said this file is in.
15059
- *
15060
- * A property of the *file*, not of the window, which is why it rides the record into version
15061
- * control rather than sitting in session state: the point of a shared plan file is that the
15062
- * colleague who opens the GBK spreadsheet next gets it right without having to know. Outranks
15063
- * the byte-order-mark sniff on open, because an author who said so has said more than a
15064
- * heuristic can.
15065
- *
15066
- * Written only when the author explicitly reopens or saves under an encoding. Absent - the
15067
- * normal state - means "sniff the BOM, then UTF-8", so merely reading a file never produces a
15068
- * change to commit.
15069
- */
15070
- textEncoding?: TextEncodingId;
15071
- /**
15072
- * Text assets only: the line ending this file uses.
15073
- *
15074
- * Recorded for the same reason as {@link textEncoding}, and needed at all because a *new* text
15075
- * file is zero bytes: there is nothing in the content to detect, so the platform that created it
15076
- * is the only thing that can answer. For a file that has content, the content wins - see
15077
- * `resolveLineEnding` - and this is only the fallback.
15078
- */
15079
- textEol?: PersistedTextEol;
15579
+ textEol?: PersistedTextEol;
15080
15580
  /**
15081
15581
  * Superseded by {@link audioLoop}, which replaced a free list of markers with the one in/out
15082
15582
  * pair a clip actually has. Read only so records written by the short-lived cue-point model
@@ -15108,11 +15608,6 @@ export interface AssetGroup {
15108
15608
  createdAt: number;
15109
15609
  updatedAt: number;
15110
15610
  }
15111
- declare class ServiceRegistry {
15112
- private services;
15113
- get<T extends Service>(service: Services): T;
15114
- getAll(): Service[];
15115
- }
15116
15611
  type PuppetAppearanceKind = "puppet" | "live2d" | "spine";
15117
15612
  type CharacterAppearanceKind = "preset" | "layered" | PuppetAppearanceKind;
15118
15613
  type PortraitCrop = {
@@ -15658,58 +16153,6 @@ declare class Character {
15658
16153
  subscribe(handler: () => void): () => void;
15659
16154
  private handleProfileChange;
15660
16155
  }
15661
- type MoveRejectReason = "invalid_surface" | "invalid_target" | "invalid_movers" | "cycle" | "root_locked";
15662
- type MoveUiElementsResult = {
15663
- ok: true;
15664
- } | {
15665
- ok: false;
15666
- reason: MoveRejectReason;
15667
- };
15668
- type UIElementSelection = {
15669
- editor: "ui";
15670
- surfaceId: UISurfaceId;
15671
- elementIds: UIElementId[];
15672
- primaryId?: UIElementId;
15673
- };
15674
- type ImageFillMode = "cover" | "contain" | "stretch" | "crop" | "tile";
15675
- interface ImageFillCropPlacement {
15676
- leftPct: number;
15677
- topPct: number;
15678
- widthPct: number;
15679
- heightPct: number;
15680
- }
15681
- interface ImageFill {
15682
- mode: ImageFillMode;
15683
- assetId?: string | null;
15684
- cropPlacement?: ImageFillCropPlacement;
15685
- }
15686
- type UIListItemScope = {
15687
- item: unknown;
15688
- index: number;
15689
- count: number;
15690
- key: string;
15691
- /**
15692
- * Whether this is the selected row.
15693
- *
15694
- * On the scope because it belongs to the row rather than to any one widget in it: everything in
15695
- * a selected row reads as selected, and each widget decides for itself whether it has an
15696
- * appearance row that says so.
15697
- */
15698
- selected?: boolean;
15699
- /**
15700
- * The shape the owning list declared, carried rather than looked up.
15701
- *
15702
- * A field binding is resolved deep inside the element merge, which holds an element and a scope
15703
- * and no document. Putting the struct on the scope keeps that read a read - the list already
15704
- * resolved it once for its own columns and keys.
15705
- */
15706
- struct?: UIStructDef | null;
15707
- };
15708
- type BehaviorNodeExecuteResult = {
15709
- nextPort?: string;
15710
- nextPorts?: string[];
15711
- outputValues?: Record<string, unknown>;
15712
- };
15713
16156
  type StoryBlockSelection = {
15714
16157
  editor: "story";
15715
16158
  tabId: string;
@@ -15942,6 +16385,13 @@ type LiveUIOp =
15942
16385
  */
15943
16386
  updates?: readonly LiveUIElementRef[];
15944
16387
  };
16388
+ type MoveRejectReason = "invalid_surface" | "invalid_target" | "invalid_movers" | "cycle" | "root_locked";
16389
+ type MoveUiElementsResult = {
16390
+ ok: true;
16391
+ } | {
16392
+ ok: false;
16393
+ reason: MoveRejectReason;
16394
+ };
15945
16395
  declare const UI_EDITOR_CLIPBOARD_VERSION: 1;
15946
16396
  declare const UI_EDITOR_CLIPBOARD_KIND: "narraleaf.ui.elements";
15947
16397
  type UIEditorClipboardSource = {
@@ -16022,8 +16472,28 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
16022
16472
  private readonly contentRevisions;
16023
16473
  protected init(ctx: WorkspaceContext, depend: (services: Service[]) => Promise<void>): Promise<void>;
16024
16474
  getDocument(): UIDocument;
16475
+ /**
16476
+ * The project's document: every page with its elements, and every component definition.
16477
+ *
16478
+ * The same object as {@link getDocument} here. It is its own method for the component editor,
16479
+ * whose document service answers `getDocument` with a view of one definition - the definition's
16480
+ * elements and none of the pages' - and answers this with the project's. Whatever asks where a
16481
+ * Page widget's page leads (what that page places, what its own Page widgets draw) asks this.
16482
+ */
16483
+ getPageDocument(): UIDocument;
16025
16484
  load(): Promise<UIDocument>;
16485
+ /**
16486
+ * Write `document` now instead of waiting for the auto-save.
16487
+ *
16488
+ * A write that fails is handed back to the auto-saver, which retries it on its backoff. This
16489
+ * cancels the saver's pending write because it supersedes it, and without handing it back the
16490
+ * change would wait for the author's next edit - while the save-failure notice, told this file
16491
+ * is one a saver retries, says it is being retried. Not before the document is loaded: a seed
16492
+ * written while the project opens has nothing for the saver to write, and its failure fails the
16493
+ * open.
16494
+ */
16026
16495
  save(document: UIDocument): Promise<void>;
16496
+ private writeDocument;
16027
16497
  /**
16028
16498
  * The one route `uidoc.json` goes out by.
16029
16499
  *
@@ -16456,7 +16926,7 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
16456
16926
  private ensureDocumentDir;
16457
16927
  }
16458
16928
  type WidgetLogicEventDispatchKind = "lifecycle" | "interaction";
16459
- type WidgetLogicEventDef = {
16929
+ export type WidgetLogicEventDef = {
16460
16930
  id: string;
16461
16931
  displayName: string;
16462
16932
  description?: string;
@@ -16485,7 +16955,7 @@ type WidgetLogicWritablePropDef = {
16485
16955
  displayName: string;
16486
16956
  description?: string;
16487
16957
  };
16488
- type WidgetLogicApi = {
16958
+ export type WidgetLogicApi = {
16489
16959
  supportsPrivateBlueprint: boolean;
16490
16960
  blueprintLabel?: string;
16491
16961
  events: readonly WidgetLogicEventDef[];
@@ -16819,6 +17289,14 @@ interface TagsFieldDefinition<TData = any> extends BaseFieldDefinition<TData> {
16819
17289
  getValue: (data: TData) => string[];
16820
17290
  addTag: (data: TData, tag: string) => void | Promise<void>;
16821
17291
  removeTag: (data: TData, tag: string) => void | Promise<void>;
17292
+ /**
17293
+ * What a stored tag's chip says, or null for a tag that gets no chip. Absent: every tag is its
17294
+ * own chip, as written.
17295
+ *
17296
+ * Only the display changes. The list still holds every stored tag, so adding one keeps the tags
17297
+ * that are not drawn, and removing a chip removes exactly the tag it stands for.
17298
+ */
17299
+ formatTag?: (data: TData, tag: string) => string | null;
16822
17300
  }
16823
17301
  interface InfoFieldDefinition<TData = any> extends BaseFieldDefinition<TData> {
16824
17302
  type: "info";
@@ -16912,254 +17390,668 @@ type UIInspectorData = {
16912
17390
  /** Set when inspecting a canvas widget; required for Blueprint M2 binding UI. */
16913
17391
  surfaceId?: string;
16914
17392
  };
16915
- type DockerBarItem = DockerBarButton | DockerBarSelect | DockerBarNumberInput | DockerBarSeparator;
16916
- type DockerBarButton = {
16917
- kind: "button";
16918
- id: string;
16919
- icon?: LucideIcon;
16920
- label?: string;
16921
- tooltip?: string;
16922
- disabled?: boolean;
16923
- active?: boolean;
16924
- onClick: () => void;
17393
+ type DockerBarItem = DockerBarButton | DockerBarSelect | DockerBarNumberInput | DockerBarSeparator;
17394
+ type DockerBarButton = {
17395
+ kind: "button";
17396
+ id: string;
17397
+ icon?: LucideIcon;
17398
+ label?: string;
17399
+ tooltip?: string;
17400
+ disabled?: boolean;
17401
+ active?: boolean;
17402
+ onClick: () => void;
17403
+ };
17404
+ type DockerBarSelect = {
17405
+ kind: "select";
17406
+ id: string;
17407
+ label?: string;
17408
+ tooltip?: string;
17409
+ value: string | number;
17410
+ options: {
17411
+ value: string | number;
17412
+ label: string;
17413
+ }[];
17414
+ /** Added for the read-only surface: without it a frozen workspace still offered the dropdown. */
17415
+ disabled?: boolean;
17416
+ onChange: (value: string | number) => void;
17417
+ };
17418
+ type DockerBarNumberInput = {
17419
+ kind: "number";
17420
+ id: string;
17421
+ label?: string;
17422
+ tooltip?: string;
17423
+ value: number;
17424
+ min?: number;
17425
+ max?: number;
17426
+ step?: number;
17427
+ disabled?: boolean;
17428
+ readOnly?: boolean;
17429
+ onChange: (value: number) => void;
17430
+ placeholder?: string;
17431
+ inputProps?: React$1.InputHTMLAttributes<HTMLInputElement>;
17432
+ };
17433
+ type DockerBarSeparator = {
17434
+ kind: "separator";
17435
+ id: string;
17436
+ };
17437
+ type FloatingToolbarButton = {
17438
+ kind: "button";
17439
+ id: string;
17440
+ icon?: LucideIcon;
17441
+ label?: string;
17442
+ tooltip?: string;
17443
+ disabled?: boolean;
17444
+ onClick: () => void;
17445
+ };
17446
+ type FloatingToolbarItem = FloatingToolbarButton;
17447
+ type DefaultChildElementContext = {
17448
+ element: UIElement;
17449
+ generateId: () => string;
17450
+ };
17451
+ type DefaultChildElementResult = {
17452
+ elementPatch?: Partial<UIElement>;
17453
+ children: UIElement[];
17454
+ };
17455
+ type BehaviorNodeExecuteResult = {
17456
+ nextPort?: string;
17457
+ nextPorts?: string[];
17458
+ outputValues?: Record<string, unknown>;
17459
+ };
17460
+ type BlueprintPinSemantic = "exec" | "data";
17461
+ export type BlueprintNodePinDef = {
17462
+ id: string;
17463
+ kind: "input" | "output";
17464
+ semantic: BlueprintPinSemantic;
17465
+ /** Loose type tag for data pins (e.g. boolean, string) */
17466
+ valueType?: string;
17467
+ label?: string;
17468
+ /** Optional inputs render as inactive until wired and resolve to runtime default/undefined when omitted. */
17469
+ optional?: boolean;
17470
+ /**
17471
+ * When true, the flow node may show a hover-only control to open an on-card input for this pin
17472
+ * when it is unwired. Only valid with kind=input, semantic=data, and valueType
17473
+ * string|integer|float|boolean.
17474
+ */
17475
+ allowInlineLiteral?: boolean;
17476
+ /**
17477
+ * Set when this pin carries a library asset id. Read by the asset reverse-lookup index, which
17478
+ * walks the catalogue rather than a hard-coded list of param names - so a node added later is
17479
+ * covered by declaring it here and nowhere else.
17480
+ */
17481
+ assetRef?: BlueprintAssetPinRef;
17482
+ /**
17483
+ * This output's own answer to {@link BlueprintNodeDeclaration.assetNames}, for the one node in
17484
+ * which outputs differ: `Get All Properties` reads a font the project picked beside a text the
17485
+ * game may have written.
17486
+ */
17487
+ assetName?: BlueprintAssetNameFlow;
17488
+ };
17489
+ /**
17490
+ * Where the strings a node outputs come from, as far as an asset name is concerned.
17491
+ *
17492
+ * A game package carries every library asset whose name is written down somewhere in the project,
17493
+ * so an asset name that reaches a picture, a sound or a typeface is only a problem when it is put
17494
+ * together while the game runs. Every node whose outputs can carry a string says which of three
17495
+ * things it does, and the asset-name judgement (`assetNameFlow`) follows values through it by that:
17496
+ *
17497
+ * - `"forward"`: every output is one of the node's inputs, part of one, or a collection of them -
17498
+ * Memo, Array Get, Get JSON Field, Make Object. An asset name comes out only if one went in.
17499
+ * - `"written"`: every output is written down in the project - a literal, the Gallery catalogue, a
17500
+ * localized string, the picture an element holds (every write to which is itself checked).
17501
+ * - `"assembled"`: an output may be a string that exists only while the game runs - Concat, Format,
17502
+ * what the player typed, what a server sent. A name out of one of these is refused where it is
17503
+ * used as an asset.
17504
+ *
17505
+ * **Undeclared means `"assembled"`.** The bar only moves the safe way: a node that says nothing is
17506
+ * assumed to make its strings up, and a registry test fails for any built-in node with a
17507
+ * string-carrying output that has not said which. Plugins declare it the same way; a plugin node
17508
+ * that says nothing is treated as assembling.
17509
+ */
17510
+ export type BlueprintAssetNameFlow = "forward" | "written" | "assembled";
17511
+ type BlueprintNodeDynamicInputPinsConfig = {
17512
+ /** Param key on node.params for string[] of additional input pin ids. */
17513
+ storageKey: string;
17514
+ /** Data input pin ids from def.pins that cannot be removed. */
17515
+ fixedDataInputIds: readonly string[];
17516
+ /** Generated ids use `${prefix}_${n}` with n increasing until unused. */
17517
+ generatedIdPrefix: string;
17518
+ valueType: string;
17519
+ allowInlineLiteral: boolean;
17520
+ /**
17521
+ * Optional grouped pins generated from one add action. When omitted, one pin id
17522
+ * `${prefix}_${n}` is created with valueType / allowInlineLiteral above.
17523
+ */
17524
+ generatedPinTemplates?: readonly {
17525
+ /** Generated ids use `${prefix}_${n}_${idSuffix}`. */
17526
+ idSuffix: string;
17527
+ label: string;
17528
+ kind?: "input" | "output";
17529
+ semantic?: BlueprintPinSemantic;
17530
+ valueType?: string;
17531
+ optional?: boolean;
17532
+ allowInlineLiteral?: boolean;
17533
+ }[];
17534
+ /**
17535
+ * Number the labels of generated pins by the add that produced them: the first add reads
17536
+ * `Button 1` / `Pressed 1`, the second `Button 2` / `Pressed 2`.
17537
+ *
17538
+ * Off by default, and deliberately so. A template label is otherwise rendered verbatim, which
17539
+ * is right where the pin already carries its own meaning - `Switch String` puts the value it
17540
+ * compares on the pin, so every generated `Case` is told apart by what is written in it. It is
17541
+ * wrong where the pins are interchangeable and their ORDER is the meaning, which is the case
17542
+ * for a row of buttons: unnumbered, the third button and its exec output are indistinguishable
17543
+ * from the first, and an author cannot tell which branch they are wiring.
17544
+ */
17545
+ numberGeneratedPinLabels?: boolean;
17546
+ /** When dynamic output pins exist, insert them before this static output pin id. */
17547
+ outputInsertBeforePinId?: string;
17548
+ /** Optional display label prefix for generated pins. Defaults to "Input". */
17549
+ labelPrefix?: string;
17550
+ /** Optional label for the node-card add button. */
17551
+ addButtonLabel?: string;
17552
+ /** Optional param key storing user-visible labels by generated pin id. */
17553
+ pinLabelParamKey?: string;
17554
+ /** Optional prefix used when initializing labels in pinLabelParamKey. */
17555
+ defaultPinLabelPrefix?: string;
17556
+ /** Optional param key storing per-pin valueType overrides (Record<pinId, valueType>). */
17557
+ pinValueTypeParamKey?: string;
17558
+ /** Allowed valueTypes for the on-card per-pin type picker; presence enables the picker. */
17559
+ pinValueTypeOptions?: readonly string[];
17560
+ /** When true, generated OUTPUT pins also get remove/label/type controls (default: inputs only). */
17561
+ editableGeneratedOutputPins?: boolean;
17562
+ };
17563
+ type BlueprintJsonValueSchema = {
17564
+ kind: "object" | "array" | "string" | "number" | "boolean" | "null";
17565
+ label?: string;
17566
+ /** Object fields are fixed schema entries. Unknown fields are rejected unless allowExtraFields is true. */
17567
+ fields?: readonly {
17568
+ key: string;
17569
+ label?: string;
17570
+ kind: "object" | "array" | "string" | "number" | "boolean" | "null";
17571
+ required?: boolean;
17572
+ }[];
17573
+ allowExtraFields?: boolean;
17574
+ };
17575
+ type BlueprintInspectorParamKind = "string" | "number" | "json" | "color" | "keyboardBinding" | "literal" | "variableRef" | "persistentVariableRef" | "sceneVariableRef" | "savedVariableRef" | "select" | "imageAsset"
17576
+ /** Bare asset-id string (audio has no ImageAsset-style envelope). */
17577
+ | "audioAsset" | "buttonCursor";
17578
+ export type BlueprintInspectorParamSelectOption = {
17579
+ value: string;
17580
+ label: string;
17581
+ meta?: Record<string, string>;
17582
+ };
17583
+ type BlueprintInspectorParamDef = {
17584
+ key: string;
17585
+ label: string;
17586
+ kind: BlueprintInspectorParamKind;
17587
+ /** Optional fixed schema for structured JSON-like values such as Vector2D. */
17588
+ jsonSchema?: BlueprintJsonValueSchema;
17589
+ /**
17590
+ * For `kind: "select"`: static options rendered as a `<select>` dropdown.
17591
+ * When omitted with `kind: "select"`, the node card will look for
17592
+ * dynamically provided options via `dynamicOptionsSource`.
17593
+ */
17594
+ options?: BlueprintInspectorParamSelectOption[];
17595
+ /**
17596
+ * For `kind: "select"` without static `options`: the flow projection
17597
+ * populates options from context data keyed by this source id.
17598
+ * Known sources include `"surfaces"` (available App Surfaces) and
17599
+ * `"frameTargetSurfaces"` (Page targets filtered for the inferred Frame).
17600
+ */
17601
+ dynamicOptionsSource?: string;
17602
+ /** Label for the empty select option. Defaults to "-". */
17603
+ emptyOptionLabel?: string;
17604
+ /**
17605
+ * Filter dynamic select options by comparing another param value with option metadata.
17606
+ * Used for dependent dropdowns such as Story -> Scene.
17607
+ */
17608
+ dynamicOptionsFilter?: {
17609
+ paramKey: string;
17610
+ optionMetaKey: string;
17611
+ };
17612
+ };
17613
+ type BlueprintNodeRole = "normal" | "eventHead" | "fnHead" | "functionEntry" | "reroute" | "dataLiteral" | "elementLiteral" | "elementEventHead" | "imageAssetLiteral" | "valueReturn" | "comment";
17614
+ type BlueprintNodeDeclaration = {
17615
+ type: string;
17616
+ displayName: string;
17617
+ category: string;
17618
+ keywords?: string[];
17619
+ /** Graph kinds where this node may appear */
17620
+ graphKinds: BlueprintGraphKind[];
17621
+ /** Keep registered for old graphs/runtime, but omit from add-node palette. */
17622
+ hideInPalette?: boolean;
17623
+ /** Creates palette entries for bound Element outputs that can be manually wired to this input. */
17624
+ magicElementTarget?: {
17625
+ inputPinId: string;
17626
+ elementTypes?: readonly string[];
17627
+ };
17628
+ /** Pure nodes have no side effects; used for validation hints */
17629
+ isPure: boolean;
17630
+ /**
17631
+ * Opt in to Blueprint Value graphs - the per-property value providers behind a widget's
17632
+ * `valueBindings`.
17633
+ *
17634
+ * Only meaningful on a node the host did not define. The built-in catalogue is admitted there by
17635
+ * review instead (see `isBlueprintNodeAllowedInBlueprintValueGraph`), and there is no way to
17636
+ * review a plugin's node the same way - `isPure` on one is the plugin's own word - so a plugin
17637
+ * declares for itself and what the value runtime already does is the real limit. It is executed
17638
+ * like any other node, its capability-gated domains are absent while the editor previews it, and
17639
+ * a graph that never reaches Return Value leaves the binding on whatever it resolved last. The
17640
+ * sharpest of those limits: the runtime records the Element and property reads it resolves and
17641
+ * reruns the binding when those change, and it cannot see what a plugin node read - so a binding
17642
+ * built on one refreshes on the host's dependencies, never on the plugin's.
17643
+ */
17644
+ allowInBlueprintValueGraph?: boolean;
17645
+ /**
17646
+ * The node reads the list row that is in scope while it runs, so it belongs only where a row can
17647
+ * be: on an element a list draws once per row, or on the list itself, whose item heads each run
17648
+ * for one row. Enforced by `isBlueprintNodeAllowedInGraphContext`, so the palette and the graph
17649
+ * validator answer it the same way.
17650
+ */
17651
+ requiresListItemContext?: boolean;
17652
+ /**
17653
+ * Where this node's string-carrying outputs come from - see {@link BlueprintAssetNameFlow}.
17654
+ * Absent reads as `"assembled"`.
17655
+ */
17656
+ assetNames?: BlueprintAssetNameFlow;
17657
+ /** Latent/async execution (delay, host awaits) - disallowed in function graphs */
17658
+ isLatent?: boolean;
17659
+ pins: BlueprintNodePinDef[];
17660
+ /** When set, users may add/remove extra data input pins (persisted in params). */
17661
+ dynamicInputPins?: BlueprintNodeDynamicInputPinsConfig;
17662
+ /**
17663
+ * Grow one pin per field the project's save schema declares (see `@shared/types/saveSchema`).
17664
+ *
17665
+ * Unlike {@link dynamicInputPins}, these come from a project document rather than from this
17666
+ * node's params - which is the point. `Save Game` and `Get Save Metadata` are a contract across
17667
+ * time, so both read one list and grow the same pins in the same order; a per-node list would
17668
+ * be as many copies as there are save nodes, drifting by hand.
17669
+ */
17670
+ saveSchemaPins?: {
17671
+ kind: "input" | "output";
17672
+ };
17673
+ inspectorParams?: BlueprintInspectorParamDef[];
17674
+ role?: BlueprintNodeRole;
17675
+ };
17676
+ interface ProjectProps {
17677
+ projectPath: string;
17678
+ }
17679
+ declare class Porject {
17680
+ private config;
17681
+ constructor(config: ProjectProps);
17682
+ getFileName(dest: string[]): string;
17683
+ getTargetPath(dest: string[]): string;
17684
+ isDir(dest: string[]): boolean;
17685
+ resolve(...paths: (Readonly<string[]> | string)[]): string;
17686
+ getConfig(): ProjectProps;
17687
+ }
17688
+ /**
17689
+ * The test pipeline - the vocabulary.
17690
+ *
17691
+ * "Test" here means a check an author runs against *their game*: does it reach an ending, does it
17692
+ * survive with no network, does a plugin's feature still behave. It is not vitest; nothing in this
17693
+ * file has anything to do with the repo's own unit tests.
17694
+ *
17695
+ * A test is a definition object in a registry that both Studio's built-in modules and plugins
17696
+ * populate (see `registry.ts`), started from Run > Test. Five decisions are load-bearing and worth
17697
+ * knowing before editing anything here:
17698
+ *
17699
+ * - **A test may only say passed / failed / skipped.** `cancelled` and `errored` are verdicts the
17700
+ * *host* reaches about a test, and a test that could claim them could lie about being killed.
17701
+ * Cancellation arrives as `ctx.signal`; a test that wants "the author aborted me" to count as a
17702
+ * failure is free to catch it and return `failed`, which is how a windowed test whose contract is
17703
+ * "close the window when satisfied" expresses termination.
17704
+ * - **Undeclared capabilities are absent, not throwing.** `requires` is the whole truth about what
17705
+ * a test can reach: `ctx.game` is `undefined` unless `game.launch` was declared, exactly as
17706
+ * `app.game`'s domains work for runtime plugins. What the picker lists and what the test can do
17707
+ * are the same set by construction. `parameters` obeys the same rule from the other direction:
17708
+ * `ctx.parameters` carries a value for every id the test *declared* and for no other, so it is a
17709
+ * resolved set rather than a free-form bag the picker could put anything into.
17710
+ * - **Tests never build prose.** Every author-visible string is a `TestText` - an i18n key for
17711
+ * Studio's own tests, a literal for a plugin's (a plugin has no `TranslationKey`s, and its own
17712
+ * translator produces plain strings). The console channel and the report tab each render it.
17713
+ * - **Severity is on the finding, unlike lint.** A lint rule defers severity to the project's
17714
+ * config; a test has no such table, and its verdict is stated separately by `run`. A finding here
17715
+ * is evidence for a verdict the test already reached, so it carries its own weight.
17716
+ * - **`presentation` is a declaration, not a mechanism.** Whether a window appears is a consequence
17717
+ * of asking the host for a game session. The field is what the picker badges and what lets the
17718
+ * author know a window is about to open; a headless test that launches one is a host error.
17719
+ */
17720
+ /**
17721
+ * Bumped when a change here would break an already-published plugin.
17722
+ *
17723
+ * Recorded on every run record so a report kept across an upgrade still says which contract produced
17724
+ * it, and surfaced to plugins so a definition can refuse a host older than it needs.
17725
+ *
17726
+ * **2** added parameters: a test may declare values the author supplies before pressing Start
17727
+ * (`TestDefinition.parameters`), and every run context now carries `parameters` - a required member,
17728
+ * which is what makes this a break rather than an addition. A definition compiled against 1 keeps
17729
+ * working unchanged, since declaring none resolves to `{}`.
17730
+ */
17731
+ export declare const TEST_PROTOCOL_VERSION = 2;
17732
+ /**
17733
+ * Stable identifier. Studio's own tests are `narraleaf-studio:<slug>`; a plugin's must be prefixed
17734
+ * with its plugin id, which the host enforces at registration rather than trusting.
17735
+ */
17736
+ export type TestId = string;
17737
+ /** Grouping in the picker. `custom` is where a plugin lands when it claims nothing else. */
17738
+ export type TestCategory = "integrity" | "runtime" | "compatibility" | "custom";
17739
+ /**
17740
+ * Does this test put a game window on screen?
17741
+ *
17742
+ * `headless` is the interesting half: a test with no window can run while the author keeps working,
17743
+ * and is the only kind that can run on a frozen workspace.
17744
+ */
17745
+ export type TestPresentation = "headless" | "windowed";
17746
+ /**
17747
+ * What the host lends a test. Anything not declared is absent from `TestRunContext`.
17748
+ *
17749
+ * Deliberately coarse and short. A capability is added here only when a test genuinely cannot be
17750
+ * written without it - the failure mode this vocabulary exists to prevent is the one where a narrow
17751
+ * API is nominally narrow but hands out a reference that reaches everything.
17752
+ */
17753
+ export type TestCapability =
17754
+ /** Read the project's stories and scenes. Read-only by construction: there is no write half. */
17755
+ "project.read"
17756
+ /** Launch, observe and stop a game process. Implies a window unless the launch asks otherwise. */
17757
+ | "game.launch";
17758
+ /**
17759
+ * An author-visible string.
17760
+ *
17761
+ * Two shapes rather than one, because the two producers genuinely differ: Studio's tests name a key
17762
+ * in its catalogue and must re-render when the editor language changes, while a plugin has no key
17763
+ * space of its own and hands over whatever its own translator already produced.
17764
+ */
17765
+ export type TestText = {
17766
+ key: TranslationKey;
17767
+ params?: Record<string, string | number>;
17768
+ text?: undefined;
17769
+ } | {
17770
+ text: string;
17771
+ key?: undefined;
17772
+ };
17773
+ /** Same ladder as the workspace console, so a test log line needs no translation at the boundary. */
17774
+ export type TestLogLevel = GameTestLogLevel;
17775
+ /**
17776
+ * Progress, or `null` to go back to indeterminate.
17777
+ *
17778
+ * `total` is optional because most interesting tests genuinely do not know it up front (how many
17779
+ * routes a story has is what walking it discovers). Omitting it renders the same indeterminate bar
17780
+ * the build uses rather than a fake fraction.
17781
+ */
17782
+ export type TestProgress = {
17783
+ completed: number;
17784
+ total?: number;
17785
+ label?: TestText;
16925
17786
  };
16926
- type DockerBarSelect = {
16927
- kind: "select";
16928
- id: string;
16929
- label?: string;
16930
- tooltip?: string;
16931
- value: string | number;
16932
- options: {
16933
- value: string | number;
16934
- label: string;
16935
- }[];
16936
- /** Added for the read-only surface: without it a frozen workspace still offered the dropdown. */
16937
- disabled?: boolean;
16938
- onChange: (value: string | number) => void;
17787
+ export type TestFindingSeverity = "error" | "warning" | "info";
17788
+ /** One piece of evidence. `target` reuses the global-search navigation layer, so the report tab's click-to-jump is existing machinery. */
17789
+ export type TestFinding = {
17790
+ severity: TestFindingSeverity;
17791
+ message: TestText;
17792
+ target?: SearchJumpTarget;
16939
17793
  };
16940
- type DockerBarNumberInput = {
16941
- kind: "number";
16942
- id: string;
16943
- label?: string;
16944
- tooltip?: string;
16945
- value: number;
16946
- min?: number;
16947
- max?: number;
16948
- step?: number;
16949
- disabled?: boolean;
16950
- readOnly?: boolean;
16951
- onChange: (value: number) => void;
16952
- placeholder?: string;
16953
- inputProps?: React$1.InputHTMLAttributes<HTMLInputElement>;
17794
+ /**
17795
+ * What `run` may return. Note the absence of `cancelled` and `errored`: see the header.
17796
+ *
17797
+ * `skipped` exists so a test can decline for a reason it only discovers while running (a project
17798
+ * with no localisation configured, say) without pretending to have passed. A test that can tell
17799
+ * before it starts should say so from `checkAvailability` instead, so the picker greys it out.
17800
+ */
17801
+ export type TestVerdict = {
17802
+ status: "passed";
17803
+ summary?: TestText;
17804
+ } | {
17805
+ status: "failed";
17806
+ summary: TestText;
17807
+ } | {
17808
+ status: "skipped";
17809
+ summary: TestText;
16954
17810
  };
16955
- type DockerBarSeparator = {
16956
- kind: "separator";
17811
+ export type TestStoryRef = {
16957
17812
  id: string;
17813
+ name: string;
16958
17814
  };
16959
- type FloatingToolbarButton = {
16960
- kind: "button";
17815
+ export type TestSceneRef = {
16961
17816
  id: string;
16962
- icon?: LucideIcon;
16963
- label?: string;
16964
- tooltip?: string;
16965
- disabled?: boolean;
16966
- onClick: () => void;
16967
- };
16968
- type FloatingToolbarItem = FloatingToolbarButton;
16969
- type DefaultChildElementContext = {
16970
- element: UIElement;
16971
- generateId: () => string;
17817
+ name: string;
16972
17818
  };
16973
- type DefaultChildElementResult = {
16974
- elementPatch?: Partial<UIElement>;
16975
- children: UIElement[];
17819
+ /**
17820
+ * `project.read`.
17821
+ *
17822
+ * Intentionally thin in v1 - it is the read half of the story service a plugin already has, and no
17823
+ * more. This is the protocol's designated extension point: a test that needs the scene *graph*
17824
+ * (route reachability, dead ends) needs a shape added here, and adding it is a protocol change with
17825
+ * a version bump rather than something a test reaches around.
17826
+ *
17827
+ * Studio's own built-in tests do not go through this handle at all; they are registered from inside
17828
+ * the workspace and close over its context directly, the same way `builtInPanels` differ from
17829
+ * `services.ui.panels`. This handle exists so a *plugin* has a bounded way in.
17830
+ */
17831
+ export type TestProjectHandle = {
17832
+ readonly projectPath: string;
17833
+ listStories(): Promise<TestStoryRef[]>;
17834
+ listScenes(storyId: string): Promise<TestSceneRef[]>;
16976
17835
  };
16977
- type BlueprintPinSemantic = "exec" | "data";
16978
- export type BlueprintNodePinDef = {
16979
- id: string;
16980
- kind: "input" | "output";
16981
- semantic: BlueprintPinSemantic;
16982
- /** Loose type tag for data pins (e.g. boolean, string) */
16983
- valueType?: string;
16984
- label?: string;
16985
- /** Optional inputs render as inactive until wired and resolve to runtime default/undefined when omitted. */
16986
- optional?: boolean;
16987
- /**
16988
- * When true, the flow node may show a hover-only control to open an on-card input for this pin
16989
- * when it is unwired. Only valid with kind=input, semantic=data, and valueType
16990
- * string|integer|float|boolean.
16991
- */
16992
- allowInlineLiteral?: boolean;
17836
+ /**
17837
+ * How a game process ended, and what it said on the way out.
17838
+ *
17839
+ * These are the wire shapes from `@shared/types/gameTest`, re-exported under the names an author
17840
+ * touches. The distinction they carry is the whole reason the pipeline needed main-process work:
17841
+ * before it, a crashed preview and an author closing the window were the same event to Studio (an
17842
+ * exit logged at `verbose`, status polled back to `idle`). A test whose pass condition is "the
17843
+ * author closed the window" cannot be written without it.
17844
+ */
17845
+ export type TestGameExitReason = GameTestExitReason;
17846
+ export type TestGameExit = GameTestExit;
17847
+ export type TestGameEvent = GameTestEvent;
17848
+ type TestGameCommand = GameTestCommand;
17849
+ export type TestGameLaunchOptions = {
16993
17850
  /**
16994
- * Set when this pin carries a library asset id. Read by the asset reverse-lookup index, which
16995
- * walks the catalogue rather than a hard-coded list of param names - so a node added later is
16996
- * covered by declaring it here and nowhere else.
17851
+ * `"blocked"` starts the game with no network access at all - the host applies it to the
17852
+ * launched process, so a game that reaches for the network fails there rather than in a mock.
16997
17853
  */
16998
- assetRef?: BlueprintAssetPinRef;
17854
+ network?: "allow" | "blocked";
16999
17855
  };
17000
- type BlueprintNodeDynamicInputPinsConfig = {
17001
- /** Param key on node.params for string[] of additional input pin ids. */
17002
- storageKey: string;
17003
- /** Data input pin ids from def.pins that cannot be removed. */
17004
- fixedDataInputIds: readonly string[];
17005
- /** Generated ids use `${prefix}_${n}` with n increasing until unused. */
17006
- generatedIdPrefix: string;
17007
- valueType: string;
17008
- allowInlineLiteral: boolean;
17009
- /**
17010
- * Optional grouped pins generated from one add action. When omitted, one pin id
17011
- * `${prefix}_${n}` is created with valueType / allowInlineLiteral above.
17012
- */
17013
- generatedPinTemplates?: readonly {
17014
- /** Generated ids use `${prefix}_${n}_${idSuffix}`. */
17015
- idSuffix: string;
17016
- label: string;
17017
- kind?: "input" | "output";
17018
- semantic?: BlueprintPinSemantic;
17019
- valueType?: string;
17020
- optional?: boolean;
17021
- allowInlineLiteral?: boolean;
17022
- }[];
17856
+ export type TestGameSession = {
17857
+ readonly id: string;
17858
+ /** Returns an unsubscribe. Events emitted before the first listener are replayed to it. */
17859
+ onEvent(listener: (event: TestGameEvent) => void): () => void;
17023
17860
  /**
17024
- * Number the labels of generated pins by the add that produced them: the first add reads
17025
- * `Button 1` / `Pressed 1`, the second `Button 2` / `Pressed 2`.
17861
+ * Ask the game to do something: start a story, advance a line, pick an option.
17026
17862
  *
17027
- * Off by default, and deliberately so. A template label is otherwise rendered verbatim, which
17028
- * is right where the pin already carries its own meaning - `Switch String` puts the value it
17029
- * compares on the pin, so every generated `Case` is told apart by what is written in it. It is
17030
- * wrong where the pins are interchangeable and their ORDER is the meaning, which is the case
17031
- * for a row of buttons: unnumbered, the third button and its exec output are indistinguishable
17032
- * from the first, and an author cannot tell which branch they are wiring.
17863
+ * Resolves `true` when the command reached the game and `false` when it could not - a session
17864
+ * that has already exited, or one whose game never opened its control channel. It never says the
17865
+ * game *did* it: the game is a separate process, and what happened comes back through
17866
+ * {@link onEvent} like everything else it says. A test that needs to know waits for the
17867
+ * observation, and treats one that never arrives as the answer it is.
17033
17868
  */
17034
- numberGeneratedPinLabels?: boolean;
17035
- /** When dynamic output pins exist, insert them before this static output pin id. */
17036
- outputInsertBeforePinId?: string;
17037
- /** Optional display label prefix for generated pins. Defaults to "Input". */
17038
- labelPrefix?: string;
17039
- /** Optional label for the node-card add button. */
17040
- addButtonLabel?: string;
17041
- /** Optional param key storing user-visible labels by generated pin id. */
17042
- pinLabelParamKey?: string;
17043
- /** Optional prefix used when initializing labels in pinLabelParamKey. */
17044
- defaultPinLabelPrefix?: string;
17045
- /** Optional param key storing per-pin valueType overrides (Record<pinId, valueType>). */
17046
- pinValueTypeParamKey?: string;
17047
- /** Allowed valueTypes for the on-card per-pin type picker; presence enables the picker. */
17048
- pinValueTypeOptions?: readonly string[];
17049
- /** When true, generated OUTPUT pins also get remove/label/type controls (default: inputs only). */
17050
- editableGeneratedOutputPins?: boolean;
17051
- };
17052
- type BlueprintJsonValueSchema = {
17053
- kind: "object" | "array" | "string" | "number" | "boolean" | "null";
17054
- label?: string;
17055
- /** Object fields are fixed schema entries. Unknown fields are rejected unless allowExtraFields is true. */
17056
- fields?: readonly {
17057
- key: string;
17058
- label?: string;
17059
- kind: "object" | "array" | "string" | "number" | "boolean" | "null";
17060
- required?: boolean;
17061
- }[];
17062
- allowExtraFields?: boolean;
17869
+ sendCommand(command: TestGameCommand): Promise<boolean>;
17870
+ /** Resolves once the process is gone, whatever the reason. Safe to call more than once. */
17871
+ waitForExit(): Promise<TestGameExit>;
17872
+ /** Graceful shutdown, then force. Resolves when the process is gone. */
17873
+ stop(): Promise<void>;
17063
17874
  };
17064
- type BlueprintInspectorParamKind = "string" | "number" | "json" | "color" | "keyboardBinding" | "literal" | "variableRef" | "persistentVariableRef" | "sceneVariableRef" | "savedVariableRef" | "select" | "imageAsset"
17065
- /** Bare asset-id string (audio has no ImageAsset-style envelope). */
17066
- | "audioAsset" | "buttonCursor";
17067
- export type BlueprintInspectorParamSelectOption = {
17068
- value: string;
17069
- label: string;
17070
- meta?: Record<string, string>;
17875
+ /**
17876
+ * `game.launch`.
17877
+ *
17878
+ * One session at a time per run: the host refuses a second `launch()` while one is alive, because
17879
+ * two preview processes contend for the same compiled artifact directory and the second would
17880
+ * silently win.
17881
+ */
17882
+ export type TestGameHandle = {
17883
+ launch(options?: TestGameLaunchOptions): Promise<TestGameSession>;
17071
17884
  };
17072
- type BlueprintInspectorParamDef = {
17073
- key: string;
17074
- label: string;
17075
- kind: BlueprintInspectorParamKind;
17076
- /** Optional fixed schema for structured JSON-like values such as Vector2D. */
17077
- jsonSchema?: BlueprintJsonValueSchema;
17885
+ export type TestRunContext = {
17886
+ readonly runId: string;
17887
+ readonly protocolVersion: typeof TEST_PROTOCOL_VERSION;
17078
17888
  /**
17079
- * For `kind: "select"`: static options rendered as a `<select>` dropdown.
17080
- * When omitted with `kind: "select"`, the node card will look for
17081
- * dynamically provided options via `dynamicOptionsSource`.
17889
+ * Aborted when the author cancels. A test that ignores it is killed anyway once its promise
17890
+ * settles, but its findings are kept - a cancelled run is still evidence.
17082
17891
  */
17083
- options?: BlueprintInspectorParamSelectOption[];
17892
+ readonly signal: AbortSignal;
17084
17893
  /**
17085
- * For `kind: "select"` without static `options`: the flow projection
17086
- * populates options from context data keyed by this source id.
17087
- * Known sources include `"surfaces"` (available App Surfaces) and
17088
- * `"frameTargetSurfaces"` (Page targets filtered for the inferred Frame).
17894
+ * What the author chose, keyed by parameter id.
17895
+ *
17896
+ * Resolved by the host from the declarations, never passed through from the picker: an id the
17897
+ * test did not declare is dropped, and a value the declaration cannot account for (an option
17898
+ * that has since disappeared) falls back to the default. So a test reads its own vocabulary and
17899
+ * nothing else, and `{}` for a test that declares none.
17089
17900
  */
17090
- dynamicOptionsSource?: string;
17091
- /** Label for the empty select option. Defaults to "-". */
17092
- emptyOptionLabel?: string;
17901
+ readonly parameters: TestParameterValues;
17902
+ log(level: TestLogLevel, message: TestText): void;
17903
+ progress(progress: TestProgress | null): void;
17904
+ report(finding: TestFinding): void;
17905
+ /** Present iff `project.read` was declared. */
17906
+ readonly project?: TestProjectHandle;
17907
+ /** Present iff `game.launch` was declared. */
17908
+ readonly game?: TestGameHandle;
17909
+ };
17910
+ /** Everything `checkAvailability` is allowed to look at - deliberately cheap, it runs on every picker open. */
17911
+ export type TestAvailabilityContext = {
17912
+ readonly projectPath: string;
17093
17913
  /**
17094
- * Filter dynamic select options by comparing another param value with option metadata.
17095
- * Used for dependent dropdowns such as Story -> Scene.
17914
+ * Whether a freeze that forbids launching a game is in force - a revision view, a manual
17915
+ * freeze, an open merge, recovery mode.
17916
+ *
17917
+ * False during a live session even though the workspace IS frozen: what a session shows every
17918
+ * participant is the working tree, so a game launched from it runs what everybody is looking at
17919
+ * and there is nothing for the refusal to protect.
17096
17920
  */
17097
- dynamicOptionsFilter?: {
17098
- paramKey: string;
17099
- optionMetaKey: string;
17100
- };
17921
+ readonly frozen: boolean;
17922
+ /**
17923
+ * Whether this project is one Studio will not run anything for - it arrived from a package or a
17924
+ * remote source and nobody has vouched for it yet.
17925
+ *
17926
+ * Passed in for the reason above and one more of its own: the ledger that answers it lives in
17927
+ * the main process, so asking costs a round trip, and everything here is synchronous. The host
17928
+ * settles the answer when the workspace comes up and hands the settled copy down.
17929
+ *
17930
+ * `checkAvailability` will not normally see this true - the host refuses every test before it
17931
+ * asks a definition anything, because a run is an execution of the project. It is here because
17932
+ * `options` is *not* gated: a list that would cost real work to build can decline to build it
17933
+ * for a project that cannot run it.
17934
+ */
17935
+ readonly distrusted: boolean;
17101
17936
  };
17102
- type BlueprintNodeRole = "normal" | "eventHead" | "fnHead" | "functionEntry" | "reroute" | "dataLiteral" | "elementLiteral" | "elementEventHead" | "imageAssetLiteral" | "valueReturn" | "comment";
17103
- type BlueprintNodeDeclaration = {
17104
- type: string;
17105
- displayName: string;
17106
- category: string;
17107
- keywords?: string[];
17108
- /** Graph kinds where this node may appear */
17109
- graphKinds: BlueprintGraphKind[];
17110
- /** Keep registered for old graphs/runtime, but omit from add-node palette. */
17111
- hideInPalette?: boolean;
17112
- /** Creates palette entries for bound Element outputs that can be manually wired to this input. */
17113
- magicElementTarget?: {
17114
- inputPinId: string;
17115
- elementTypes?: readonly string[];
17116
- };
17117
- /** Pure nodes have no side effects; used for validation hints */
17118
- isPure: boolean;
17937
+ export type TestAvailability = {
17938
+ available: true;
17939
+ }
17940
+ /** Greys the row out and says why. Not an error: an unavailable test is a normal state. */
17941
+ | {
17942
+ available: false;
17943
+ reason: TestText;
17944
+ };
17945
+ /**
17946
+ * One row of a `select` parameter's list.
17947
+ *
17948
+ * `value` is what the test reads and what is remembered on disk; `label` is what the author picks
17949
+ * from. They are separate because the label is a display string that follows the editor language
17950
+ * while the value has to survive a language switch and a Studio upgrade unchanged.
17951
+ *
17952
+ * `name` is what a command line calls the row, for a list whose values are generated ids. A value is
17953
+ * the right thing to remember and the wrong thing to type: `--test-list` would have to print it, and
17954
+ * no id belongs in front of a person. With a name, `--test-list` prints the name in the value's
17955
+ * place and `--test-parameter` takes it (matched without regard to case); the value never leaves the
17956
+ * workspace. Unique among the rows, written in words the author already knows the row by, and not a
17957
+ * translated string - a line has to mean the same thing whatever language Studio is in. A list whose
17958
+ * values are already words needs none.
17959
+ */
17960
+ export type TestParameterOption = {
17961
+ value: string;
17962
+ label: TestText;
17963
+ name?: string;
17964
+ };
17965
+ /** A parameter the author answers by picking from a list. */
17966
+ export type TestSelectParameterDefinition = {
17967
+ id: string;
17968
+ kind: "select";
17969
+ label: TestText;
17970
+ /** One clause about what the value selects, if it needs one. Not a sentence explaining the UI. */
17971
+ description?: TestText;
17119
17972
  /**
17120
- * Opt in to Blueprint Value graphs - the per-property value providers behind a widget's
17121
- * `valueBindings`.
17973
+ * The list to offer, evaluated when the picker opens.
17122
17974
  *
17123
- * Only meaningful on a node the host did not define. The built-in catalogue is admitted there by
17124
- * review instead (see `isBlueprintNodeAllowedInBlueprintValueGraph`), and there is no way to
17125
- * review a plugin's node the same way - `isPure` on one is the plugin's own word - so a plugin
17126
- * declares for itself and what the value runtime already does is the real limit. It is executed
17127
- * like any other node, its capability-gated domains are absent while the editor previews it, and
17128
- * a graph that never reaches Return Value leaves the binding on whatever it resolved last. The
17129
- * sharpest of those limits: the runtime records the Element and property reads it resolves and
17130
- * reruns the binding when those change, and it cannot see what a plugin node read - so a binding
17131
- * built on one refreshes on the host's dependencies, never on the plugin's.
17975
+ * Same contract as `checkAvailability`, and for the same reason: it runs on every open, so keep
17976
+ * it synchronous, cheap and free of side effects. It is handed the same context, so a list can
17977
+ * depend on the project without the definition holding a workspace of its own.
17978
+ *
17979
+ * **An empty list is a real answer**, not a failure - a project with no endings yet has nothing
17980
+ * to walk to. The host treats it as one: the whole test is greyed out naming this parameter,
17981
+ * rather than offering a dropdown with nothing in it and a Start that cannot work.
17132
17982
  */
17133
- allowInBlueprintValueGraph?: boolean;
17983
+ options(ctx: TestAvailabilityContext): TestParameterOption[];
17134
17984
  /**
17135
- * The node reads the list row that is in scope while it runs, so it belongs only where a row can
17136
- * be: on an element a list draws once per row, or on the list itself, whose item heads each run
17137
- * for one row. Enforced by `isBlueprintNodeAllowedInGraphContext`, so the palette and the graph
17138
- * validator answer it the same way.
17985
+ * Which option to start on. Falls back to the first option when absent, and also when it names
17986
+ * an option that is not in the list any more.
17139
17987
  */
17140
- requiresListItemContext?: boolean;
17141
- /** Latent/async execution (delay, host awaits) - disallowed in function graphs */
17142
- isLatent?: boolean;
17143
- pins: BlueprintNodePinDef[];
17144
- /** When set, users may add/remove extra data input pins (persisted in params). */
17145
- dynamicInputPins?: BlueprintNodeDynamicInputPinsConfig;
17988
+ defaultValue?: string;
17989
+ };
17990
+ /** A parameter the author answers with a switch. */
17991
+ export type TestBooleanParameterDefinition = {
17992
+ id: string;
17993
+ kind: "boolean";
17994
+ label: TestText;
17995
+ /** One clause about what the value selects, if it needs one. Not a sentence explaining the UI. */
17996
+ description?: TestText;
17997
+ /** Absent means off. */
17998
+ defaultValue?: boolean;
17999
+ };
18000
+ export type TestParameterDefinition = TestSelectParameterDefinition | TestBooleanParameterDefinition;
18001
+ /** What one parameter resolves to. A `select` resolves to the chosen option's `value`. */
18002
+ export type TestParameterValue = string | boolean;
18003
+ /**
18004
+ * Resolved parameter values, keyed by parameter id.
18005
+ *
18006
+ * Only ids the test declared are present - see the header. A test that declares none is handed an
18007
+ * empty object, never `undefined`, so reading `ctx.parameters` needs no guard.
18008
+ */
18009
+ export type TestParameterValues = Readonly<Record<string, TestParameterValue>>;
18010
+ export type TestDefinition = {
18011
+ id: TestId;
18012
+ title: TestText;
18013
+ description?: TestText;
18014
+ category?: TestCategory;
18015
+ presentation: TestPresentation;
18016
+ /** Omitted means "nothing" - a pure computation over what it was given. */
18017
+ requires?: readonly TestCapability[];
17146
18018
  /**
17147
- * Grow one pin per field the project's save schema declares (see `@shared/types/saveSchema`).
18019
+ * Values the author supplies before pressing Start, drawn as a row of controls in the picker and
18020
+ * handed to `run` as `ctx.parameters`.
17148
18021
  *
17149
- * Unlike {@link dynamicInputPins}, these come from a project document rather than from this
17150
- * node's params - which is the point. `Save Game` and `Get Save Metadata` are a contract across
17151
- * time, so both read one list and grow the same pins in the same order; a per-node list would
17152
- * be as many copies as there are save nodes, drifting by hand.
18022
+ * Omitted means the test needs none, which is every test that existed before this: select it,
18023
+ * press Start. Two parameters with the same id are one parameter - the first declaration wins,
18024
+ * because the resolved values are keyed by id and two rows writing one key could not both be
18025
+ * shown honestly.
17153
18026
  */
17154
- saveSchemaPins?: {
17155
- kind: "input" | "output";
17156
- };
17157
- inspectorParams?: BlueprintInspectorParamDef[];
17158
- role?: BlueprintNodeRole;
18027
+ parameters?: readonly TestParameterDefinition[];
18028
+ /**
18029
+ * Evaluated when the picker opens, so keep it synchronous and cheap. Absent means always
18030
+ * available; the host still applies its own gates (a `windowed` test is unavailable while the
18031
+ * workspace is frozen, and no test runs at all in a project that is not trusted, no matter what
18032
+ * this returns).
18033
+ */
18034
+ checkAvailability?(ctx: TestAvailabilityContext): TestAvailability;
18035
+ run(ctx: TestRunContext): Promise<TestVerdict> | TestVerdict;
17159
18036
  };
18037
+ declare class ServiceRegistry {
18038
+ private services;
18039
+ get<T extends Service>(service: Services): T;
18040
+ getAll(): Service[];
18041
+ }
17160
18042
  interface WorkspaceContext {
17161
18043
  project: Porject;
17162
18044
  services: ServiceRegistry;
18045
+ /**
18046
+ * Whether this window was opened to answer a command line - `--build`, `--test` or `--lint` -
18047
+ * rather than for somebody to work in.
18048
+ *
18049
+ * Read from the window's props when the context is built, so a service knows it before its own
18050
+ * `init` runs. Everything a workspace does *because an author is here* has to ask: a run is a
18051
+ * machine passing through, and what it records about the person whose profile it borrowed is a
18052
+ * fiction nobody can correct afterwards.
18053
+ */
18054
+ commandLineRun: boolean;
17163
18055
  }
17164
18056
  interface IService {
17165
18057
  activate(ctx: WorkspaceContext): Promise<void> | void;
@@ -17372,6 +18264,7 @@ declare const HELP_TOPIC_IDS: readonly [
17372
18264
  "puppetRuntimes",
17373
18265
  "audio",
17374
18266
  "audioClips",
18267
+ "videoClips",
17375
18268
  "voice",
17376
18269
  "localization",
17377
18270
  "brand",
@@ -17794,6 +18687,120 @@ export interface StoryCompilePass {
17794
18687
  /** Called once per scene, before that scene's rows are compiled. */
17795
18688
  scene(ctx: SceneCompileContext): void;
17796
18689
  }
18690
+ /**
18691
+ * The detail on `nl.launch`: where this page sits on the launch's own timeline.
18692
+ *
18693
+ * Every entry's `startTime` is measured from the page's time origin. Add `pageOrigin` to it and it
18694
+ * is measured from the launch instead, which is how a boot phase and the process start end up on
18695
+ * one axis.
18696
+ */
18697
+ type GameLaunchEntryDetail = {
18698
+ /**
18699
+ * What zero is.
18700
+ *
18701
+ * - `process` - the game's process was created (a packaged game, a preview).
18702
+ * - `devMode` - an author asked Studio to run the game (a Dev Mode window).
18703
+ * - `page` - nothing earlier is known, so zero is this page's own origin (the web export).
18704
+ */
18705
+ origin: "process" | "devMode" | "page";
18706
+ /** Milliseconds from zero to this page's time origin. Zero when `origin` is `page`. */
18707
+ pageOrigin: number;
18708
+ /**
18709
+ * What happened between zero and the page, in milliseconds from zero: `appReady` (Electron has
18710
+ * started) and `windowCreated` (the window exists and its page has been asked for) on a
18711
+ * packaged game, `windowCreated` alone in Dev Mode. Empty when `origin` is `page`.
18712
+ */
18713
+ milestones: {
18714
+ name: string;
18715
+ at: number;
18716
+ }[];
18717
+ };
18718
+ /** `nl.story.compile` - a story compiled outside the boot. The boot's own is `nl.boot.story`. */
18719
+ type GameStoryCompileDetail = {
18720
+ storyId: string;
18721
+ sceneId: string | null;
18722
+ };
18723
+ /**
18724
+ * `nl.scene.load` - a scene's gate: from the player asking what to warm for the scene to the last
18725
+ * picture its first frame waits for having landed.
18726
+ *
18727
+ * That is the part of entering a scene that loading decides; a transition the author wrote plays
18728
+ * after it and is not included. A scene whose gate was already warm - or that had nothing in it -
18729
+ * records a span of about zero, which is the honest answer rather than a missing one.
18730
+ */
18731
+ type GameSceneLoadDetail = {
18732
+ /** The scene as the author named it, or null for a scene Studio did not compile (a row launch). */
18733
+ scene: string | null;
18734
+ /** Pictures the scene's first frame waits for. */
18735
+ gated: number;
18736
+ /** Of those, how many were not warm yet when the scene was asked for. */
18737
+ waited: number;
18738
+ /** False when it gave up waiting: something in the gate never landed. */
18739
+ complete: boolean;
18740
+ };
18741
+ /**
18742
+ * `nl.preload.asset` - one asset warmed, from the moment the work on it started to the moment it
18743
+ * settled, loaded or not.
18744
+ *
18745
+ * - `pass: "interface"` - the pass that warms the pages' pictures, sounds and fonts before the first
18746
+ * screen and behind it. `firstScreen` says whether the first screen was waiting for it.
18747
+ * - `pass: "scene"` / `"advance"` - the player warming a picture a scene will show, asked for when
18748
+ * the scene was entered or as the story moved. `band` is how urgently: `gate` holds the frame,
18749
+ * `soon` is a click away, `idle` is speculative. Only pictures that were actually fetched and
18750
+ * decoded appear; speculative ones the player merely noted cost nothing and are not listed. A
18751
+ * picture it already held without a bitmap - fetched earlier as look-ahead, which is how most
18752
+ * scenes' opening pictures arrive, or released by the decoded-image budget - is decoded again,
18753
+ * and its span runs from the moment the scene or the story asked for it to the bitmap being held.
18754
+ */
18755
+ type GamePreloadAssetDetail = {
18756
+ pass: "interface" | "scene" | "advance";
18757
+ kind: "image" | "audio" | "video" | "font";
18758
+ /** The project's id for the asset, or null where only its address is known. */
18759
+ assetId: string | null;
18760
+ /** The address it was loaded from. Opaque; differs between Dev Mode and a build. */
18761
+ url: string;
18762
+ band?: "gate" | "soon" | "idle";
18763
+ firstScreen?: boolean;
18764
+ /** The scene it was warmed for, as the author named it. */
18765
+ scene?: string | null;
18766
+ ok: boolean;
18767
+ };
18768
+ /** `nl.surface.mount` - a page or layer, from being asked for to its first paint being allowed. */
18769
+ type GameSurfaceMountDetail = {
18770
+ surfaceId: string;
18771
+ /** The page's name as the author gave it. */
18772
+ surface: string | null;
18773
+ kind: "page" | "layer";
18774
+ };
18775
+ /** `nl.save.write` - a save, from the request to the file being written. */
18776
+ type GameSaveWriteDetail = {
18777
+ slot: string;
18778
+ screenshot: boolean;
18779
+ ok: boolean;
18780
+ };
18781
+ /** `nl.save.load` - a load, from the request to the player being back on the stage. */
18782
+ type GameSaveLoadDetail = {
18783
+ slot: string;
18784
+ /** `refused`: the save did not fit this build and the run was left where it was. */
18785
+ outcome: "loaded" | "refused" | "failed";
18786
+ };
18787
+ type GameTimelineSpanDetails = {
18788
+ "nl.story.compile": GameStoryCompileDetail;
18789
+ "nl.scene.load": GameSceneLoadDetail;
18790
+ "nl.preload.asset": GamePreloadAssetDetail;
18791
+ "nl.surface.mount": GameSurfaceMountDetail;
18792
+ "nl.save.write": GameSaveWriteDetail;
18793
+ "nl.save.load": GameSaveLoadDetail;
18794
+ };
18795
+ type GameTimelineSpanName = keyof GameTimelineSpanDetails;
18796
+ /**
18797
+ * Every name the game writes to the performance timeline.
18798
+ *
18799
+ * `nl.launch` and `nl.boot.firstFrame` are marks; `nl.boot` and every `nl.boot.<phase>` are measures
18800
+ * with `.start` / `.end` marks beside them; the rest are measures whose `detail` is described by
18801
+ * {@link RuntimePluginTimelineSpanDetails}.
18802
+ */
18803
+ export type RuntimePluginTimelineName = "nl.launch" | "nl.boot" | "nl.boot.firstFrame" | `nl.boot.${"bundle" | "story" | "preload"}` | `nl.boot.${"bundle" | "story" | "preload"}.${"start" | "end"}` | GameTimelineSpanName;
17797
18804
  export type RuntimePluginLogLevel = "info" | "warning" | "error";
17798
18805
  /**
17799
18806
  * What a plugin's node execute actually receives.
@@ -17896,14 +18903,17 @@ export type RuntimeWidgetRendererProps = {
17896
18903
  /**
17897
18904
  * Raise one of this element's own event slots, running whatever the author wired to it.
17898
18905
  *
17899
- * The only route a plugin widget has to the author's graph, and the reason the host's
17900
- * blueprint runtime cannot simply be withheld: the dispatcher that turns a click into
17901
- * `mouseClick` reads a static table of built-in widget types, so a plugin type is not in
17902
- * it and nothing else will ever fire the slot.
18906
+ * The route a plugin widget's own events take to the author's graph - a rating picked, a
18907
+ * page turned - and the reason the host's blueprint runtime cannot simply be withheld. The
18908
+ * host raises the pointer, key and lifecycle events it raises for every widget itself, for
18909
+ * the ones the widget's `logicApi` declares; everything else only the widget knows happened.
18910
+ * An event its `logicApi` does not declare starts nothing.
17903
18911
  *
17904
18912
  * Bound to the element being drawn - a widget cannot raise an event on another one - and
17905
- * to the scope it is being drawn in, so a dispatch from inside a repeated row addresses
17906
- * that row. Pass `options` only to address a different row than the one being drawn.
18913
+ * to the drawing it is in: the row, so a dispatch from inside a repeated row addresses that
18914
+ * row, and the component placement, so a widget placed inside a component reaches the
18915
+ * graph authored on the component. Pass `options` only to address a different row than the
18916
+ * one being drawn.
17907
18917
  *
17908
18918
  * A new function on every render, like anything bound to the current drawing: call it
17909
18919
  * from a handler, and keep it in a ref rather than in an effect's dependency list.
@@ -17923,6 +18933,14 @@ export type RuntimeWidgetRendererProps = {
17923
18933
  export type RuntimeWidgetRendererDef = {
17924
18934
  type: string;
17925
18935
  render: (props: RuntimeWidgetRendererProps) => React$1.ReactElement | null;
18936
+ /**
18937
+ * The events this widget raises - the same object the studio entry's widget module declares.
18938
+ *
18939
+ * The game has no widget module to read it from, so without it here a `dispatchEvent` from
18940
+ * `render` reaches no graph in a game or in Dev Mode, whatever the editor showed. Put the
18941
+ * declaration in the module both entries import, as with the render function.
18942
+ */
18943
+ logicApi?: WidgetLogicApi;
17926
18944
  };
17927
18945
  /** Removes a subscription. Also tracked by the host, so a failed plugin cannot leak listeners. */
17928
18946
  export type RuntimePluginCleanup = () => void;
@@ -18182,6 +19200,16 @@ export type RuntimePluginDiagnostics = {
18182
19200
  /** The engine's image cache, or null when no session is live. */
18183
19201
  imageCache(): RuntimePluginImageCacheStats | null;
18184
19202
  };
19203
+ /**
19204
+ * `process.memory` — how much memory the game's processes hold, as the operating system counts it:
19205
+ * the number a task manager shows, across the main, renderer, GPU and utility processes.
19206
+ *
19207
+ * Asynchronous because only the main process can see them. Polled rather than subscribed, like
19208
+ * `diagnostics`: a plugin asks on its own clock, and each call is a fresh reading.
19209
+ */
19210
+ export type RuntimePluginProcess = {
19211
+ memory(): Promise<GameProcessMemoryReading>;
19212
+ };
18185
19213
  /** `assets` — turn an asset id from the pack into a URL this shell can load. */
18186
19214
  export type RuntimePluginAssets = {
18187
19215
  url(assetId: string): string;
@@ -18314,6 +19342,11 @@ export type RuntimePluginGame = {
18314
19342
  story?: RuntimePluginStory;
18315
19343
  /** Present with `"diagnostics"`. */
18316
19344
  diagnostics?: RuntimePluginDiagnostics;
19345
+ /**
19346
+ * Present with `"process.memory"`, on shells that have processes of their own to count: the
19347
+ * packaged game, a preview, and Dev Mode (narrowed to its window). Absent on the web export.
19348
+ */
19349
+ process?: RuntimePluginProcess;
18317
19350
  /** Present when `contributes.sidecars` is non-empty. */
18318
19351
  sidecar?: RuntimePluginSidecars;
18319
19352
  /** Present when `contributes.externalLinks` is non-empty. */
@@ -18473,8 +19506,43 @@ export type PluginWidgetModule = {
18473
19506
  readonly type: string;
18474
19507
  /** Widget type this one specialises. */
18475
19508
  readonly extends?: string;
18476
- /** Event and effect capabilities, shared with the blueprint tooling. */
19509
+ /**
19510
+ * The events this widget raises, and whether it has a blueprint of its own to raise them on.
19511
+ *
19512
+ * With `supportsPrivateBlueprint: true` the element gets a blueprint like a built-in widget's,
19513
+ * the properties panel lists `events`, and each event starts the graphs whose head is one of its
19514
+ * `headNodeTypes` when `render` calls `dispatchEvent(event.id, payload)` - or, for the pointer,
19515
+ * key and lifecycle events, when the host raises them as it does for its own widgets.
19516
+ *
19517
+ * A head is either one a built-in widget uses for the same event (`blueprint.event.head.mouseClick`,
19518
+ * `blueprint.event.head.init`, `blueprint.event.head.onBroadcast`, ...) or an event-head node this
19519
+ * plugin registers through `app.services.blueprintNodes` with `role: "eventHead"`; its output pins
19520
+ * read the fields of the payload of the same name. An event naming neither is dropped with a
19521
+ * console warning, because nothing could start on it. The runtime entry declares the same object
19522
+ * on its `app.game.widgets.register`, or the game does not know the events exist.
19523
+ */
18477
19524
  readonly logicApi?: WidgetLogicApi;
19525
+ /**
19526
+ * Whether an author may put other elements inside this widget, as they can inside a Container.
19527
+ *
19528
+ * When true the canvas, the layer outline and the insert commands let elements be placed in it,
19529
+ * and `render` receives them already drawn as `children` - placing them is the widget's own job.
19530
+ * Absent or false, it is a leaf.
19531
+ */
19532
+ readonly acceptsChildren?: boolean;
19533
+ /**
19534
+ * The parts this widget builds for itself, when it holds those and nothing else - as the built-in
19535
+ * Slider holds its track and handle.
19536
+ *
19537
+ * Each entry names a slot. `createDefaultChildElements` builds the parts, and each part says which
19538
+ * slot it fills with `extra: { partSlot: "<slot>" }`. The widget then holds exactly those: the
19539
+ * canvas, the layer outline, paste and the insert tool put nothing else inside it, a part cannot
19540
+ * be dragged out of it, and the interface command line refuses a child that names no declared slot.
19541
+ * An author can still select, restyle and delete a part. Declaring slots makes `acceptsChildren`
19542
+ * irrelevant - a widget with parts takes nothing else - and `render` receives the parts as
19543
+ * `children`, like any other children.
19544
+ */
19545
+ readonly partSlots?: readonly string[];
18478
19546
  readonly displayName: string;
18479
19547
  readonly icon: LucideIcon;
18480
19548
  createDefaultElement(): Partial<UIElement>;
@@ -19265,8 +20333,12 @@ export type PluginStorageService = {
19265
20333
  * stays writable, and everything else in the project - a plugin's own stored documents included -
19266
20334
  * does not. Nothing a plugin can write is on the writable side of it, so for a plugin it behaves
19267
20335
  * exactly like the others: `frozen` is true, and the buttons come off.
20336
+ *
20337
+ * `taken-over` means another NarraLeaf Studio has opened this project and this window has stopped
20338
+ * writing it for good - there is no way back within the window. The workspace replaces its editor
20339
+ * with a screen that says so, so a plugin sees it at most once, on its way out.
19268
20340
  */
19269
- export type PluginFreezeReason = "revision" | "manual" | "merge" | "recovery" | "live-session";
20341
+ export type PluginFreezeReason = "revision" | "manual" | "merge" | "recovery" | "live-session" | "taken-over";
19270
20342
  /**
19271
20343
  * The state of the project a plugin's data lives in.
19272
20344
  *
@@ -19542,6 +20614,18 @@ export declare function isPluginDefinition(value: unknown): value is PluginDefin
19542
20614
 
19543
20615
  export {
19544
20616
  BehaviorNodeExecuteResult as RuntimeBlueprintNodeResult,
20617
+ GameLaunchEntryDetail as RuntimePluginLaunchTimelineDetail,
20618
+ GamePreloadAssetDetail as RuntimePluginPreloadAssetTimelineDetail,
20619
+ GameProcessKind as RuntimePluginProcessKind,
20620
+ GameProcessMemoryEntry as RuntimePluginProcessMemoryEntry,
20621
+ GameProcessMemoryReading as RuntimePluginProcessMemory,
20622
+ GameSaveLoadDetail as RuntimePluginSaveLoadTimelineDetail,
20623
+ GameSaveWriteDetail as RuntimePluginSaveWriteTimelineDetail,
20624
+ GameSceneLoadDetail as RuntimePluginSceneLoadTimelineDetail,
20625
+ GameStoryCompileDetail as RuntimePluginStoryCompileTimelineDetail,
20626
+ GameSurfaceMountDetail as RuntimePluginSurfaceMountTimelineDetail,
20627
+ GameTimelineSpanDetails as RuntimePluginTimelineSpanDetails,
20628
+ GameTimelineSpanName as RuntimePluginTimelineSpanName,
19545
20629
  SelectOption$1 as SelectOption,
19546
20630
  };
19547
20631