narraleaf-studio 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/_api.d.ts +1557 -1384
  2. package/package.json +1 -1
package/dist/_api.d.ts CHANGED
@@ -1736,1336 +1736,6 @@ type BlueprintAssetPinRef = {
1736
1736
  */
1737
1737
  origin?: "stored" | "published";
1738
1738
  };
1739
- type AudioClipRegion = {
1740
- /** Offset from the start of the clip, in milliseconds. Where playback starts. */
1741
- inMs?: number;
1742
- /** Offset from the start of the clip, in milliseconds. Where playback stops, or where a loop turns around. */
1743
- outMs?: number;
1744
- /**
1745
- * Offset from the start of the clip, in milliseconds. Where each repeat returns to.
1746
- *
1747
- * Absent means "return to {@link inMs}" - a plain loop, and exactly what every record written
1748
- * before this field existed means. Set past the in point it describes the standard VN
1749
- * **intro→loop**: `inMs..loopStartMs` plays once, then `loopStartMs..outMs` repeats forever.
1750
- *
1751
- * Constrained to `inMs <= loopStartMs < outMs`. A value outside that window is dropped rather
1752
- * than clamped: clamping would invent a loop point the author never marked, while dropping
1753
- * degrades to the plain loop the two other markers already describe.
1754
- */
1755
- loopStartMs?: number;
1756
- /**
1757
- * Playback gain in decibels, never above 0: the engine cannot raise a clip past its own level, so
1758
- * balancing loudness means bringing the loud clips down. Absent is unity. Folded into a volume
1759
- * only by {@link clipVolume}.
1760
- */
1761
- gainDb?: number;
1762
- };
1763
- type StoredAudioClipRegion = Pick<AudioClipRegion, "inMs" | "outMs" | "loopStartMs">;
1764
- type StoredAudioGain = {
1765
- db: number;
1766
- };
1767
- type BlueprintOpenExternalRequest = {
1768
- url: string;
1769
- };
1770
- type BlueprintOpenExternalOutcome = "opened" | "refused" | "failed";
1771
- type BlueprintOpenExternalResult = {
1772
- outcome: BlueprintOpenExternalOutcome;
1773
- /** Human-readable reason, null when the page was handed over. */
1774
- error: string | null;
1775
- };
1776
- type BlueprintGraphKind = "event" | "function" | "macro";
1777
- type GameBuildPlatform = "windows" | "macos" | "linux" | "web" | "android" | "ios";
1778
- declare const UI_STRUCT_FIELD_TYPES: readonly [
1779
- "string",
1780
- "number",
1781
- "boolean",
1782
- "image",
1783
- "color",
1784
- "json"
1785
- ];
1786
- type UIStructFieldType = (typeof UI_STRUCT_FIELD_TYPES)[number];
1787
- type UIStructField = {
1788
- /**
1789
- * Stable identity. Value bindings, node params and generated pins all address a field by this,
1790
- * so renaming `key` costs nothing and reshuffling the list costs nothing.
1791
- */
1792
- id: string;
1793
- /** The property name this field occupies inside an item object. */
1794
- key: string;
1795
- /**
1796
- * What the author called it, shown in the item table header and in every field picker.
1797
- *
1798
- * Optional because `key` is usually already the readable name; a label is what lets a field
1799
- * whose key has to stay ASCII (it travels in JSON to a plugin, a save file, a story variable)
1800
- * still read as words in the editor.
1801
- */
1802
- label?: string;
1803
- type: UIStructFieldType;
1804
- };
1805
- type UIStructId = string;
1806
- type UIStructDef = {
1807
- id: UIStructId;
1808
- fields: UIStructField[];
1809
- };
1810
- type PluginPermissionPersistence = "temporary" | "permanent";
1811
- type PluginFileSystemPermissionMode = "read" | "write" | "readwrite";
1812
- /**
1813
- * Install-time permissions split into two families, and the split is the whole
1814
- * point:
1815
- *
1816
- * - **Author-declared** (`filesystem`, `api`) — privileged Studio controls the
1817
- * plugin asks for explicitly in `permissions[]`.
1818
- * - **Derived** (`runtime`, `sidecar`, `buildDependency`, `externalLink`,
1819
- * `network`) — computed from `contributes` by {@link validatePluginManifest}
1820
- * and *rejected* if written by
1821
- * hand. A capability is declared in exactly one place, so what the prompt shows
1822
- * and what the plugin can actually reach cannot drift apart. Adding a
1823
- * capability widens the permission set, which makes the update re-prompt for
1824
- * free (see `isPermissionSubset`).
1825
- */
1826
- export type PluginInstallPermission = {
1827
- kind: "filesystem";
1828
- path: string;
1829
- mode: PluginFileSystemPermissionMode;
1830
- recursive: boolean;
1831
- } | {
1832
- kind: "api";
1833
- capability: string;
1834
- } | {
1835
- /** Derived from `contributes.runtimeCapabilities`. */
1836
- kind: "runtime";
1837
- capability: PluginRuntimeCapability;
1838
- } | {
1839
- /** Derived from `contributes.sidecars`: a native child process shipped inside the author's game. */
1840
- kind: "sidecar";
1841
- id: string;
1842
- /**
1843
- * Which of the two sidecar shapes this is, carried through from the manifest because the
1844
- * two are different promises: `executable` starts a separate binary, `node` runs the
1845
- * plugin's own JavaScript under the game's Electron as Node. An author told only "this
1846
- * ships a helper program" has not been told which one they are approving.
1847
- *
1848
- * Named apart from `kind` above on purpose - that one says which permission this is, this
1849
- * one says what the sidecar runs.
1850
- */
1851
- sidecarKind: PluginSidecarKind;
1852
- /** `<platform>-<arch>` keys the sidecar ships binaries for. */
1853
- platforms: string[];
1854
- } | {
1855
- /** Derived from `contributes.buildDependencies`: binaries downloaded at build time. */
1856
- kind: "buildDependency";
1857
- id: string;
1858
- /** Distinct hostnames the binaries are fetched from. */
1859
- hosts: string[];
1860
- } | {
1861
- /**
1862
- * Derived from `contributes.externalLinks`: addresses outside the game this plugin may open
1863
- * in the player's browser or platform handler.
1864
- *
1865
- * One permission carrying every pattern rather than one permission each, because what the
1866
- * author is deciding is a single question - "may this plugin send the player out to these
1867
- * places" - and a prompt that asked it four times would be four chances to stop reading.
1868
- * `isPermissionSubset` still compares the patterns one by one, so adding one to a later
1869
- * version widens the set and re-prompts.
1870
- *
1871
- * The patterns are the author's own strings, unrewritten: this list is what the prompt
1872
- * shows, and normalizing a permission before showing it to the person approving it would
1873
- * make the prompt and the manifest two slightly different documents.
1874
- */
1875
- kind: "externalLink";
1876
- patterns: string[];
1877
- } | {
1878
- /**
1879
- * Derived from `contributes.network`: hosts this plugin's runtime code requests bytes from.
1880
- *
1881
- * A separate question from `externalLink` and never folded into it. Opening a page hands an
1882
- * address to the browser the player already uses and nothing comes back; this fetches, and
1883
- * what comes back runs inside the game. An author deciding about one has not decided about
1884
- * the other.
1885
- *
1886
- * The patterns are also what a project's network allowlist shows attributed to this plugin,
1887
- * which is the point of declaring them: a build narrowed to a list still reaches what the
1888
- * author approved here, and the list can say so rather than silently making room.
1889
- */
1890
- kind: "network";
1891
- patterns: string[];
1892
- };
1893
- type PluginSidecarKind = "executable" | "node";
1894
- declare const PluginRuntimeCapability: {
1895
- /** `app.game.store` — plugin-scoped persistent key/value storage. */
1896
- readonly Store: "store";
1897
- /** `app.game.events` — game lifecycle and engine event subscription. */
1898
- readonly Events: "events";
1899
- /** `app.game.state.get` — read story variables. */
1900
- readonly StateRead: "state.read";
1901
- /** `app.game.state.set` — write story variables. */
1902
- readonly StateWrite: "state.write";
1903
- /** `app.game.saves` — list and read save metadata. */
1904
- readonly SavesRead: "saves.read";
1905
- /**
1906
- * `app.game.saves.write` / `.load` — overwrite a save slot and load one.
1907
- *
1908
- * Separate from (and heavier than) `saves.read`: this can destroy a
1909
- * playthrough. It exists because quick-save/quick-load is an ordinary thing
1910
- * for a visual novel to ship — the built-in Quick Save plugin is exactly
1911
- * this — and a capability model that cannot express a feature the product
1912
- * already has would just push authors back around it.
1913
- */
1914
- readonly SavesWrite: "saves.write";
1915
- /** `app.game.ui.overlay` — draw on top of the game. */
1916
- readonly UiOverlay: "ui.overlay";
1917
- /** `app.game.assets` — resolve packaged asset URLs. */
1918
- readonly Assets: "assets";
1919
- /**
1920
- * `app.game.locale` — read and observe the game language, and read the project's own
1921
- * localized text through the same table and fallback chain the `Get Text` node uses.
1922
- */
1923
- readonly Locale: "locale";
1924
- /**
1925
- * `app.game.menu` — own the menu bar above the game.
1926
- *
1927
- * Its own capability rather than part of anything else, because what it grants is *the chrome
1928
- * the player reads the game through*: every row on that bar, and what each one does, is this
1929
- * plugin's to decide. The rows still act through the game's own vocabulary (see
1930
- * `@shared/types/gameMenu`) - nothing here reaches past what a blueprint could already do - but
1931
- * an author approving this is handing over a surface their players will read as the game's own,
1932
- * which is worth a name at install rather than arriving with something else.
1933
- *
1934
- * Absent on shells with no bar to give (the web export, Dev Mode), which is reported the way
1935
- * every environment difference is: the member is not there.
1936
- */
1937
- readonly Menu: "menu";
1938
- /**
1939
- * `app.game.story` — register a compile pass that observes each scene and injects engine
1940
- * actions around its rows.
1941
- *
1942
- * The heaviest of the ten to read, and the one whose weight is easiest to miss: nothing here
1943
- * touches the player's files, and the author still sees only their own document — but a pass
1944
- * runs over *every* scene of the project, sees who speaks in each, and can put actions around
1945
- * lines it did not write. That is a plugin editing the story, at the last moment before it
1946
- * plays, and it is worth a name the author approves at install.
1947
- *
1948
- * It is not `state.write` in disguise and does not imply it: a pass builds actions, it does not
1949
- * run them, and the one piece of runtime state it can touch is a scene-local flag the compiler
1950
- * creates and owns (`SceneCompileContext.runtimeFlag`), never a story variable the author
1951
- * declared.
1952
- */
1953
- readonly StoryCompile: "story.compile";
1954
- /**
1955
- * `app.game.diagnostics` — read what the player's caches are holding.
1956
- *
1957
- * The lightest of the list to grant and the easiest to misread as heavier than it is. Nothing
1958
- * here is authored data: it reports how many images the engine is holding, how many bytes those
1959
- * cost, and what budgets they are being held against. It reads no story, no save and no
1960
- * variable, and every member is a read - there is nothing to set.
1961
- *
1962
- * It is its own capability rather than free like `data` and `config` because a plugin that
1963
- * watches memory is a plugin that runs continuously and reports numbers about the player's
1964
- * machine, and an author should see that named at install. The performance inspector is the
1965
- * plugin this exists for.
1966
- *
1967
- * Absent until a game session is live, which is the same rule every other engine-backed member
1968
- * follows: a plugin's `setup()` runs during boot, and asking then answers null rather than
1969
- * throwing.
1970
- */
1971
- readonly Diagnostics: "diagnostics";
1972
- /**
1973
- * `app.game.process.memory()` — read how much memory the game's processes hold, as the
1974
- * operating system counts it.
1975
- *
1976
- * Its own capability rather than a member of `diagnostics`, because what it reports is not the
1977
- * game's: `diagnostics` describes the engine's caches, which are the game's own bookkeeping,
1978
- * while this is the player's machine - how much of its memory every process of the game is
1979
- * holding, the main process and the GPU process included. An author who approved the first did
1980
- * not approve the second, and a plugin that already holds `diagnostics` must not gain this by
1981
- * an update without being asked again - which is what a capability of its own guarantees.
1982
- *
1983
- * Read-only, and numbers only: sizes and what each process is for, never a path, an id or a
1984
- * command line. Nothing is sent anywhere by the host; what a plugin does with a reading is
1985
- * visible in the code the author installed.
1986
- *
1987
- * Absent where there are no processes to count - the web export - and narrowed in Dev Mode to
1988
- * the window's own renderer, since everything around it is Studio's.
1989
- */
1990
- readonly ProcessMemory: "process.memory";
1991
- };
1992
- type PluginRuntimeCapability = typeof PluginRuntimeCapability[keyof typeof PluginRuntimeCapability];
1993
- /**
1994
- * `app.game.sidecar` has no entry here on purpose: it exists exactly when
1995
- * `contributes.sidecars` is non-empty. Declaring the sidecar *is* the request.
1996
- */
1997
- export interface PluginIdentity {
1998
- id: string;
1999
- name?: string;
2000
- version?: string;
2001
- publisher?: string;
2002
- }
2003
- interface PluginPermissionRequestBase {
2004
- requestId: string;
2005
- plugin: PluginIdentity;
2006
- reason?: string;
2007
- requestedAt?: number;
2008
- }
2009
- export type PluginPermissionRequest = (PluginPermissionRequestBase & {
2010
- kind: "trust";
2011
- persistence?: PluginPermissionPersistence;
2012
- }) | (PluginPermissionRequestBase & {
2013
- kind: "filesystem";
2014
- path: string;
2015
- mode: PluginFileSystemPermissionMode;
2016
- recursive: boolean;
2017
- persistence: PluginPermissionPersistence;
2018
- }) | (PluginPermissionRequestBase & {
2019
- kind: "install";
2020
- source: string;
2021
- permissions?: PluginInstallPermission[];
2022
- persistence?: PluginPermissionPersistence;
2023
- }) | (PluginPermissionRequestBase & {
2024
- kind: "api";
2025
- capability: string;
2026
- persistence?: PluginPermissionPersistence;
2027
- });
2028
- interface PluginPermissionGrantResult {
2029
- requestId: string;
2030
- pluginId: string;
2031
- kind: PluginPermissionRequest["kind"];
2032
- approved: boolean;
2033
- persistence: PluginPermissionPersistence;
2034
- grantedAt?: number;
2035
- }
2036
- export type PluginPermissionPromptResult = PluginPermissionGrantResult | null;
2037
- declare const PluginManifestVersion = 2;
2038
- /**
2039
- * Per-target plugin entry files. Each entry is a prebundled ESM file relative
2040
- * to the plugin package root. At least one target must be declared.
2041
- *
2042
- * - `studio`: loaded in the workspace window only; talks to the
2043
- * `narraleaf-studio/plugin` host API (editor extensions).
2044
- * - `runtime`: loaded in every game execution environment (Dev Mode window,
2045
- * Preview, Production); talks to the `narraleaf-studio/runtime` host API
2046
- * (game logic such as blueprint node execute bindings).
2047
- */
2048
- export type PluginManifestEntries = {
2049
- studio?: string;
2050
- runtime?: string;
2051
- };
2052
- type PluginLocaleContribution = {
2053
- code: string;
2054
- nativeName?: string;
2055
- englishName?: string;
2056
- intl?: string;
2057
- dir?: "ltr" | "rtl";
2058
- messages: string;
2059
- };
2060
- type PluginBinaryPlatformKey = string;
2061
- type PluginSidecarTargetContribution = {
2062
- /** Executable (or, for `kind: "node"`, the .js file) to run. Must appear in `include`. */
2063
- entry: string;
2064
- /**
2065
- * Everything shipped for this platform. Entries are package-relative paths,
2066
- * or `dep:<buildDependencyId>/<path>` to pull an artifact produced by a
2067
- * declared build dependency (that is how third-party redistributables that
2068
- * we may not vendor ourselves reach the pack).
2069
- */
2070
- include: string[];
2071
- /**
2072
- * `sha256` of every package-relative entry in `include`, hex. Verified at
2073
- * install and again at pack time, so a tampered package fails to install
2074
- * instead of silently shipping a different binary. `dep:` entries are covered
2075
- * by the build dependency's own digest instead.
2076
- */
2077
- sha256: Record<string, string>;
2078
- };
2079
- type PluginSidecarContribution = {
2080
- /** Prefixed with the plugin id, like every other contributed identifier. */
2081
- id: string;
2082
- /**
2083
- * `executable` spawns the binary directly and talks to it over stdin/stdout; `node` runs the
2084
- * .js as an Electron utility process and talks to it over `process.parentPort`, which is the
2085
- * one channel a utility process has - it has no stdin.
2086
- */
2087
- kind: PluginSidecarKind;
2088
- /**
2089
- * Newline-delimited JSON, which is the framing and not the channel: where the frames travel is
2090
- * {@link kind}'s business, and only an executable sidecar's travel over stdio. Either way
2091
- * stderr stays a plain log channel.
2092
- *
2093
- * `stdio-jsonl` is the older spelling of the same thing, from when there was only one channel
2094
- * it could mean. Manifests that say it keep working and always will - a published plugin is a
2095
- * file somebody already shipped - but a new one should say `jsonl`.
2096
- */
2097
- transport: "jsonl" | "stdio-jsonl";
2098
- /** `onGameStart` spawns with the window; `onRequest` waits for the first call. */
2099
- autostart: "onGameStart" | "onRequest";
2100
- /** How long the handshake may take before the sidecar counts as unavailable. */
2101
- startupTimeoutMs: number;
2102
- /** Grace period between the shutdown message and SIGTERM. */
2103
- shutdownTimeoutMs: number;
2104
- restart: {
2105
- maxRetries: number;
2106
- backoffMs: number;
2107
- };
2108
- targets: Record<PluginBinaryPlatformKey, PluginSidecarTargetContribution>;
2109
- };
2110
- type PluginBuildDependencyTargetContribution = {
2111
- url: string;
2112
- /** Mandatory. Doubles as the cache key, so re-pointing the URL at identical bytes never re-downloads. */
2113
- sha256: string;
2114
- archive: "zip";
2115
- /** Archive-internal path -> path inside the produced dependency directory. */
2116
- files: Record<string, string>;
2117
- } | {
2118
- url: string;
2119
- sha256: string;
2120
- archive: "none";
2121
- /** Name the downloaded file takes inside the produced dependency directory. */
2122
- fileName: string;
2123
- };
2124
- type PluginBuildDependencyContribution = {
2125
- /** Prefixed with the plugin id. Referenced from sidecar `include` as `dep:<id>/<path>`. */
2126
- id: string;
2127
- /** Shown to the author at install and in build logs; say what the binaries are. */
2128
- description?: string;
2129
- targets: Record<PluginBinaryPlatformKey, PluginBuildDependencyTargetContribution>;
2130
- };
2131
- declare const PLUGIN_BUILD_CONFIG_TYPES: readonly [
2132
- "text",
2133
- "secret"
2134
- ];
2135
- type PluginBuildConfigType = (typeof PLUGIN_BUILD_CONFIG_TYPES)[number];
2136
- declare const PLUGIN_BUILD_CONFIG_SCOPES: readonly [
2137
- "global",
2138
- "variant",
2139
- "platform",
2140
- "variant-platform"
2141
- ];
2142
- type PluginBuildConfigScope = (typeof PLUGIN_BUILD_CONFIG_SCOPES)[number];
2143
- type PluginBuildConfigFieldContribution = {
2144
- /** Unique within the plugin. Keys the stored value; never displayed. */
2145
- key: string;
2146
- /** Author-facing name of the field. */
2147
- label: string;
2148
- /** One line saying what the value is for. */
2149
- description?: string;
2150
- /** `text` is stored in the project; `secret` is not - the project stores a handle. */
2151
- type: PluginBuildConfigType;
2152
- /** Which builds share one value. */
2153
- scope: PluginBuildConfigScope;
2154
- /** Platforms the field applies to. Absent means every platform. */
2155
- platforms?: GameBuildPlatform[];
2156
- /** A build that would ship without a value for this field is refused. */
2157
- required?: boolean;
2158
- };
2159
- export type PluginContributes = {
2160
- /** Blueprint node types this plugin provides (editor def + runtime execute). */
2161
- blueprintNodes?: string[];
2162
- /** Widget element types this plugin provides (editor module + runtime renderer). */
2163
- widgets?: string[];
2164
- /** Studio language packs: locales this plugin adds or fills. */
2165
- locales?: PluginLocaleContribution[];
2166
- /**
2167
- * Plugin storage namespaces to publish with the game, readable at runtime
2168
- * through `app.game.data.readJson(namespace)`.
2169
- *
2170
- * Plugin stores live under the project's `editor/` directory, which is never
2171
- * packaged. A plugin whose runtime needs authored data (catalogs, tables)
2172
- * must list those namespaces here. The list is an explicit allowlist so
2173
- * editor-only plugin state cannot leak into a shipped game by accident.
2174
- */
2175
- runtimeData?: string[];
2176
- /**
2177
- * Test ids this plugin registers with `app.services.tests` (see
2178
- * `docs/plugin-test-protocol.md`). Declaring them lets the Launcher say what
2179
- * a plugin checks before any of its code runs, and registering an
2180
- * undeclared id throws at load.
2181
- *
2182
- * Unlike every other code-backed contribution here, this one derives no
2183
- * install permission — a test only ever executes because the author picked
2184
- * it out of the Run > Test dialog and pressed Start, so there is no ambient
2185
- * capability to consent to at install time.
2186
- */
2187
- tests?: string[];
2188
- /**
2189
- * Save ids the plugin keeps for its own bookkeeping - a quick-save slot, a checkpoint - rather
2190
- * than slots a player chose.
2191
- *
2192
- * The game leaves them out of `List Saves`, and so out of every save screen built on it, the way
2193
- * it leaves out its own autosave ring. Nothing else changes for them: the plugin still writes,
2194
- * loads and finds them through `app.game.saves` (whose `listIds` is the raw store listing and
2195
- * still includes them), and the explicit-id save nodes - `Load Save`, `Delete Save`, the
2196
- * `Get Save` readers - take them like any other id.
2197
- *
2198
- * Prefixed with the plugin id like the type lists above, so a plugin can only reserve ids in its
2199
- * own namespace and can never hide a slot the author's own `Save Game` wrote. That is also why it
2200
- * derives no install permission: it reaches nothing the plugin did not already own.
2201
- */
2202
- reservedSaveIds?: string[];
2203
- /**
2204
- * Capability domains the `runtime` entry may use. Each maps 1:1 onto a
2205
- * namespace on `app.game`, and an undeclared domain is absent from that
2206
- * object rather than present-and-throwing — so what the install prompt
2207
- * listed and what the plugin can reach are the same set by construction.
2208
- */
2209
- runtimeCapabilities?: PluginRuntimeCapability[];
2210
- /** Native child processes shipped inside the author's game. */
2211
- sidecars?: PluginSidecarContribution[];
2212
- /** External binaries fetched (and cached) at build time. */
2213
- buildDependencies?: PluginBuildDependencyContribution[];
2214
- /**
2215
- * Values the author supplies before a build can ship. Declaration only - see
2216
- * {@link PluginBuildConfigFieldContribution} for why there is no registration API and why this
2217
- * grants the plugin nothing.
2218
- */
2219
- buildConfig?: PluginBuildConfigFieldContribution[];
2220
- /**
2221
- * Address patterns this plugin may hand to the player's browser or platform handler, through
2222
- * `app.game.navigation.openExternal`.
2223
- *
2224
- * Unlike everything else here, this one is *not* a list of identifiers the plugin owns - it is a
2225
- * list of places outside the game it may reach, so it derives an install permission the author
2226
- * approves by name (see `PluginInstallPermission` `kind: "externalLink"`) and adding one to a
2227
- * later version re-prompts.
2228
- *
2229
- * Patterns are matched structurally, never as strings: see `isExternalLinkPatternDeclared` in
2230
- * `@shared/types/externalLinkPattern` for the exact semantics. Wildcards are allowed
2231
- * (`https://*.example.com/app/*`) and the scheme is not restricted to `http(s)`, because the
2232
- * storefronts a plugin integrates with speak their own (`steam://run/480`). A handful of schemes
2233
- * are refused at validation whatever a manifest says; that list and its reasoning live in the
2234
- * same module.
2235
- *
2236
- * This is separate from, and grants nothing towards, {@link network}: opening a page and
2237
- * fetching bytes are different acts, and the Open Link node the author writes is a third thing
2238
- * again, governed only by its scheme.
2239
- */
2240
- externalLinks?: string[];
2241
- /**
2242
- * Address patterns this plugin's runtime code requests bytes from.
2243
- *
2244
- * Like {@link externalLinks} and unlike everything else here, this is a list of places outside
2245
- * the game rather than identifiers the plugin owns, so it derives an install permission the
2246
- * author approves by name (`PluginInstallPermission` `kind: "network"`) and adding one to a
2247
- * later version re-prompts.
2248
- *
2249
- * Two rules narrower than the external-link patterns, both for the same reason - what comes
2250
- * back from a fetch runs inside the game, while an opened page does not:
2251
- *
2252
- * - **`http(s)` only.** There is no storefront-scheme case here; a plugin that fetches speaks
2253
- * HTTP.
2254
- * - **The path is written out.** `https://api.example.com/*` is a declaration and
2255
- * `https://api.example.com` is not, because the pattern language reads the second as the
2256
- * single path `/`. It is refused rather than rewritten: this string is what the install
2257
- * prompt shows, and a manifest whose text differs from what was approved is the one thing
2258
- * this whole mechanism exists to prevent.
2259
- *
2260
- * What this reaches is *not* widened by the project's own network allowlist and does not widen
2261
- * it. A build narrowed to a list still reaches these hosts - the author approved them at
2262
- * install - and the list shows them attributed to this plugin rather than quietly making room.
2263
- */
2264
- network?: string[];
2265
- /**
2266
- * The props of a contributed widget that hold words a player reads, by widget type.
2267
- *
2268
- * Each type named here must also be in {@link widgets}. A declared prop is treated as Studio's own
2269
- * text and button labels are: the properties panel offers its words written directly or read
2270
- * from a translation key, the canvas draws a key's words, the words go into the project's
2271
- * translation table and exports, the project checks report a missing translation or key, and
2272
- * the glyph check reads them. In the game the widget's renderer is handed the words already in
2273
- * the player's language, in the prop it declared - it never resolves a key itself.
2274
- *
2275
- * Declared in the manifest rather than at registration because both entries need it - the
2276
- * studio entry's widget is edited by it and the runtime entry's is drawn by it - and a
2277
- * declaration only one of them made would be the half that drifts.
2278
- */
2279
- widgetText?: Record<string, PluginWidgetTextContribution[]>;
2280
- /**
2281
- * The shapes of the rows this plugin's nodes hand out, so a list can be given one and a field
2282
- * read from one by name.
2283
- *
2284
- * A node pin carrying them says `struct:<id>` or `array<struct:<id>>` - `Get Gallery` answers
2285
- * `array<struct:narraleaf.gallery.entry>` - and from then on the plugin's rows are what the
2286
- * engine's own are: a list picks the shape under Edit content and its fields are the plugin's
2287
- * and locked, Get Field offers them, and a list handed rows that lack them is reported.
2288
- *
2289
- * Declared in the manifest rather than at registration because the editor, the running game and
2290
- * the command-line checker all need the shape, and only the manifest is read by all three before
2291
- * any plugin code runs. Ids must start with the plugin id.
2292
- */
2293
- structs?: PluginStructContribution[];
2294
- };
2295
- type PluginStructContribution = {
2296
- /** Prefixed with the plugin id: `narraleaf.gallery.entry`. */
2297
- id: string;
2298
- /** What the editor calls the shape - "Gallery entry". */
2299
- name: string;
2300
- /** `name` per editor locale, matched exactly as `localized` names are. */
2301
- localized?: Record<string, string>;
2302
- /** In the order the rows are read: the list's content editor shows them in this order. */
2303
- fields: {
2304
- key: string;
2305
- type: UIStructFieldType;
2306
- }[];
2307
- };
2308
- /**
2309
- * One prop of a contributed widget whose value is words a player reads.
2310
- *
2311
- * `keyProp` is the prop the widget's translation key is kept in when the author reads the words from
2312
- * one. It defaults to `<prop>LocalizationKey`, and it is the host's to write: the widget never reads it
2313
- * to draw, because the words in `prop` already come from the key when there is one.
2314
- */
2315
- export type PluginWidgetTextContribution = {
2316
- /** The prop holding the words. */
2317
- prop: string;
2318
- /** The prop the translation key is kept in; `<prop>LocalizationKey` when not declared. */
2319
- keyProp?: string;
2320
- /** What the properties panel calls the words. The prop name when not declared. */
2321
- label?: string;
2322
- /** `label` per editor locale, matched exactly as `localized` names are. */
2323
- localized?: Record<string, string>;
2324
- /** Whether the words may run over several lines; one line when not declared. */
2325
- multiline?: boolean;
2326
- };
2327
- /**
2328
- * The plugin's name and description in one editor language. Either may be left out, and the plain
2329
- * manifest field is shown in its place.
2330
- */
2331
- export type PluginManifestLocalizedText = {
2332
- name?: string;
2333
- description?: string;
2334
- };
2335
- /**
2336
- * `name` and `description` per editor locale, keyed by the exact code Studio's language setting
2337
- * stores (`zh`, `ja`, or a code a language pack adds). There is no region matching: an entry under
2338
- * `zh-CN` is never shown, because no Studio locale is called that.
2339
- */
2340
- export type PluginManifestLocalized = Record<string, PluginManifestLocalizedText>;
2341
- export type PluginManifestV2 = Omit<PluginIdentity, "id" | "name" | "version"> & Required<Pick<PluginIdentity, "id" | "name" | "version">> & {
2342
- manifestVersion: typeof PluginManifestVersion;
2343
- description?: string;
2344
- /**
2345
- * Translations of `name` and `description`, shown wherever Studio names the plugin to an author
2346
- * whose editor is set to that language. The plain fields stay the plugin's identity: command
2347
- * lines, project files and built games name it by them.
2348
- */
2349
- localized?: PluginManifestLocalized;
2350
- /**
2351
- * Package-relative path to the thumbnail shown beside the plugin's name in
2352
- * the Launcher list. Square, at most 512x512, and one of the extensions in
2353
- * `PLUGIN_ICON_EXTENSIONS`. Plugins without one keep the name monogram.
2354
- */
2355
- icon?: string;
2356
- entries: PluginManifestEntries;
2357
- contributes?: PluginContributes;
2358
- permissions?: PluginInstallPermission[];
2359
- };
2360
- export type NormalizedPluginManifestV2 = PluginManifestV2 & {
2361
- contributes: Required<PluginContributes>;
2362
- permissions: PluginInstallPermission[];
2363
- };
2364
- type PluginInstallSource = {
2365
- kind: "local-directory";
2366
- path: string;
2367
- } | {
2368
- kind: "builtin";
2369
- path: string;
2370
- } | {
2371
- kind: "registry";
2372
- url: string;
2373
- };
2374
- export type PluginInstallRecord = {
2375
- pluginId: string;
2376
- installPath: string;
2377
- enabled: boolean;
2378
- builtIn: boolean;
2379
- manifest: NormalizedPluginManifestV2;
2380
- installSource: PluginInstallSource;
2381
- installedAt: number;
2382
- updatedAt: number;
2383
- grantedManifestVersion?: string | null;
2384
- /**
2385
- * The permission set the user actually approved, recorded so a later version
2386
- * that does not widen it can inherit the grant instead of re-prompting.
2387
- * Absent on records written before this was tracked — the manifest at
2388
- * `grantedManifestVersion` is the fallback source of truth for those.
2389
- */
2390
- grantedPermissions?: PluginInstallPermission[] | null;
2391
- lastError?: string | null;
2392
- };
2393
- type PluginStatus = "enabled" | "disabled" | "needsAuthorization" | "error";
2394
- export type PluginListItem = {
2395
- pluginId: string;
2396
- manifest: NormalizedPluginManifestV2;
2397
- /**
2398
- * `app://` address of the declared icon, absent when the plugin ships none.
2399
- * Resolved here rather than in the renderer because only the main process
2400
- * knows where the package landed on disk.
2401
- */
2402
- iconUrl?: string;
2403
- installPath: string;
2404
- enabled: boolean;
2405
- builtIn: boolean;
2406
- status: PluginStatus;
2407
- installSource: PluginInstallSource;
2408
- installedAt: number;
2409
- updatedAt: number;
2410
- grantedManifestVersion?: string | null;
2411
- lastError?: string | null;
2412
- };
2413
- /** Descriptor handed to the workspace window: plugins with a studio entry. */
2414
- export type WorkspacePluginDescriptor = {
2415
- plugin: PluginIdentity;
2416
- manifest: NormalizedPluginManifestV2;
2417
- entryUrl: string;
2418
- };
2419
- /** Descriptor handed to game execution environments: plugins with a runtime entry. */
2420
- export type RuntimePluginDescriptor = {
2421
- plugin: PluginIdentity;
2422
- manifest: NormalizedPluginManifestV2;
2423
- entryUrl: string;
2424
- /**
2425
- * Published plugin storage, keyed by the namespaces declared in
2426
- * `contributes.runtimeData`. Absent namespaces simply have no entry - a
2427
- * plugin must tolerate missing data (the project may never have written it).
2428
- */
2429
- data?: Record<string, unknown>;
2430
- /**
2431
- * This plugin's own `contributes.buildConfig` values, as the build that produced the pack
2432
- * resolved them, keyed by field key. Only ever the declaring plugin's own values: the pack
2433
- * holds one record per plugin and a descriptor is built from one entry.
2434
- *
2435
- * Absent outside a compiled pack - the editor and Dev Mode hand out descriptors that were never
2436
- * built for a variant, so there is nothing resolved to carry.
2437
- */
2438
- buildConfig?: Record<string, string>;
2439
- };
2440
- type GameMenuLabel = {
2441
- readonly key?: string | null;
2442
- readonly text: string;
2443
- readonly words?: string | null;
2444
- };
2445
- declare const GAME_MENU_DYNAMIC_SOURCES: readonly [
2446
- "textLanguage",
2447
- "voiceLanguage",
2448
- "windowScale"
2449
- ];
2450
- type GameMenuDynamicSource = typeof GAME_MENU_DYNAMIC_SOURCES[number];
2451
- type GameMenuAction =
2452
- /** Open an app surface as a page. */
2453
- {
2454
- readonly type: "openPage";
2455
- readonly surfaceId: string;
2456
- }
2457
- /** Open an app surface as a layer over whatever is on screen. */
2458
- | {
2459
- readonly type: "openLayer";
2460
- readonly surfaceId: string;
2461
- readonly modal?: boolean;
2462
- readonly dismissible?: boolean;
2463
- readonly group?: string | null;
2464
- }
2465
- /** End the playthrough and hand the screen to a page - the same act as the story's quit row. */
2466
- | {
2467
- readonly type: "quitToPage";
2468
- readonly surfaceId: string;
2469
- }
2470
- /**
2471
- * Close the game.
2472
- *
2473
- * The same act as the Quit Application node, which means the same terms: a graceful quit that
2474
- * does NOT run the author's close handler (`On Window Close Requested` answers the window
2475
- * frame's own close, and the node deliberately stands the guard aside so a quit the game asked
2476
- * for is not asked back about). An author who wants their own "really quit?" screen binds a
2477
- * function instead and calls the node at the end of it - which is the same answer this menu
2478
- * gives for every other confirmation.
2479
- */
2480
- | {
2481
- readonly type: "quitApp";
2482
- }
2483
- /** Advance the line on screen. */
2484
- | {
2485
- readonly type: "next";
2486
- }
2487
- /** Auto-forward on/off. Ticked while on. */
2488
- | {
2489
- readonly type: "toggleAutoForward";
2490
- }
2491
- /** Skipping on/off. Ticked while running. */
2492
- | {
2493
- readonly type: "toggleSkipping";
2494
- }
2495
- /** Which lines skipping stops at. The two values are a radio pair, never one switch. */
2496
- | {
2497
- readonly type: "setSkipReadText";
2498
- readonly value: boolean;
2499
- }
2500
- /** Show or hide the dialogue box. Ticked while shown. */
2501
- | {
2502
- readonly type: "toggleDialog";
2503
- }
2504
- /** Step back one line of history. Greyed out when there is none. */
2505
- | {
2506
- readonly type: "historyUndo";
2507
- }
2508
- /** Step forward again after an undo. Greyed out when there is none. */
2509
- | {
2510
- readonly type: "historyRedo";
2511
- }
2512
- /** Full-screen on/off. Ticked while full-screen, including when the OS did it. */
2513
- | {
2514
- readonly type: "toggleFullscreen";
2515
- }
2516
- /**
2517
- * Call a function declared in the global blueprint.
2518
- *
2519
- * `fnRef` is the catalogue's own reference (see `fnCatalog`), and `args` are keyed by the head's
2520
- * parameter pin ids. Everything the named actions above do not cover goes through here -
2521
- * including every confirmation, because what a game asks before losing a playthrough is the
2522
- * author's screen and not a native box in a language the game may not even be running in.
2523
- */
2524
- | {
2525
- readonly type: "fn";
2526
- readonly fnRef: string;
2527
- readonly args?: Readonly<Record<string, unknown>>;
2528
- };
2529
- type GameMenuItemSpec = {
2530
- readonly kind: "separator";
2531
- } | {
2532
- readonly kind: "submenu";
2533
- readonly label: GameMenuLabel;
2534
- readonly items: readonly GameMenuItemSpec[];
2535
- } | {
2536
- readonly kind: "action";
2537
- readonly label: GameMenuLabel;
2538
- readonly action: GameMenuAction;
2539
- } | {
2540
- readonly kind: "dynamic";
2541
- readonly source: GameMenuDynamicSource;
2542
- };
2543
- type GameMenuSpecMenu = {
2544
- readonly label: GameMenuLabel;
2545
- readonly items: readonly GameMenuItemSpec[];
2546
- };
2547
- type GameMenuSpec = {
2548
- readonly menus: readonly GameMenuSpecMenu[];
2549
- };
2550
- /**
2551
- * How much memory a running game costs the machine it runs on, process by process.
2552
- *
2553
- * Read in the main process from Electron's process metrics (`app.getAppMetrics()`), which is the
2554
- * operating system's own account: the same working set a task manager shows. Nothing a page can
2555
- * read gives this. `performance.memory` describes one JavaScript heap, and in Electron it is also
2556
- * rounded to coarse buckets - the two arms of a measured comparison both read exactly 92.9 MB -
2557
- * while the memory a visual novel actually spends is mostly outside any heap:
2558
- * decoded pictures, textures in the GPU process, audio buffers, the main process serving and
2559
- * decrypting assets. The whole game is several processes, and only the process that owns them can
2560
- * see all of them.
2561
- *
2562
- * `process.memoryUsage()` in the main process was the other candidate and answers a narrower
2563
- * question - the main process's own heap and buffers - which is the one process a game's pictures
2564
- * are not in.
2565
- *
2566
- * Comments in English per project convention.
2567
- */
2568
- /** What a process is for, in the words a reader of a memory report needs. */
2569
- type GameProcessKind = "main" | "renderer" | "gpu" | "utility" | "other";
2570
- type GameProcessMemoryEntry = {
2571
- kind: GameProcessKind;
2572
- /**
2573
- * What the platform calls it, when it says: a utility process names its service ("Network
2574
- * Service", "Audio Service", a sidecar's own name). Absent for the rest.
2575
- */
2576
- name?: string;
2577
- /** Whether this is the process the asking page runs in. */
2578
- current: boolean;
2579
- /** Memory currently resident in physical RAM, in bytes. */
2580
- workingSetBytes: number;
2581
- /** The most this process has ever had resident, in bytes. */
2582
- peakWorkingSetBytes: number;
2583
- /** Memory no other process shares, in bytes. Windows reports it; other platforms do not. */
2584
- privateBytes?: number;
2585
- };
2586
- type GameProcessMemoryReading = {
2587
- /**
2588
- * What the processes below add up to.
2589
- *
2590
- * - `game` - every process the game is made of. A packaged game and a preview.
2591
- * - `window` - only the process that draws this window. Dev Mode: the window is Studio's, and
2592
- * the processes around it (Studio's main process, its GPU process, its other windows) are
2593
- * shared with everything else Studio has open, so counting them would report Studio.
2594
- */
2595
- scope: "game" | "window";
2596
- processes: GameProcessMemoryEntry[];
2597
- /** The sum of every process's working set, in bytes. */
2598
- workingSetBytes: number;
2599
- /** The sum of every process's private bytes, or null where the platform does not report them. */
2600
- privateBytes: number | null;
2601
- };
2602
- declare const UI_INPUT_POINTER_GESTURES: readonly [
2603
- "click",
2604
- "doubleClick",
2605
- "rightClick",
2606
- "wheelUp",
2607
- "wheelDown",
2608
- "wheelLeft",
2609
- "wheelRight",
2610
- "longPress",
2611
- "middleClick"
2612
- ];
2613
- type UIInputPointerGesture = (typeof UI_INPUT_POINTER_GESTURES)[number];
2614
- type UIInputBinding = {
2615
- kind: "pointer";
2616
- gesture: UIInputPointerGesture;
2617
- } | {
2618
- kind: "key";
2619
- key: string;
2620
- } | {
2621
- kind: "gamepad";
2622
- button: string;
2623
- };
2624
- type UIInputActionDef = {
2625
- id: string;
2626
- /** Author-facing name. */
2627
- name: string;
2628
- /** The bindings a surface gets unless it overrides them. */
2629
- bindings: UIInputBinding[];
2630
- };
2631
- type UISurfaceActionEnablement = {
2632
- actionId: string;
2633
- /** Whether firing this action stops the input here. Default true. */
2634
- consume?: boolean;
2635
- };
2636
- declare const UI_PAGE_ANIMATION_PRESETS: readonly [
2637
- "none",
2638
- "fade",
2639
- "slide",
2640
- "push",
2641
- "zoom",
2642
- "pop",
2643
- "blur"
2644
- ];
2645
- declare const UI_PAGE_ANIMATION_DIRECTIONS: readonly [
2646
- "auto",
2647
- "left",
2648
- "right",
2649
- "up",
2650
- "down",
2651
- "angle"
2652
- ];
2653
- type UIPageAnimationPreset = (typeof UI_PAGE_ANIMATION_PRESETS)[number];
2654
- type UIPageAnimationDirection = (typeof UI_PAGE_ANIMATION_DIRECTIONS)[number];
2655
- type UIPageAnimationSettings = {
2656
- enter: UIPageAnimationPreset;
2657
- exit: UIPageAnimationPreset;
2658
- enterDirection: UIPageAnimationDirection;
2659
- exitDirection: UIPageAnimationDirection;
2660
- enterAngleDegrees: number;
2661
- exitAngleDegrees: number;
2662
- enterDurationSeconds: number;
2663
- exitDurationSeconds: number;
2664
- /** Held before this host's own enter starts, counted from the moment its subtree begins entering. */
2665
- enterDelaySeconds: number;
2666
- /** Held before this host's own exit starts, counted from the moment its subtree begins exiting. */
2667
- exitDelaySeconds: number;
2668
- /** Added to each successive direct child, so a row of children arrives (and leaves) one after another. */
2669
- childStaggerSeconds: number;
2670
- /** Hold this host's own exit until every child has finished leaving. Chains through nested parents. */
2671
- exitWaitsForChildren: boolean;
2672
- /** Surfaces only: hold the incoming Page until this exit has finished. */
2673
- exitBlocking: boolean;
2674
- };
2675
- type UISurfaceBackgroundFillMode = "cover" | "contain" | "stretch" | "tile";
2676
- type UISurfaceBackgroundImage = {
2677
- /** Image library asset id. Absent background = absent field, so this is never empty. */
2678
- assetId: string;
2679
- fillMode: UISurfaceBackgroundFillMode;
2680
- };
2681
- type UIDocumentVersion = number;
2682
- type UIDocumentId = string;
2683
- type UISurfaceId = string;
2684
- type UIElementId = string;
2685
- type UIDocument = {
2686
- schemaVersion: UIDocumentVersion;
2687
- id: UIDocumentId;
2688
- name: string;
2689
- surfaces: UISurface[];
2690
- components?: UIComponentDefinition[];
2691
- elements: Record<UIElementId, UIElement>;
2692
- /**
2693
- * The shapes this document's widgets describe their data with, keyed by id.
2694
- *
2695
- * No authoring surface manages this table: an author edits the fields on the widget that uses
2696
- * them, and a widget stores only the id. It is a document-level table rather than a copy on each
2697
- * widget so that a shape has one spelling for the whole project - which is what lets one list's
2698
- * content feed another's, and what a Break node's pins are generated from. The rules that keep
2699
- * an implicit library safe to edit are in `struct.ts`.
2700
- */
2701
- structs?: Record<UIStructId, UIStructDef>;
2702
- /**
2703
- * What the gestures of this project mean, keyed by id.
2704
- *
2705
- * The vocabulary half of the input model: "advance", "skip", "open the menu" - named once for
2706
- * the whole project, with the bindings a surface gets unless it says otherwise. A surface stores
2707
- * only the id, in {@link UISurface.actions}, so renaming an action here renames it everywhere
2708
- * and rebinding it rebinds every surface that took the default. The rules are in
2709
- * `inputAction.ts`.
2710
- */
2711
- actions?: Record<string, UIInputActionDef>;
2712
- /**
2713
- * The page the game starts on, by id.
2714
- *
2715
- * Absent means the page carrying `MAIN_APP_SURFACE_ID` - the id a new document gives its first
2716
- * page, and the only entry a document had before this field existed - so a document whose author
2717
- * never moved the entry stays exactly as short as it was. Read only through `resolveEntrySurface`
2718
- * (`entrySurface.ts`), which also says what a pointer that has lost its page falls back to.
2719
- *
2720
- * A pointer rather than an id swap: a page's id is what its blueprints are filed under and what
2721
- * every `Go Page` names, so making another page the entry must not change which id it has.
2722
- *
2723
- * Carries no schema bump, for the reason `actions` and `structs` did not: absence already means a
2724
- * defined answer, so a document written before this field reads correctly without one.
2725
- */
2726
- entrySurfaceId?: UISurfaceId;
2727
- meta?: UIDocumentMeta;
2728
- };
2729
- type UIDocumentMeta = {
2730
- createdAt?: string;
2731
- updatedAt?: string;
2732
- };
2733
- type UIHost = "app" | "player";
2734
- type UISurfaceKind = "appSurface" | "stageSurface";
2735
- type UIStageSlotId = "onStage" | "dialog" | "notification" | "choice" | "nvl";
2736
- type UIStageSurfaceMount = {
2737
- kind: "slot";
2738
- slotId: UIStageSlotId;
2739
- };
2740
- type UIAppSurface = {
2741
- id: UISurfaceId;
2742
- name: string;
2743
- host: "app";
2744
- kind: "appSurface";
2745
- designSize: UISurfaceDesignSize;
2746
- rootElementId: UIElementId;
2747
- settings?: UISurfaceSettings;
2748
- /**
2749
- * Which of the project's actions this surface answers. An id here names an entry of
2750
- * {@link UIDocument.actions}; absent means it answers none of them.
2751
- *
2752
- * Input that lands on this surface's content stops here whether or not an action answered it,
2753
- * unless the action that answered says otherwise. There is no surface-wide setting for that: a
2754
- * surface drawn over another is drawn over it, and an action is the only thing that knows
2755
- * whether what it just did leaves anything for the surface behind to do.
2756
- */
2757
- actions?: UISurfaceActionEnablement[];
2758
- /** The values this page is opened with, in author order. See {@link UIPageParam}. */
2759
- params?: UIPageParam[];
2760
- };
2761
- type UIPageParam = {
2762
- id: string;
2763
- name: string;
2764
- type: UIPageParamType;
2765
- /**
2766
- * For a `list`: the shape each row has, as a struct id (an engine shape such as `nl.confirmButton`,
2767
- * a plugin's, or one of the project's). It types the value on both ends - the inputs that give it
2768
- * and `Get Page Param` - and a list on the page bound to it is checked against it. Absent for
2769
- * every other type, and for a list of rows of no declared shape.
2770
- */
2771
- struct?: string;
2772
- /**
2773
- * What the page reads when whoever opened it gave nothing for this name, in the declared type.
2774
- * Absent reads as the type's empty value (`uiPageParamDefaultValue`).
2775
- */
2776
- defaultValue?: unknown;
2777
- };
2778
- type UIPageParamType = "string" | "text" | "number" | "boolean" | "list" | "json";
2779
- type UIStageSurface = {
2780
- id: UISurfaceId;
2781
- name: string;
2782
- host: "player";
2783
- kind: "stageSurface";
2784
- designSize: UISurfaceDesignSize;
2785
- rootElementId: UIElementId;
2786
- settings?: UISurfaceSettings;
2787
- mount: UIStageSurfaceMount;
2788
- slots?: Record<string, UISlotDefinition>;
2789
- /** @see UIAppSurface.actions */
2790
- actions?: UISurfaceActionEnablement[];
2791
- };
2792
- type UISurface = UIAppSurface | UIStageSurface;
2793
- type UISurfaceDesignSize = {
2794
- width: number;
2795
- height: number;
2796
- };
2797
- type UISurfaceSettings = {
2798
- backgroundColor?: string;
2799
- /**
2800
- * Painted over {@link backgroundColor}, inside the design area only - the letterbox bars around
2801
- * a scaled Surface keep showing the colour, so the two settings are not alternatives.
2802
- */
2803
- backgroundImage?: UISurfaceBackgroundImage;
2804
- pageAnimation?: UIPageAnimationSettings;
2805
- /**
2806
- * What the asset set this Surface's own background names resolves to, per locale. Build-written
2807
- * exactly as {@link UIElement.assetVariants} is, and here rather than on the Surface so the
2808
- * answer sits beside the only asset a Surface names by itself.
2809
- */
2810
- assetVariants?: AssetVariantMap;
2811
- };
2812
- type UISlotDefinition = {
2813
- id: string;
2814
- name: string;
2815
- rootElementId?: UIElementId;
2816
- };
2817
- type UIComponentId = string;
2818
- type UIComponentDefinition = {
2819
- id: UIComponentId;
2820
- name: string;
2821
- rootElementId: UIElementId;
2822
- elements: Record<UIElementId, UIElement>;
2823
- /** Declared in the component's own inspector; each instance supplies its own values. */
2824
- params?: UIComponentParam[];
2825
- previewMeta?: {
2826
- width?: number;
2827
- height?: number;
2828
- };
2829
- createdAt?: string;
2830
- updatedAt?: string;
2831
- };
2832
- type UIComponentParam = {
2833
- /** Stable identity. Blueprints and instance values reference this, so renaming `name` is free. */
2834
- id: string;
2835
- /** What the author called it, shown in the instance's inspector. */
2836
- name: string;
2837
- type: UIComponentParamType;
2838
- /**
2839
- * Used by an instance that has not overridden it, and by the definition's own preview. For a
2840
- * `text` parameter these are words written directly, translated through the definition's own unit
2841
- * (`uiComponentParamUnitId(componentId, paramId)`).
2842
- */
2843
- defaultValue: string;
2844
- };
2845
- type UIComponentParamType = "string" | "text";
2846
- type UIElement = {
2847
- id: UIElementId;
2848
- type: string;
2849
- name?: string;
2850
- parentId: UIElementId | null;
2851
- childrenIds: UIElementId[];
2852
- layout: UILayout;
2853
- style?: UIStyle;
2854
- props?: Record<string, unknown>;
2855
- valueBindings?: Record<string, UIElementValueBinding>;
2856
- /**
2857
- * How this element arrives and leaves - the same record a Surface uses for its page animation.
2858
- * Absent means it does neither. Read it through `elementAnimation.ts` rather than directly.
2859
- *
2860
- * Its own field rather than a `props` key, because it belongs to the placement and not to the
2861
- * widget: every type has one, no renderer reads it, and a linked component instance may set it
2862
- * even though its props come from the definition.
2863
- */
2864
- animation?: UIPageAnimationSettings;
2865
- extra?: Record<string, unknown>;
2866
- /**
2867
- * What each asset set THIS element names resolves to, per locale.
2868
- *
2869
- * **Never authored and never on disk under `editor/`.** It is written while a package is being
2870
- * assembled (`@shared/build/uiAssetSets`) and exists only in the bundle a game runs from, which
2871
- * is why editing a widget cannot produce one and why version control never sees it.
2872
- *
2873
- * It sits on the element rather than in a document-wide table for the reason the story row's
2874
- * copy gives: a table would enumerate every localized asset the project has, which is the one
2875
- * thing a protected pack refuses to disclose. One element discloses the variants of the sets
2876
- * that element itself uses, which is what drawing the widget already discloses.
2877
- *
2878
- * Keyed by the set id the props still name - the prop keeps the set id, so a widget whose map is
2879
- * missing fails loudly at a resolver rather than quietly fetching one language's picture.
2880
- */
2881
- assetVariants?: AssetVariantMap;
2882
- };
2883
- type UIElementValueBindingValueType = "string" | "json" | "float" | "boolean";
2884
- type UIElementValueBinding = {
2885
- kind: "blueprintValue";
2886
- blueprintId: string;
2887
- valueType: UIElementValueBindingValueType;
2888
- }
2889
- /**
2890
- * Read one declared field of the list item this element is being drawn for.
2891
- *
2892
- * The whole reason the struct model exists. Reaching a row's own data used to take a private
2893
- * blueprint per element - Get List Item Props into Get JSON Field with the key typed into a
2894
- * string pin - which is three abstractions deep for "show the title". This is the same read with
2895
- * none of them: the field is picked from a dropdown, and an element outside an item template
2896
- * simply has nothing to bind to.
2897
- *
2898
- * No `valueType` of its own. The field declares one and the bound prop declares one, and the
2899
- * conversion between them belongs to the target, not to the binding - the same reason the
2900
- * slider's binding does not restate that a slider holds a number.
2901
- */
2902
- | {
2903
- kind: "listItemField";
2904
- fieldId: string;
2905
- }
2906
- /**
2907
- * Show the value one placement gives a text parameter of the component this element is part of.
2908
- *
2909
- * The component's counterpart of `listItemField`, and the same kind of read: no graph, nothing
2910
- * kept alive - the value is taken from the placement being drawn (`resolveUIComponentTextParams`),
2911
- * so the editing canvas draws each placement with its own words, and the game translates each
2912
- * through the placement's own unit or reads the key it names. Only a text parameter answers one,
2913
- * and only a widget's words take one; outside a placement (the component's own editor) the
2914
- * element's own words are drawn, as sample text.
2915
- */
2916
- | {
2917
- kind: "componentParam";
2918
- paramId: string;
2919
- }
2920
- /**
2921
- * Show the value the page this element is on was opened with for one of its text parameters.
2922
- *
2923
- * The page's counterpart of `componentParam`, read the same way: no graph - the value is taken from
2924
- * the props the page was opened with (`resolveUIPageTextParams`), words given at the opening shown
2925
- * as given, the declared default translated through the page's own unit. Only a text parameter
2926
- * answers one, only a widget's words take one, and only on a page: a component's insides and a
2927
- * Game UI have no page parameters to show. The editing canvas, where the page has not been opened,
2928
- * draws the default, or the element's own words as sample text while the default is empty.
2929
- */
2930
- | {
2931
- kind: "pageParam";
2932
- paramId: string;
2933
- };
2934
- type UILayout = {
2935
- x: number;
2936
- y: number;
2937
- width: number;
2938
- height: number;
2939
- rotation?: number;
2940
- opacity?: number;
2941
- visible?: boolean;
2942
- /** When true, width and height stay proportional in the inspector and on-canvas resize. */
2943
- lockAspectRatio?: boolean;
2944
- };
2945
- type UIStyle = Record<string, unknown>;
2946
- type GameTestLogLevel = "verbose" | "info" | "success" | "warning" | "error";
2947
- type GameTestExitReason =
2948
- /** Closed from inside the game - the window's own close, or the engine quitting itself. */
2949
- "closed-by-user"
2950
- /** The host asked: an explicit stop, or the run being cancelled. */
2951
- | "stopped-by-host"
2952
- /** Non-zero exit code, a fatal signal, or an uncaught exception in the game's main process. */
2953
- | "crashed"
2954
- /** Never got far enough to run: compile failed, or the runner binary would not spawn. */
2955
- | "failed-to-start";
2956
- type GameTestExit = {
2957
- reason: GameTestExitReason;
2958
- code: number | null;
2959
- signal: string | null;
2960
- };
2961
- type GameTestChoiceOption = {
2962
- index: number;
2963
- text: string;
2964
- /** Offered but not selectable: the option's `disabledWhen` condition holds. */
2965
- disabled: boolean;
2966
- };
2967
- type GameTestEvent = {
2968
- kind: "console";
2969
- level: GameTestLogLevel;
2970
- source: string;
2971
- message: string;
2972
- }
2973
- /**
2974
- * An uncaught error inside the running game - `window.onerror` / `unhandledrejection` in its
2975
- * renderer, `uncaughtException` / `unhandledRejection` in its main process. The game runtime had
2976
- * no such hook before this pipeline; an uncaught renderer exception was reported nowhere.
2977
- */
2978
- | {
2979
- kind: "runtime-error";
2980
- scope: "renderer" | "main";
2981
- message: string;
2982
- stack?: string;
2983
- }
2984
- /** The engine reached an ending (`event:state.end`), forwarded out of the game's renderer. */
2985
- | {
2986
- kind: "game-end";
2987
- }
2988
- /**
2989
- * An `/ending` row ran, naming the ending it declares.
2990
- *
2991
- * Beside `game-end` rather than replacing it: `game-end` also fires for a story that simply ran
2992
- * out of rows, which has no ending to name. A test whose pass condition is "*this* ending" can
2993
- * only be written against this one.
2994
- */
2995
- | {
2996
- kind: "ending";
2997
- endingId: string;
2998
- name: string;
2999
- }
3000
- /**
3001
- * A choice menu is on screen, with every option it evaluated.
3002
- *
3003
- * Fires each time a menu mounts, so a rollback into the same choice reports it again. The list
3004
- * is what `choose` addresses; see {@link GameTestChoiceOption}.
3005
- */
3006
- | {
3007
- kind: "choice";
3008
- options: GameTestChoiceOption[];
3009
- } | {
3010
- kind: "exit";
3011
- exit: GameTestExit;
3012
- };
3013
- type GameTestCommand =
3014
- /**
3015
- * Begin a story at a scene.
3016
- *
3017
- * Necessary because a test session boots to the main app surface - the title screen - and no
3018
- * story runs until something starts one. The caller supplies the target, exactly as the
3019
- * blueprint behind a Start button does.
3020
- */
3021
- {
3022
- kind: "start";
3023
- storyId: string;
3024
- sceneId: string;
3025
- }
3026
- /** One click on the dialogue: finish the line being typed, or move to the next. */
3027
- | {
3028
- kind: "advance";
3029
- }
3030
- /** Pick an option of the choice currently on screen, by the index the `choice` event reported. */
3031
- | {
3032
- kind: "choose";
3033
- index: number;
3034
- };
3035
- type UITemplateSurfacePlacement = {
3036
- kind: UISurfaceKind;
3037
- /** Required for `stageSurface`; the game-UI slot the surface mounts into. */
3038
- slotId?: UIStageSlotId;
3039
- };
3040
- interface AssetTransferManifestEntry {
3041
- /**
3042
- * The asset's id in the source project, which the importing project reuses.
3043
- *
3044
- * Reusing it is what lets the rows that travelled alongside keep pointing at their files
3045
- * without being rewritten, and it makes a second paste of the same clipboard a no-op: the id
3046
- * is already in the library, so there is nothing to import.
3047
- */
3048
- assetId: string;
3049
- /** Author-facing file name, extension included: what the file is called once imported. */
3050
- fileName: string;
3051
- /**
3052
- * The `AssetType` value as a string.
3053
- *
3054
- * Structural for the reason `assetSet.ts` gives for the same field: the enum lives under the
3055
- * renderer and cannot be imported here.
3056
- */
3057
- type: string;
3058
- /** Size in bytes, when the offering side already knew it. */
3059
- size?: number;
3060
- /**
3061
- * Whether the payload is a directory rather than a single file.
3062
- *
3063
- * True for model bundles, the one asset type whose contents are a tree. It decides both halves
3064
- * of the transfer: the grant covering the entry reaches everything below the path, and the
3065
- * importing project copies the tree instead of writing bytes.
3066
- */
3067
- isDirectory?: boolean;
3068
- }
3069
1739
  declare const en: {
3070
1740
  readonly common: {
3071
1741
  readonly appName: "NarraLeaf Studio";
@@ -3152,6 +1822,16 @@ declare const en: {
3152
1822
  readonly other: "{count} info";
3153
1823
  };
3154
1824
  };
1825
+ readonly flowCanvas: {
1826
+ readonly nodeHelp: "Press Enter or Space to select a node. Press Delete to remove it and Escape to cancel.";
1827
+ readonly movableNodeHelp: "Press Enter or Space to select a node, then move it with the arrow keys. Press Delete to remove it and Escape to cancel.";
1828
+ readonly wireHelp: "Press Enter or Space to select a wire. Press Delete to remove it and Escape to cancel.";
1829
+ readonly nodeMoved: "Moved the selected node {direction}. Position x {x}, y {y}";
1830
+ readonly up: "up";
1831
+ readonly down: "down";
1832
+ readonly left: "left";
1833
+ readonly right: "right";
1834
+ };
3155
1835
  };
3156
1836
  readonly menu: {
3157
1837
  readonly app: {
@@ -3995,6 +2675,8 @@ declare const en: {
3995
2675
  readonly characterSpeaker: "Character";
3996
2676
  readonly scenesGroup: "Scenes";
3997
2677
  readonly sceneSpeaker: "Scene";
2678
+ readonly endingsGroup: "Endings";
2679
+ readonly endingSpeaker: "Ending";
3998
2680
  readonly addKey: "Add";
3999
2681
  readonly keyNamePlaceholder: "Key (menu.start\u2026)";
4000
2682
  readonly keySourcePlaceholder: "Source text";
@@ -4037,6 +2719,24 @@ declare const en: {
4037
2719
  readonly statusReviewed: "Reviewed";
4038
2720
  readonly statusStale: "To review";
4039
2721
  readonly findPlaceholder: "Find in translations";
2722
+ readonly orphans: {
2723
+ readonly source: "Orphan translations ({count})";
2724
+ readonly summary: {
2725
+ readonly one: "{count} translation belongs to a line that is not in the game";
2726
+ readonly other: "{count} translations belong to lines that are not in the game";
2727
+ };
2728
+ readonly none: "No orphan translations";
2729
+ readonly disabledLine: "Disabled line";
2730
+ readonly deletedLine: "Deleted line";
2731
+ readonly delete: "Delete";
2732
+ readonly deleteAll: "Delete all";
2733
+ readonly deleteConfirm: {
2734
+ readonly one: "Delete {count} orphan translation?";
2735
+ readonly other: "Delete {count} orphan translations?";
2736
+ };
2737
+ readonly deleteDetail: "This cannot be undone. A line that is enabled or restored later needs translating again.";
2738
+ readonly unknown: "A story cannot be read, so orphan translations are not listed.";
2739
+ };
4040
2740
  };
4041
2741
  readonly live: {
4042
2742
  readonly entryClaimed: "{name} is translating this line";
@@ -4128,6 +2828,21 @@ declare const en: {
4128
2828
  readonly notePlaceholder: "Note\u2026";
4129
2829
  readonly dropHint: "Drop audio to assign";
4130
2830
  readonly findPlaceholder: "Find in lines";
2831
+ readonly orphans: {
2832
+ readonly source: "Orphan voice ({count})";
2833
+ readonly summary: {
2834
+ readonly one: "{count} recording is linked to a line that is not in the game";
2835
+ readonly other: "{count} recordings are linked to lines that are not in the game";
2836
+ };
2837
+ readonly none: "No orphan voice";
2838
+ readonly removeAll: "Remove all";
2839
+ readonly removeConfirm: {
2840
+ readonly one: "Remove {count} orphan recording?";
2841
+ readonly other: "Remove {count} orphan recordings?";
2842
+ };
2843
+ readonly removeDetail: "The audio files stay in the asset library. A line that is enabled or restored later needs its audio assigned again.";
2844
+ readonly unknown: "A story cannot be read, so orphan voice is not listed.";
2845
+ };
4131
2846
  };
4132
2847
  };
4133
2848
  readonly textSourceMigration: {
@@ -6088,6 +4803,9 @@ declare const en: {
6088
4803
  readonly pip: "Picture-in-picture";
6089
4804
  readonly selectRow: "Select a story row to preview its stage state.";
6090
4805
  readonly failed: "Preview failed";
4806
+ readonly settleTimeout: "The stage did not finish setting up in time.";
4807
+ readonly gameUiBlueprintError: "Game UI blueprint error: {message}";
4808
+ readonly previousSessionError: "Previous preview session: {message}";
6091
4809
  readonly playFromHere: "Play from here";
6092
4810
  readonly restart: "Restart";
6093
4811
  readonly stop: "Stop playback";
@@ -6537,6 +5255,7 @@ declare const en: {
6537
5255
  readonly parallel: "Parallel";
6538
5256
  readonly race: "Race, first to finish";
6539
5257
  readonly sequence: "In order";
5258
+ readonly noWait: "Doesn't wait";
6540
5259
  readonly nvl: "NVL";
6541
5260
  readonly menu: "Menu";
6542
5261
  readonly option: "Option";
@@ -6623,6 +5342,7 @@ declare const en: {
6623
5342
  readonly vfx: "{operation} ambience {name}";
6624
5343
  readonly nvl: "NVL block";
6625
5344
  readonly blueprint: "Blueprint";
5345
+ readonly blueprintNamed: "Blueprint {name}";
6626
5346
  readonly pluginAction: "Plugin action";
6627
5347
  readonly cameraOp: {
6628
5348
  readonly transform: "Camera";
@@ -7222,6 +5942,7 @@ declare const en: {
7222
5942
  };
7223
5943
  readonly blueprint: {
7224
5944
  readonly storyActionTitle: "Story Action";
5945
+ readonly name: "Blueprint name";
7225
5946
  };
7226
5947
  readonly audio: {
7227
5948
  readonly track: "Track";
@@ -7327,13 +6048,9 @@ declare const en: {
7327
6048
  readonly parallel: "Parallel";
7328
6049
  readonly race: "Race";
7329
6050
  readonly repeat: "Repeat";
7330
- readonly mode: {
7331
- readonly do: "Do";
7332
- readonly doAsync: "Do async";
7333
- readonly all: "All";
7334
- readonly allAsync: "All async";
7335
- readonly any: "Any";
7336
- };
6051
+ readonly rowsAfter: "Rows after";
6052
+ readonly rowsAfterWait: "Start when the group finishes";
6053
+ readonly rowsAfterStart: "Start with the group";
7337
6054
  readonly times: "Times";
7338
6055
  readonly loopKind: "Loop";
7339
6056
  readonly loopKindTimes: "A number of times";
@@ -8127,9 +6844,9 @@ declare const en: {
8127
6844
  };
8128
6845
  readonly graph: {
8129
6846
  readonly noNodes: "Graph has no nodes yet.";
8130
- readonly pinMultipleInput: "Pin {node}.{port} has multiple input connections; a pin takes one connection.";
8131
- readonly pinMultipleOutput: "Pin {node}.{port} has multiple output connections; a pin takes one connection.";
8132
- readonly entryMissingNode: "Entry points to missing node \"{node}\".";
6847
+ readonly pinMultipleInput: "Pin \"{pin}\" has multiple input connections; a pin takes one connection.";
6848
+ readonly pinMultipleOutput: "Pin \"{pin}\" has multiple output connections; a pin takes one connection.";
6849
+ readonly entryMissingNode: "The entry points to a node that does not exist.";
8133
6850
  readonly entryInvalid: "Function entry not found.";
8134
6851
  };
8135
6852
  readonly event: {
@@ -8142,21 +6859,21 @@ declare const en: {
8142
6859
  };
8143
6860
  readonly edge: {
8144
6861
  readonly selfConnection: "Node \"{node}\" cannot connect to itself.";
8145
- readonly fromUnknown: "Missing source node \"{node}\".";
8146
- readonly toUnknown: "Missing target node \"{node}\".";
6862
+ readonly fromUnknown: "A wire starts at a node that does not exist.";
6863
+ readonly toUnknown: "A wire ends at a node that does not exist.";
8147
6864
  readonly portMismatch: "Pin mismatch on {from} \u2192 {to}.";
8148
6865
  readonly connectionInvalid: "Invalid connection {from} -> {to}.{detail}";
8149
6866
  readonly connectionTypeDetail: " Type mismatch: {from} -> {to}.";
8150
6867
  };
8151
6868
  readonly node: {
8152
- readonly contextInvalid: "Node \"{name}\" is not allowed in this {ownerKind} {graphKind} graph.{hint}";
6869
+ readonly contextInvalid: "Node \"{name}\" is not allowed in {owner}.{hint}";
8153
6870
  readonly contextValueReturnHint: " Return Value only belongs in Blueprint Value graphs.";
8154
6871
  readonly contextListItemHint: " It reads the list row it runs for, and nothing here draws one.";
8155
- readonly noRuntime: "Node \"{node}\": no runtime for type \"{type}\".";
6872
+ readonly noRuntime: "Node \"{node}\" has no runtime.";
8156
6873
  readonly inputMissing: "\"{node}\" has nothing connected to \"{pin}\".";
8157
6874
  readonly stepLimit: "\"{head}\" stopped at \"{node}\" after {steps} steps without a wait.";
8158
6875
  readonly stepLimitGlobal: "\"{head}\" in App logic stopped at \"{node}\" after {steps} steps without a wait.";
8159
- readonly unknownType: "Node \"{node}\": unknown type \"{type}\". Its plugin may be uninstalled or disabled.";
6876
+ readonly unknownType: "Unknown node type \"{type}\". Its plugin may be uninstalled or disabled.";
8160
6877
  readonly variableIdInvalid: "Node \"{node}\": pick a variable.";
8161
6878
  readonly persistentVariableIdInvalid: "Node \"{node}\": pick a persistent variable.";
8162
6879
  readonly savedVariableIdInvalid: "Node \"{node}\": pick a saved variable.";
@@ -8167,11 +6884,11 @@ declare const en: {
8167
6884
  readonly pageParamMissing: "\"{node}\": the page does not declare this param.";
8168
6885
  };
8169
6886
  readonly fn: {
8170
- readonly nameMissing: "Fn \"{node}\": set a function name.";
6887
+ readonly nameMissing: "\"{node}\": set a function name.";
8171
6888
  readonly duplicateName: "Fn name \"{name}\" is used by another function in scope.";
8172
6889
  readonly returnOrphan: "Fn Return must be reachable from a Fn head.";
8173
6890
  readonly returnMultipleHeads: "Fn Return is reachable from multiple Fn heads.";
8174
- readonly returnSignatureConflict: "Fn Return \"{node}\" declares different results than the first Return of this fn.";
6891
+ readonly returnSignatureConflict: "\"{node}\" declares different results than the first Return of this fn.";
8175
6892
  readonly callUnset: "Node \"{node}\": pick a function.";
8176
6893
  readonly callTargetNotFound: "Fn \"{name}\" does not exist in this scope.";
8177
6894
  readonly callSignatureStale: "Function signature changed; re-select \"{name}\" to update its pins.";
@@ -8438,6 +7155,8 @@ declare const en: {
8438
7155
  readonly removeOutput: "Remove output pin";
8439
7156
  readonly jsonFieldName: "JSON object field name";
8440
7157
  readonly readsRow: "Current row";
7158
+ readonly newParamName: "param{n}";
7159
+ readonly newResultName: "result{n}";
8441
7160
  };
8442
7161
  readonly comment: {
8443
7162
  readonly color: {
@@ -9939,6 +8658,9 @@ declare const en: {
9939
8658
  readonly type: "Type";
9940
8659
  readonly typeString: "String";
9941
8660
  readonly typeText: "Text";
8661
+ readonly typeAudioTrack: "Audio track";
8662
+ readonly trackMissing: "Missing track";
8663
+ readonly trackNone: "No track";
9942
8664
  readonly bindLabel: "Parameter";
9943
8665
  readonly bindNone: "No parameter";
9944
8666
  readonly bindMissing: "Missing parameter";
@@ -10046,6 +8768,7 @@ declare const en: {
10046
8768
  readonly unknown: "Usage could not be determined";
10047
8769
  readonly unknownDetail: "{location} could not be read";
10048
8770
  readonly unknownComputed: "{location} picks its asset by a name assembled at run time";
8771
+ readonly defaultFonts: "Default fonts";
10049
8772
  readonly count: {
10050
8773
  readonly one: "{count} reference";
10051
8774
  readonly other: "{count} references";
@@ -10415,6 +9138,7 @@ declare const en: {
10415
9138
  };
10416
9139
  readonly row: {
10417
9140
  readonly nameAria: "Variable name";
9141
+ readonly defaultName: "Variable";
10418
9142
  readonly defaultPlaceholder: "default";
10419
9143
  readonly defaultAria: "Default value";
10420
9144
  readonly delete: "Delete variable";
@@ -10516,6 +9240,8 @@ declare const en: {
10516
9240
  readonly history: {
10517
9241
  readonly moveSurface: "move interface {name}";
10518
9242
  readonly setEntryPage: "set entry page {name}";
9243
+ readonly setRootElement: "set root element {name}";
9244
+ readonly wrapRoot: "wrap {name} in a container";
10519
9245
  readonly createComponent: "create component {name}";
10520
9246
  readonly duplicateComponent: "duplicate component {name}";
10521
9247
  readonly duplicateComponents: "duplicate {count} components";
@@ -10625,6 +9351,9 @@ declare const en: {
10625
9351
  readonly canvas: {
10626
9352
  readonly unknownWidget: "Unknown widget";
10627
9353
  readonly widgetRenderFailed: "Widget failed to draw";
9354
+ readonly componentMissing: "Missing component";
9355
+ readonly componentLoopBlocked: "Component loop blocked";
9356
+ readonly componentRootMissing: "Component root missing";
10628
9357
  readonly wordsFromRowField: "These words come from the list row field \u201C{field}\u201D.";
10629
9358
  readonly wordsFromBlueprintValue: "These words come from the blueprint value \u201C{name}\u201D.";
10630
9359
  readonly wordsFromComponentParam: "These words come from the component parameter \u201C{param}\u201D, set on each placement.";
@@ -10687,6 +9416,14 @@ declare const en: {
10687
9416
  readonly showSelected: "Show selected";
10688
9417
  readonly hideSelected: "Hide selected";
10689
9418
  readonly addToComponentLibrary: "Add to Component Library";
9419
+ readonly setAsRoot: "Set as root element";
9420
+ readonly wrapRootInContainer: "Wrap in container";
9421
+ readonly setAsRootNotContainer: "Available when the root element is a container";
9422
+ readonly setAsRootNotAlone: "Available when this is the only element in the root container";
9423
+ readonly setAsRootNotFree: "Available when the root container's child layout is Free";
9424
+ readonly addedToComponentLibrary: "Added \u201C{name}\u201D to the Component Library.";
9425
+ readonly cannotHoldChildren: "{name} cannot contain child elements";
9426
+ readonly linkedInstanceContents: "The contents of a linked instance are edited in its component";
10690
9427
  readonly group: "Group";
10691
9428
  readonly ungroup: "Ungroup";
10692
9429
  readonly addState: "Add state";
@@ -11804,6 +10541,8 @@ declare const en: {
11804
10541
  readonly ending: {
11805
10542
  readonly title: "Page shown when the story ends";
11806
10543
  readonly none: "Show nothing";
10544
+ readonly unset: "Not chosen";
10545
+ readonly missing: "Deleted page";
11807
10546
  };
11808
10547
  readonly usedBy: {
11809
10548
  readonly one: "Used by {count} reference";
@@ -12007,6 +10746,11 @@ declare const en: {
12007
10746
  readonly one: "{count} track under it moves to {parent}.";
12008
10747
  readonly other: "{count} tracks under it move to {parent}.";
12009
10748
  };
10749
+ readonly builtinName: {
10750
+ readonly bgm: "Music";
10751
+ readonly sound: "SFX";
10752
+ readonly voice: "Voice";
10753
+ };
12010
10754
  readonly slider: {
12011
10755
  readonly bgm: "Music volume";
12012
10756
  readonly sound: "SFX volume";
@@ -13197,7 +11941,7 @@ declare const en: {
13197
11941
  readonly rootName: "Root";
13198
11942
  readonly componentName: "Component";
13199
11943
  readonly pageName: "Page";
13200
- readonly pageCopy: "{name} Copy";
11944
+ readonly nameCopy: "{name} Copy";
13201
11945
  readonly speaker: "Speaker";
13202
11946
  readonly dialog: {
13203
11947
  readonly interactionLayer: "Dialog Interaction Layer";
@@ -13384,6 +12128,7 @@ declare const en: {
13384
12128
  readonly contentLabel: "Variant the content comes from";
13385
12129
  readonly contentHint: "The scenes, variant conditions and art of this variant go into the patch.";
13386
12130
  readonly sameVariant: "The content and the build it updates are the same variant. The patch carries no changes.";
12131
+ readonly sameProject: "The build it updates is made from the current project. The patch carries no changes. For a build already released, choose An existing build folder.";
13387
12132
  readonly outputLabel: "Save as";
13388
12133
  readonly nameLabel: "Name";
13389
12134
  readonly namePlaceholder: "Shown in the game's log";
@@ -14342,6 +13087,7 @@ declare const en: {
14342
13087
  };
14343
13088
  readonly message: {
14344
13089
  readonly ruleFailed: "{rule} could not run";
13090
+ readonly listSeparator: ", ";
14345
13091
  readonly storyLoadFailed: "{story} could not be opened";
14346
13092
  readonly storyTooOld: "{story} is in story format v{version}, and this Studio opens v{minimum} and later";
14347
13093
  readonly storyTooNew: "{story} was written by a newer NarraLeaf Studio (story format v{version}); this Studio reads up to v{supported}";
@@ -15228,13 +13974,14 @@ declare const en: {
15228
13974
  readonly finding: {
15229
13975
  readonly sceneUnreachable: "No path can satisfy the conditions leading to \"{scene}\"";
15230
13976
  readonly optionUnreachable: "No path can satisfy the condition for offering \"{option}\"";
13977
+ readonly conditionUnreachable: "No path can satisfy \"{condition}\"";
15231
13978
  readonly branchUnreachable: "No path can satisfy the condition for taking this branch";
15232
13979
  readonly endingUnreachable: "\"{name}\" is written but no path can satisfy the conditions to reach it";
15233
13980
  readonly endingUnreachableUnnamed: "This ending is written but no path can satisfy the conditions to reach it";
15234
13981
  };
15235
13982
  readonly summary: {
15236
13983
  readonly passed: "Everything the script leads to can be reached";
15237
- readonly failed: "Unreachable: scenes {scenes}, options {options}, endings {endings}";
13984
+ readonly failed: "Unreachable: scenes {scenes}, options {options}, condition branches {conditions}, endings {endings}";
15238
13985
  };
15239
13986
  };
15240
13987
  readonly reachableEndings: {
@@ -15667,6 +14414,19 @@ declare const en: {
15667
14414
  };
15668
14415
  readonly preview: "Preview in";
15669
14416
  readonly excluded: "Not used for {language}";
14417
+ readonly builtin: {
14418
+ readonly group: "Built-in fonts";
14419
+ readonly systemUi: "System UI";
14420
+ readonly sansSerif: "Sans-serif (generic)";
14421
+ readonly serif: "Serif (generic)";
14422
+ readonly monospace: "Monospace (generic)";
14423
+ readonly kind: {
14424
+ readonly system: "System font";
14425
+ readonly sansSerif: "Sans-serif";
14426
+ readonly serif: "Serif";
14427
+ readonly monospace: "Monospace";
14428
+ };
14429
+ };
15670
14430
  };
15671
14431
  readonly panel: {
15672
14432
  readonly add: "Add color";
@@ -15794,6 +14554,10 @@ declare const en: {
15794
14554
  readonly detail: "It is running in another browser tab. Close that tab, then reload this page.";
15795
14555
  readonly reload: "Reload";
15796
14556
  };
14557
+ readonly words: {
14558
+ readonly option: "Option";
14559
+ readonly unknownSpeaker: "Unknown";
14560
+ };
15797
14561
  };
15798
14562
  readonly crash: {
15799
14563
  readonly screen: {
@@ -15836,36 +14600,1366 @@ declare const en: {
15836
14600
  readonly quit: "Quit";
15837
14601
  };
15838
14602
  };
15839
- readonly documents: {
15840
- readonly tooNew: {
15841
- readonly message: "{subject} was written by a newer NarraLeaf Studio ({kind} format v{version}); this build reads up to v{supported}";
15842
- readonly kind: {
15843
- readonly story: "story";
15844
- readonly storyIndex: "story library";
15845
- readonly storyAnimation: "animation";
15846
- readonly uiDocument: "interface";
15847
- readonly uiGraphs: "interface blueprint";
15848
- readonly blueprints: "blueprint";
15849
- readonly variables: "variable";
15850
- readonly saveSchema: "save schema";
15851
- readonly localization: "translation";
15852
- readonly localizationKeys: "translation key";
15853
- readonly voice: "voice";
15854
- readonly brand: "design";
15855
- readonly appTags: "variant";
15856
- readonly dlc: "DLC";
15857
- readonly assetSets: "asset set";
15858
- readonly audioTracks: "audio track";
15859
- readonly characters: "character";
15860
- };
15861
- };
14603
+ readonly documents: {
14604
+ readonly tooNew: {
14605
+ readonly message: "{subject} was written by a newer NarraLeaf Studio ({kind} format v{version}); this build reads up to v{supported}";
14606
+ readonly kind: {
14607
+ readonly story: "story";
14608
+ readonly storyIndex: "story library";
14609
+ readonly storyAnimation: "animation";
14610
+ readonly uiDocument: "interface";
14611
+ readonly uiGraphs: "interface blueprint";
14612
+ readonly blueprints: "blueprint";
14613
+ readonly variables: "variable";
14614
+ readonly saveSchema: "save schema";
14615
+ readonly localization: "translation";
14616
+ readonly localizationKeys: "translation key";
14617
+ readonly voice: "voice";
14618
+ readonly brand: "design";
14619
+ readonly appTags: "variant";
14620
+ readonly dlc: "DLC";
14621
+ readonly assetSets: "asset set";
14622
+ readonly audioTracks: "audio track";
14623
+ readonly characters: "character";
14624
+ };
14625
+ };
14626
+ };
14627
+ };
14628
+ type Messages = typeof en;
14629
+ type Flatten<T, Prefix extends string = ""> = {
14630
+ [K in keyof T & string]: T[K] extends string ? `${Prefix}${K}` : Flatten<T[K], `${Prefix}${K}.`>;
14631
+ }[keyof T & string];
14632
+ type TranslationKey = Flatten<Messages> | (string & {});
14633
+ type AudioClipRegion = {
14634
+ /** Offset from the start of the clip, in milliseconds. Where playback starts. */
14635
+ inMs?: number;
14636
+ /** Offset from the start of the clip, in milliseconds. Where playback stops, or where a loop turns around. */
14637
+ outMs?: number;
14638
+ /**
14639
+ * Offset from the start of the clip, in milliseconds. Where each repeat returns to.
14640
+ *
14641
+ * Absent means "return to {@link inMs}" - a plain loop, and exactly what every record written
14642
+ * before this field existed means. Set past the in point it describes the standard VN
14643
+ * **intro→loop**: `inMs..loopStartMs` plays once, then `loopStartMs..outMs` repeats forever.
14644
+ *
14645
+ * Constrained to `inMs <= loopStartMs < outMs`. A value outside that window is dropped rather
14646
+ * than clamped: clamping would invent a loop point the author never marked, while dropping
14647
+ * degrades to the plain loop the two other markers already describe.
14648
+ */
14649
+ loopStartMs?: number;
14650
+ /**
14651
+ * Playback gain in decibels, never above 0: the engine cannot raise a clip past its own level, so
14652
+ * balancing loudness means bringing the loud clips down. Absent is unity. Folded into a volume
14653
+ * only by {@link clipVolume}.
14654
+ */
14655
+ gainDb?: number;
14656
+ };
14657
+ type StoredAudioClipRegion = Pick<AudioClipRegion, "inMs" | "outMs" | "loopStartMs">;
14658
+ type StoredAudioGain = {
14659
+ db: number;
14660
+ };
14661
+ type BlueprintOpenExternalRequest = {
14662
+ url: string;
14663
+ };
14664
+ type BlueprintOpenExternalOutcome = "opened" | "refused" | "failed";
14665
+ type BlueprintOpenExternalResult = {
14666
+ outcome: BlueprintOpenExternalOutcome;
14667
+ /** Human-readable reason, null when the page was handed over. */
14668
+ error: string | null;
14669
+ };
14670
+ type BlueprintGraphKind = "event" | "function" | "macro";
14671
+ type GameBuildPlatform = "windows" | "macos" | "linux" | "web" | "android" | "ios";
14672
+ declare const UI_STRUCT_FIELD_TYPES: readonly [
14673
+ "string",
14674
+ "number",
14675
+ "boolean",
14676
+ "image",
14677
+ "color",
14678
+ "json"
14679
+ ];
14680
+ type UIStructFieldType = (typeof UI_STRUCT_FIELD_TYPES)[number];
14681
+ type UIStructField = {
14682
+ /**
14683
+ * Stable identity. Value bindings, node params and generated pins all address a field by this,
14684
+ * so renaming `key` costs nothing and reshuffling the list costs nothing.
14685
+ */
14686
+ id: string;
14687
+ /** The property name this field occupies inside an item object. */
14688
+ key: string;
14689
+ /**
14690
+ * What the author called it, shown in the item table header and in every field picker.
14691
+ *
14692
+ * Optional because `key` is usually already the readable name; a label is what lets a field
14693
+ * whose key has to stay ASCII (it travels in JSON to a plugin, a save file, a story variable)
14694
+ * still read as words in the editor.
14695
+ */
14696
+ label?: string;
14697
+ type: UIStructFieldType;
14698
+ };
14699
+ type UIStructId = string;
14700
+ type UIStructDef = {
14701
+ id: UIStructId;
14702
+ fields: UIStructField[];
14703
+ };
14704
+ type PluginPermissionPersistence = "temporary" | "permanent";
14705
+ type PluginFileSystemPermissionMode = "read" | "write" | "readwrite";
14706
+ /**
14707
+ * Install-time permissions split into two families, and the split is the whole
14708
+ * point:
14709
+ *
14710
+ * - **Author-declared** (`filesystem`, `api`) — privileged Studio controls the
14711
+ * plugin asks for explicitly in `permissions[]`.
14712
+ * - **Derived** (`runtime`, `sidecar`, `buildDependency`, `externalLink`,
14713
+ * `network`) — computed from `contributes` by {@link validatePluginManifest}
14714
+ * and *rejected* if written by
14715
+ * hand. A capability is declared in exactly one place, so what the prompt shows
14716
+ * and what the plugin can actually reach cannot drift apart. Adding a
14717
+ * capability widens the permission set, which makes the update re-prompt for
14718
+ * free (see `isPermissionSubset`).
14719
+ */
14720
+ export type PluginInstallPermission = {
14721
+ kind: "filesystem";
14722
+ path: string;
14723
+ mode: PluginFileSystemPermissionMode;
14724
+ recursive: boolean;
14725
+ } | {
14726
+ kind: "api";
14727
+ capability: string;
14728
+ } | {
14729
+ /** Derived from `contributes.runtimeCapabilities`. */
14730
+ kind: "runtime";
14731
+ capability: PluginRuntimeCapability;
14732
+ } | {
14733
+ /** Derived from `contributes.sidecars`: a native child process shipped inside the author's game. */
14734
+ kind: "sidecar";
14735
+ id: string;
14736
+ /**
14737
+ * Which of the two sidecar shapes this is, carried through from the manifest because the
14738
+ * two are different promises: `executable` starts a separate binary, `node` runs the
14739
+ * plugin's own JavaScript under the game's Electron as Node. An author told only "this
14740
+ * ships a helper program" has not been told which one they are approving.
14741
+ *
14742
+ * Named apart from `kind` above on purpose - that one says which permission this is, this
14743
+ * one says what the sidecar runs.
14744
+ */
14745
+ sidecarKind: PluginSidecarKind;
14746
+ /** `<platform>-<arch>` keys the sidecar ships binaries for. */
14747
+ platforms: string[];
14748
+ } | {
14749
+ /** Derived from `contributes.buildDependencies`: binaries downloaded at build time. */
14750
+ kind: "buildDependency";
14751
+ id: string;
14752
+ /** Distinct hostnames the binaries are fetched from. */
14753
+ hosts: string[];
14754
+ } | {
14755
+ /**
14756
+ * Derived from `contributes.externalLinks`: addresses outside the game this plugin may open
14757
+ * in the player's browser or platform handler.
14758
+ *
14759
+ * One permission carrying every pattern rather than one permission each, because what the
14760
+ * author is deciding is a single question - "may this plugin send the player out to these
14761
+ * places" - and a prompt that asked it four times would be four chances to stop reading.
14762
+ * `isPermissionSubset` still compares the patterns one by one, so adding one to a later
14763
+ * version widens the set and re-prompts.
14764
+ *
14765
+ * The patterns are the author's own strings, unrewritten: this list is what the prompt
14766
+ * shows, and normalizing a permission before showing it to the person approving it would
14767
+ * make the prompt and the manifest two slightly different documents.
14768
+ */
14769
+ kind: "externalLink";
14770
+ patterns: string[];
14771
+ } | {
14772
+ /**
14773
+ * Derived from `contributes.network`: hosts this plugin's runtime code requests bytes from.
14774
+ *
14775
+ * A separate question from `externalLink` and never folded into it. Opening a page hands an
14776
+ * address to the browser the player already uses and nothing comes back; this fetches, and
14777
+ * what comes back runs inside the game. An author deciding about one has not decided about
14778
+ * the other.
14779
+ *
14780
+ * The patterns are also what a project's network allowlist shows attributed to this plugin,
14781
+ * which is the point of declaring them: a build narrowed to a list still reaches what the
14782
+ * author approved here, and the list can say so rather than silently making room.
14783
+ */
14784
+ kind: "network";
14785
+ patterns: string[];
14786
+ };
14787
+ type PluginSidecarKind = "executable" | "node";
14788
+ declare const PluginRuntimeCapability: {
14789
+ /** `app.game.store` — plugin-scoped persistent key/value storage. */
14790
+ readonly Store: "store";
14791
+ /** `app.game.events` — game lifecycle and engine event subscription. */
14792
+ readonly Events: "events";
14793
+ /** `app.game.state.get` — read story variables. */
14794
+ readonly StateRead: "state.read";
14795
+ /** `app.game.state.set` — write story variables. */
14796
+ readonly StateWrite: "state.write";
14797
+ /** `app.game.saves` — list and read save metadata. */
14798
+ readonly SavesRead: "saves.read";
14799
+ /**
14800
+ * `app.game.saves.write` / `.load` — overwrite a save slot and load one.
14801
+ *
14802
+ * Separate from (and heavier than) `saves.read`: this can destroy a
14803
+ * playthrough. It exists because quick-save/quick-load is an ordinary thing
14804
+ * for a visual novel to ship — the built-in Quick Save plugin is exactly
14805
+ * this — and a capability model that cannot express a feature the product
14806
+ * already has would just push authors back around it.
14807
+ */
14808
+ readonly SavesWrite: "saves.write";
14809
+ /** `app.game.ui.overlay` — draw on top of the game. */
14810
+ readonly UiOverlay: "ui.overlay";
14811
+ /** `app.game.assets` — resolve packaged asset URLs. */
14812
+ readonly Assets: "assets";
14813
+ /**
14814
+ * `app.game.locale` — read and observe the game language, and read the project's own
14815
+ * localized text through the same table and fallback chain the `Get Text` node uses.
14816
+ */
14817
+ readonly Locale: "locale";
14818
+ /**
14819
+ * `app.game.menu` — own the menu bar above the game.
14820
+ *
14821
+ * Its own capability rather than part of anything else, because what it grants is *the chrome
14822
+ * the player reads the game through*: every row on that bar, and what each one does, is this
14823
+ * plugin's to decide. The rows still act through the game's own vocabulary (see
14824
+ * `@shared/types/gameMenu`) - nothing here reaches past what a blueprint could already do - but
14825
+ * an author approving this is handing over a surface their players will read as the game's own,
14826
+ * which is worth a name at install rather than arriving with something else.
14827
+ *
14828
+ * Absent on shells with no bar to give (the web export, Dev Mode), which is reported the way
14829
+ * every environment difference is: the member is not there.
14830
+ */
14831
+ readonly Menu: "menu";
14832
+ /**
14833
+ * `app.game.story` — register a compile pass that observes each scene and injects engine
14834
+ * actions around its rows.
14835
+ *
14836
+ * The heaviest of the ten to read, and the one whose weight is easiest to miss: nothing here
14837
+ * touches the player's files, and the author still sees only their own document — but a pass
14838
+ * runs over *every* scene of the project, sees who speaks in each, and can put actions around
14839
+ * lines it did not write. That is a plugin editing the story, at the last moment before it
14840
+ * plays, and it is worth a name the author approves at install.
14841
+ *
14842
+ * It is not `state.write` in disguise and does not imply it: a pass builds actions, it does not
14843
+ * run them, and the one piece of runtime state it can touch is a scene-local flag the compiler
14844
+ * creates and owns (`SceneCompileContext.runtimeFlag`), never a story variable the author
14845
+ * declared.
14846
+ */
14847
+ readonly StoryCompile: "story.compile";
14848
+ /**
14849
+ * `app.game.diagnostics` — read what the player's caches are holding.
14850
+ *
14851
+ * The lightest of the list to grant and the easiest to misread as heavier than it is. Nothing
14852
+ * here is authored data: it reports how many images the engine is holding, how many bytes those
14853
+ * cost, and what budgets they are being held against. It reads no story, no save and no
14854
+ * variable, and every member is a read - there is nothing to set.
14855
+ *
14856
+ * It is its own capability rather than free like `data` and `config` because a plugin that
14857
+ * watches memory is a plugin that runs continuously and reports numbers about the player's
14858
+ * machine, and an author should see that named at install. The performance inspector is the
14859
+ * plugin this exists for.
14860
+ *
14861
+ * Absent until a game session is live, which is the same rule every other engine-backed member
14862
+ * follows: a plugin's `setup()` runs during boot, and asking then answers null rather than
14863
+ * throwing.
14864
+ */
14865
+ readonly Diagnostics: "diagnostics";
14866
+ /**
14867
+ * `app.game.process.memory()` — read how much memory the game's processes hold, as the
14868
+ * operating system counts it.
14869
+ *
14870
+ * Its own capability rather than a member of `diagnostics`, because what it reports is not the
14871
+ * game's: `diagnostics` describes the engine's caches, which are the game's own bookkeeping,
14872
+ * while this is the player's machine - how much of its memory every process of the game is
14873
+ * holding, the main process and the GPU process included. An author who approved the first did
14874
+ * not approve the second, and a plugin that already holds `diagnostics` must not gain this by
14875
+ * an update without being asked again - which is what a capability of its own guarantees.
14876
+ *
14877
+ * Read-only, and numbers only: sizes and what each process is for, never a path, an id or a
14878
+ * command line. Nothing is sent anywhere by the host; what a plugin does with a reading is
14879
+ * visible in the code the author installed.
14880
+ *
14881
+ * Absent where there are no processes to count - the web export - and narrowed in Dev Mode to
14882
+ * the window's own renderer, since everything around it is Studio's.
14883
+ */
14884
+ readonly ProcessMemory: "process.memory";
14885
+ };
14886
+ type PluginRuntimeCapability = typeof PluginRuntimeCapability[keyof typeof PluginRuntimeCapability];
14887
+ /**
14888
+ * `app.game.sidecar` has no entry here on purpose: it exists exactly when
14889
+ * `contributes.sidecars` is non-empty. Declaring the sidecar *is* the request.
14890
+ */
14891
+ export interface PluginIdentity {
14892
+ id: string;
14893
+ name?: string;
14894
+ version?: string;
14895
+ publisher?: string;
14896
+ }
14897
+ interface PluginPermissionRequestBase {
14898
+ requestId: string;
14899
+ plugin: PluginIdentity;
14900
+ reason?: string;
14901
+ requestedAt?: number;
14902
+ }
14903
+ export type PluginPermissionRequest = (PluginPermissionRequestBase & {
14904
+ kind: "trust";
14905
+ persistence?: PluginPermissionPersistence;
14906
+ }) | (PluginPermissionRequestBase & {
14907
+ kind: "filesystem";
14908
+ path: string;
14909
+ mode: PluginFileSystemPermissionMode;
14910
+ recursive: boolean;
14911
+ persistence: PluginPermissionPersistence;
14912
+ }) | (PluginPermissionRequestBase & {
14913
+ kind: "install";
14914
+ source: string;
14915
+ permissions?: PluginInstallPermission[];
14916
+ persistence?: PluginPermissionPersistence;
14917
+ }) | (PluginPermissionRequestBase & {
14918
+ kind: "api";
14919
+ capability: string;
14920
+ persistence?: PluginPermissionPersistence;
14921
+ });
14922
+ interface PluginPermissionGrantResult {
14923
+ requestId: string;
14924
+ pluginId: string;
14925
+ kind: PluginPermissionRequest["kind"];
14926
+ approved: boolean;
14927
+ persistence: PluginPermissionPersistence;
14928
+ grantedAt?: number;
14929
+ }
14930
+ export type PluginPermissionPromptResult = PluginPermissionGrantResult | null;
14931
+ declare const PluginManifestVersion = 2;
14932
+ /**
14933
+ * Per-target plugin entry files. Each entry is a prebundled ESM file relative
14934
+ * to the plugin package root. At least one target must be declared.
14935
+ *
14936
+ * - `studio`: loaded in the workspace window only; talks to the
14937
+ * `narraleaf-studio/plugin` host API (editor extensions).
14938
+ * - `runtime`: loaded in every game execution environment (Dev Mode window,
14939
+ * Preview, Production); talks to the `narraleaf-studio/runtime` host API
14940
+ * (game logic such as blueprint node execute bindings).
14941
+ */
14942
+ export type PluginManifestEntries = {
14943
+ studio?: string;
14944
+ runtime?: string;
14945
+ };
14946
+ type PluginLocaleContribution = {
14947
+ code: string;
14948
+ nativeName?: string;
14949
+ englishName?: string;
14950
+ intl?: string;
14951
+ dir?: "ltr" | "rtl";
14952
+ messages: string;
14953
+ };
14954
+ type PluginBinaryPlatformKey = string;
14955
+ type PluginSidecarTargetContribution = {
14956
+ /** Executable (or, for `kind: "node"`, the .js file) to run. Must appear in `include`. */
14957
+ entry: string;
14958
+ /**
14959
+ * Everything shipped for this platform. Entries are package-relative paths,
14960
+ * or `dep:<buildDependencyId>/<path>` to pull an artifact produced by a
14961
+ * declared build dependency (that is how third-party redistributables that
14962
+ * we may not vendor ourselves reach the pack).
14963
+ */
14964
+ include: string[];
14965
+ /**
14966
+ * `sha256` of every package-relative entry in `include`, hex. Verified at
14967
+ * install and again at pack time, so a tampered package fails to install
14968
+ * instead of silently shipping a different binary. `dep:` entries are covered
14969
+ * by the build dependency's own digest instead.
14970
+ */
14971
+ sha256: Record<string, string>;
14972
+ };
14973
+ type PluginSidecarContribution = {
14974
+ /** Prefixed with the plugin id, like every other contributed identifier. */
14975
+ id: string;
14976
+ /**
14977
+ * `executable` spawns the binary directly and talks to it over stdin/stdout; `node` runs the
14978
+ * .js as an Electron utility process and talks to it over `process.parentPort`, which is the
14979
+ * one channel a utility process has - it has no stdin.
14980
+ */
14981
+ kind: PluginSidecarKind;
14982
+ /**
14983
+ * Newline-delimited JSON, which is the framing and not the channel: where the frames travel is
14984
+ * {@link kind}'s business, and only an executable sidecar's travel over stdio. Either way
14985
+ * stderr stays a plain log channel.
14986
+ *
14987
+ * `stdio-jsonl` is the older spelling of the same thing, from when there was only one channel
14988
+ * it could mean. Manifests that say it keep working and always will - a published plugin is a
14989
+ * file somebody already shipped - but a new one should say `jsonl`.
14990
+ */
14991
+ transport: "jsonl" | "stdio-jsonl";
14992
+ /** `onGameStart` spawns with the window; `onRequest` waits for the first call. */
14993
+ autostart: "onGameStart" | "onRequest";
14994
+ /** How long the handshake may take before the sidecar counts as unavailable. */
14995
+ startupTimeoutMs: number;
14996
+ /** Grace period between the shutdown message and SIGTERM. */
14997
+ shutdownTimeoutMs: number;
14998
+ restart: {
14999
+ maxRetries: number;
15000
+ backoffMs: number;
15001
+ };
15002
+ targets: Record<PluginBinaryPlatformKey, PluginSidecarTargetContribution>;
15003
+ };
15004
+ type PluginBuildDependencyTargetContribution = {
15005
+ url: string;
15006
+ /** Mandatory. Doubles as the cache key, so re-pointing the URL at identical bytes never re-downloads. */
15007
+ sha256: string;
15008
+ archive: "zip";
15009
+ /** Archive-internal path -> path inside the produced dependency directory. */
15010
+ files: Record<string, string>;
15011
+ } | {
15012
+ url: string;
15013
+ sha256: string;
15014
+ archive: "none";
15015
+ /** Name the downloaded file takes inside the produced dependency directory. */
15016
+ fileName: string;
15017
+ };
15018
+ type PluginBuildDependencyContribution = {
15019
+ /** Prefixed with the plugin id. Referenced from sidecar `include` as `dep:<id>/<path>`. */
15020
+ id: string;
15021
+ /** Shown to the author at install and in build logs; say what the binaries are. */
15022
+ description?: string;
15023
+ targets: Record<PluginBinaryPlatformKey, PluginBuildDependencyTargetContribution>;
15024
+ };
15025
+ declare const PLUGIN_BUILD_CONFIG_TYPES: readonly [
15026
+ "text",
15027
+ "secret"
15028
+ ];
15029
+ type PluginBuildConfigType = (typeof PLUGIN_BUILD_CONFIG_TYPES)[number];
15030
+ declare const PLUGIN_BUILD_CONFIG_SCOPES: readonly [
15031
+ "global",
15032
+ "variant",
15033
+ "platform",
15034
+ "variant-platform"
15035
+ ];
15036
+ type PluginBuildConfigScope = (typeof PLUGIN_BUILD_CONFIG_SCOPES)[number];
15037
+ type PluginBuildConfigFieldContribution = {
15038
+ /** Unique within the plugin. Keys the stored value; never displayed. */
15039
+ key: string;
15040
+ /** Author-facing name of the field. */
15041
+ label: string;
15042
+ /** One line saying what the value is for. */
15043
+ description?: string;
15044
+ /** `text` is stored in the project; `secret` is not - the project stores a handle. */
15045
+ type: PluginBuildConfigType;
15046
+ /** Which builds share one value. */
15047
+ scope: PluginBuildConfigScope;
15048
+ /** Platforms the field applies to. Absent means every platform. */
15049
+ platforms?: GameBuildPlatform[];
15050
+ /** A build that would ship without a value for this field is refused. */
15051
+ required?: boolean;
15052
+ };
15053
+ export type PluginContributes = {
15054
+ /** Blueprint node types this plugin provides (editor def + runtime execute). */
15055
+ blueprintNodes?: string[];
15056
+ /** Widget element types this plugin provides (editor module + runtime renderer). */
15057
+ widgets?: string[];
15058
+ /** Studio language packs: locales this plugin adds or fills. */
15059
+ locales?: PluginLocaleContribution[];
15060
+ /**
15061
+ * Plugin storage namespaces to publish with the game, readable at runtime
15062
+ * through `app.game.data.readJson(namespace)`.
15063
+ *
15064
+ * Plugin stores live under the project's `editor/` directory, which is never
15065
+ * packaged. A plugin whose runtime needs authored data (catalogs, tables)
15066
+ * must list those namespaces here. The list is an explicit allowlist so
15067
+ * editor-only plugin state cannot leak into a shipped game by accident.
15068
+ */
15069
+ runtimeData?: string[];
15070
+ /**
15071
+ * Test ids this plugin registers with `app.services.tests` (see
15072
+ * `docs/plugin-test-protocol.md`). Declaring them lets the Launcher say what
15073
+ * a plugin checks before any of its code runs, and registering an
15074
+ * undeclared id throws at load.
15075
+ *
15076
+ * Unlike every other code-backed contribution here, this one derives no
15077
+ * install permission — a test only ever executes because the author picked
15078
+ * it out of the Run > Test dialog and pressed Start, so there is no ambient
15079
+ * capability to consent to at install time.
15080
+ */
15081
+ tests?: string[];
15082
+ /**
15083
+ * Save ids the plugin keeps for its own bookkeeping - a quick-save slot, a checkpoint - rather
15084
+ * than slots a player chose.
15085
+ *
15086
+ * The game leaves them out of `List Saves`, and so out of every save screen built on it, the way
15087
+ * it leaves out its own autosave ring. Nothing else changes for them: the plugin still writes,
15088
+ * loads and finds them through `app.game.saves` (whose `listIds` is the raw store listing and
15089
+ * still includes them), and the explicit-id save nodes - `Load Save`, `Delete Save`, the
15090
+ * `Get Save` readers - take them like any other id.
15091
+ *
15092
+ * Prefixed with the plugin id like the type lists above, so a plugin can only reserve ids in its
15093
+ * own namespace and can never hide a slot the author's own `Save Game` wrote. That is also why it
15094
+ * derives no install permission: it reaches nothing the plugin did not already own.
15095
+ */
15096
+ reservedSaveIds?: string[];
15097
+ /**
15098
+ * Capability domains the `runtime` entry may use. Each maps 1:1 onto a
15099
+ * namespace on `app.game`, and an undeclared domain is absent from that
15100
+ * object rather than present-and-throwing — so what the install prompt
15101
+ * listed and what the plugin can reach are the same set by construction.
15102
+ */
15103
+ runtimeCapabilities?: PluginRuntimeCapability[];
15104
+ /** Native child processes shipped inside the author's game. */
15105
+ sidecars?: PluginSidecarContribution[];
15106
+ /** External binaries fetched (and cached) at build time. */
15107
+ buildDependencies?: PluginBuildDependencyContribution[];
15108
+ /**
15109
+ * Values the author supplies before a build can ship. Declaration only - see
15110
+ * {@link PluginBuildConfigFieldContribution} for why there is no registration API and why this
15111
+ * grants the plugin nothing.
15112
+ */
15113
+ buildConfig?: PluginBuildConfigFieldContribution[];
15114
+ /**
15115
+ * Address patterns this plugin may hand to the player's browser or platform handler, through
15116
+ * `app.game.navigation.openExternal`.
15117
+ *
15118
+ * Unlike everything else here, this one is *not* a list of identifiers the plugin owns - it is a
15119
+ * list of places outside the game it may reach, so it derives an install permission the author
15120
+ * approves by name (see `PluginInstallPermission` `kind: "externalLink"`) and adding one to a
15121
+ * later version re-prompts.
15122
+ *
15123
+ * Patterns are matched structurally, never as strings: see `isExternalLinkPatternDeclared` in
15124
+ * `@shared/types/externalLinkPattern` for the exact semantics. Wildcards are allowed
15125
+ * (`https://*.example.com/app/*`) and the scheme is not restricted to `http(s)`, because the
15126
+ * storefronts a plugin integrates with speak their own (`steam://run/480`). A handful of schemes
15127
+ * are refused at validation whatever a manifest says; that list and its reasoning live in the
15128
+ * same module.
15129
+ *
15130
+ * This is separate from, and grants nothing towards, {@link network}: opening a page and
15131
+ * fetching bytes are different acts, and the Open Link node the author writes is a third thing
15132
+ * again, governed only by its scheme.
15133
+ */
15134
+ externalLinks?: string[];
15135
+ /**
15136
+ * Address patterns this plugin's runtime code requests bytes from.
15137
+ *
15138
+ * Like {@link externalLinks} and unlike everything else here, this is a list of places outside
15139
+ * the game rather than identifiers the plugin owns, so it derives an install permission the
15140
+ * author approves by name (`PluginInstallPermission` `kind: "network"`) and adding one to a
15141
+ * later version re-prompts.
15142
+ *
15143
+ * Two rules narrower than the external-link patterns, both for the same reason - what comes
15144
+ * back from a fetch runs inside the game, while an opened page does not:
15145
+ *
15146
+ * - **`http(s)` only.** There is no storefront-scheme case here; a plugin that fetches speaks
15147
+ * HTTP.
15148
+ * - **The path is written out.** `https://api.example.com/*` is a declaration and
15149
+ * `https://api.example.com` is not, because the pattern language reads the second as the
15150
+ * single path `/`. It is refused rather than rewritten: this string is what the install
15151
+ * prompt shows, and a manifest whose text differs from what was approved is the one thing
15152
+ * this whole mechanism exists to prevent.
15153
+ *
15154
+ * What this reaches is *not* widened by the project's own network allowlist and does not widen
15155
+ * it. A build narrowed to a list still reaches these hosts - the author approved them at
15156
+ * install - and the list shows them attributed to this plugin rather than quietly making room.
15157
+ */
15158
+ network?: string[];
15159
+ /**
15160
+ * The props of a contributed widget that hold words a player reads, by widget type.
15161
+ *
15162
+ * Each type named here must also be in {@link widgets}. A declared prop is treated as Studio's own
15163
+ * text and button labels are: the properties panel offers its words written directly or read
15164
+ * from a translation key, the canvas draws a key's words, the words go into the project's
15165
+ * translation table and exports, the project checks report a missing translation or key, and
15166
+ * the glyph check reads them. In the game the widget's renderer is handed the words already in
15167
+ * the player's language, in the prop it declared - it never resolves a key itself.
15168
+ *
15169
+ * Declared in the manifest rather than at registration because both entries need it - the
15170
+ * studio entry's widget is edited by it and the runtime entry's is drawn by it - and a
15171
+ * declaration only one of them made would be the half that drifts.
15172
+ */
15173
+ widgetText?: Record<string, PluginWidgetTextContribution[]>;
15174
+ /**
15175
+ * The shapes of the rows this plugin's nodes hand out, so a list can be given one and a field
15176
+ * read from one by name.
15177
+ *
15178
+ * A node pin carrying them says `struct:<id>` or `array<struct:<id>>` - `Get Gallery` answers
15179
+ * `array<struct:narraleaf.gallery.entry>` - and from then on the plugin's rows are what the
15180
+ * engine's own are: a list picks the shape under Edit content and its fields are the plugin's
15181
+ * and locked, Get Field offers them, and a list handed rows that lack them is reported.
15182
+ *
15183
+ * Declared in the manifest rather than at registration because the editor, the running game and
15184
+ * the command-line checker all need the shape, and only the manifest is read by all three before
15185
+ * any plugin code runs. Ids must start with the plugin id.
15186
+ */
15187
+ structs?: PluginStructContribution[];
15188
+ };
15189
+ type PluginStructContribution = {
15190
+ /** Prefixed with the plugin id: `narraleaf.gallery.entry`. */
15191
+ id: string;
15192
+ /** What the editor calls the shape - "Gallery entry". */
15193
+ name: string;
15194
+ /** `name` per editor locale, matched exactly as `localized` names are. */
15195
+ localized?: Record<string, string>;
15196
+ /** In the order the rows are read: the list's content editor shows them in this order. */
15197
+ fields: {
15198
+ key: string;
15199
+ type: UIStructFieldType;
15200
+ }[];
15201
+ };
15202
+ /**
15203
+ * One prop of a contributed widget whose value is words a player reads.
15204
+ *
15205
+ * `keyProp` is the prop the widget's translation key is kept in when the author reads the words from
15206
+ * one. It defaults to `<prop>LocalizationKey`, and it is the host's to write: the widget never reads it
15207
+ * to draw, because the words in `prop` already come from the key when there is one.
15208
+ */
15209
+ export type PluginWidgetTextContribution = {
15210
+ /** The prop holding the words. */
15211
+ prop: string;
15212
+ /** The prop the translation key is kept in; `<prop>LocalizationKey` when not declared. */
15213
+ keyProp?: string;
15214
+ /** What the properties panel calls the words. The prop name when not declared. */
15215
+ label?: string;
15216
+ /** `label` per editor locale, matched exactly as `localized` names are. */
15217
+ localized?: Record<string, string>;
15218
+ /** Whether the words may run over several lines; one line when not declared. */
15219
+ multiline?: boolean;
15220
+ };
15221
+ /**
15222
+ * The plugin's name and description in one editor language. Either may be left out, and the plain
15223
+ * manifest field is shown in its place.
15224
+ */
15225
+ export type PluginManifestLocalizedText = {
15226
+ name?: string;
15227
+ description?: string;
15228
+ };
15229
+ /**
15230
+ * `name` and `description` per editor locale, keyed by the exact code Studio's language setting
15231
+ * stores (`zh`, `ja`, or a code a language pack adds). There is no region matching: an entry under
15232
+ * `zh-CN` is never shown, because no Studio locale is called that.
15233
+ */
15234
+ export type PluginManifestLocalized = Record<string, PluginManifestLocalizedText>;
15235
+ export type PluginManifestV2 = Omit<PluginIdentity, "id" | "name" | "version"> & Required<Pick<PluginIdentity, "id" | "name" | "version">> & {
15236
+ manifestVersion: typeof PluginManifestVersion;
15237
+ description?: string;
15238
+ /**
15239
+ * Translations of `name` and `description`, shown wherever Studio names the plugin to an author
15240
+ * whose editor is set to that language. The plain fields stay the plugin's identity: command
15241
+ * lines, project files and built games name it by them.
15242
+ */
15243
+ localized?: PluginManifestLocalized;
15244
+ /**
15245
+ * Package-relative path to the thumbnail shown beside the plugin's name in
15246
+ * the Launcher list. Square, at most 512x512, and one of the extensions in
15247
+ * `PLUGIN_ICON_EXTENSIONS`. Plugins without one keep the name monogram.
15248
+ */
15249
+ icon?: string;
15250
+ entries: PluginManifestEntries;
15251
+ contributes?: PluginContributes;
15252
+ permissions?: PluginInstallPermission[];
15253
+ };
15254
+ export type NormalizedPluginManifestV2 = PluginManifestV2 & {
15255
+ contributes: Required<PluginContributes>;
15256
+ permissions: PluginInstallPermission[];
15257
+ };
15258
+ type PluginInstallSource = {
15259
+ kind: "local-directory";
15260
+ path: string;
15261
+ } | {
15262
+ kind: "builtin";
15263
+ path: string;
15264
+ } | {
15265
+ kind: "registry";
15266
+ url: string;
15267
+ };
15268
+ export type PluginInstallRecord = {
15269
+ pluginId: string;
15270
+ installPath: string;
15271
+ enabled: boolean;
15272
+ builtIn: boolean;
15273
+ manifest: NormalizedPluginManifestV2;
15274
+ installSource: PluginInstallSource;
15275
+ installedAt: number;
15276
+ updatedAt: number;
15277
+ grantedManifestVersion?: string | null;
15278
+ /**
15279
+ * The permission set the user actually approved, recorded so a later version
15280
+ * that does not widen it can inherit the grant instead of re-prompting.
15281
+ * Absent on records written before this was tracked — the manifest at
15282
+ * `grantedManifestVersion` is the fallback source of truth for those.
15283
+ */
15284
+ grantedPermissions?: PluginInstallPermission[] | null;
15285
+ lastError?: string | null;
15286
+ };
15287
+ type PluginStatus = "enabled" | "disabled" | "needsAuthorization" | "error";
15288
+ export type PluginListItem = {
15289
+ pluginId: string;
15290
+ manifest: NormalizedPluginManifestV2;
15291
+ /**
15292
+ * `app://` address of the declared icon, absent when the plugin ships none.
15293
+ * Resolved here rather than in the renderer because only the main process
15294
+ * knows where the package landed on disk.
15295
+ */
15296
+ iconUrl?: string;
15297
+ installPath: string;
15298
+ enabled: boolean;
15299
+ builtIn: boolean;
15300
+ status: PluginStatus;
15301
+ installSource: PluginInstallSource;
15302
+ installedAt: number;
15303
+ updatedAt: number;
15304
+ grantedManifestVersion?: string | null;
15305
+ lastError?: string | null;
15306
+ };
15307
+ /** Descriptor handed to the workspace window: plugins with a studio entry. */
15308
+ export type WorkspacePluginDescriptor = {
15309
+ plugin: PluginIdentity;
15310
+ manifest: NormalizedPluginManifestV2;
15311
+ entryUrl: string;
15312
+ };
15313
+ /** Descriptor handed to game execution environments: plugins with a runtime entry. */
15314
+ export type RuntimePluginDescriptor = {
15315
+ plugin: PluginIdentity;
15316
+ manifest: NormalizedPluginManifestV2;
15317
+ entryUrl: string;
15318
+ /**
15319
+ * Published plugin storage, keyed by the namespaces declared in
15320
+ * `contributes.runtimeData`. Absent namespaces simply have no entry - a
15321
+ * plugin must tolerate missing data (the project may never have written it).
15322
+ */
15323
+ data?: Record<string, unknown>;
15324
+ /**
15325
+ * This plugin's own `contributes.buildConfig` values, as the build that produced the pack
15326
+ * resolved them, keyed by field key. Only ever the declaring plugin's own values: the pack
15327
+ * holds one record per plugin and a descriptor is built from one entry.
15328
+ *
15329
+ * Absent outside a compiled pack - the editor and Dev Mode hand out descriptors that were never
15330
+ * built for a variant, so there is nothing resolved to carry.
15331
+ */
15332
+ buildConfig?: Record<string, string>;
15333
+ };
15334
+ type GameMenuLabel = {
15335
+ readonly key?: string | null;
15336
+ readonly text: string;
15337
+ readonly words?: string | null;
15338
+ };
15339
+ declare const GAME_MENU_DYNAMIC_SOURCES: readonly [
15340
+ "textLanguage",
15341
+ "voiceLanguage",
15342
+ "windowScale"
15343
+ ];
15344
+ type GameMenuDynamicSource = typeof GAME_MENU_DYNAMIC_SOURCES[number];
15345
+ type GameMenuAction =
15346
+ /** Open an app surface as a page. */
15347
+ {
15348
+ readonly type: "openPage";
15349
+ readonly surfaceId: string;
15350
+ }
15351
+ /** Open an app surface as a layer over whatever is on screen. */
15352
+ | {
15353
+ readonly type: "openLayer";
15354
+ readonly surfaceId: string;
15355
+ readonly modal?: boolean;
15356
+ readonly dismissible?: boolean;
15357
+ readonly group?: string | null;
15358
+ }
15359
+ /** End the playthrough and hand the screen to a page - the same act as the story's quit row. */
15360
+ | {
15361
+ readonly type: "quitToPage";
15362
+ readonly surfaceId: string;
15363
+ }
15364
+ /**
15365
+ * Close the game.
15366
+ *
15367
+ * The same act as the Quit Application node, which means the same terms: a graceful quit that
15368
+ * does NOT run the author's close handler (`On Window Close Requested` answers the window
15369
+ * frame's own close, and the node deliberately stands the guard aside so a quit the game asked
15370
+ * for is not asked back about). An author who wants their own "really quit?" screen binds a
15371
+ * function instead and calls the node at the end of it - which is the same answer this menu
15372
+ * gives for every other confirmation.
15373
+ */
15374
+ | {
15375
+ readonly type: "quitApp";
15376
+ }
15377
+ /** Advance the line on screen. */
15378
+ | {
15379
+ readonly type: "next";
15380
+ }
15381
+ /** Auto-forward on/off. Ticked while on. */
15382
+ | {
15383
+ readonly type: "toggleAutoForward";
15384
+ }
15385
+ /** Skipping on/off. Ticked while running. */
15386
+ | {
15387
+ readonly type: "toggleSkipping";
15388
+ }
15389
+ /** Which lines skipping stops at. The two values are a radio pair, never one switch. */
15390
+ | {
15391
+ readonly type: "setSkipReadText";
15392
+ readonly value: boolean;
15393
+ }
15394
+ /** Show or hide the dialogue box. Ticked while shown. */
15395
+ | {
15396
+ readonly type: "toggleDialog";
15397
+ }
15398
+ /** Step back one line of history. Greyed out when there is none. */
15399
+ | {
15400
+ readonly type: "historyUndo";
15401
+ }
15402
+ /** Step forward again after an undo. Greyed out when there is none. */
15403
+ | {
15404
+ readonly type: "historyRedo";
15405
+ }
15406
+ /** Full-screen on/off. Ticked while full-screen, including when the OS did it. */
15407
+ | {
15408
+ readonly type: "toggleFullscreen";
15409
+ }
15410
+ /**
15411
+ * Call a function declared in the global blueprint.
15412
+ *
15413
+ * `fnRef` is the catalogue's own reference (see `fnCatalog`), and `args` are keyed by the head's
15414
+ * parameter pin ids. Everything the named actions above do not cover goes through here -
15415
+ * including every confirmation, because what a game asks before losing a playthrough is the
15416
+ * author's screen and not a native box in a language the game may not even be running in.
15417
+ */
15418
+ | {
15419
+ readonly type: "fn";
15420
+ readonly fnRef: string;
15421
+ readonly args?: Readonly<Record<string, unknown>>;
15422
+ };
15423
+ type GameMenuItemSpec = {
15424
+ readonly kind: "separator";
15425
+ } | {
15426
+ readonly kind: "submenu";
15427
+ readonly label: GameMenuLabel;
15428
+ readonly items: readonly GameMenuItemSpec[];
15429
+ } | {
15430
+ readonly kind: "action";
15431
+ readonly label: GameMenuLabel;
15432
+ readonly action: GameMenuAction;
15433
+ } | {
15434
+ readonly kind: "dynamic";
15435
+ readonly source: GameMenuDynamicSource;
15436
+ };
15437
+ type GameMenuSpecMenu = {
15438
+ readonly label: GameMenuLabel;
15439
+ readonly items: readonly GameMenuItemSpec[];
15440
+ };
15441
+ type GameMenuSpec = {
15442
+ readonly menus: readonly GameMenuSpecMenu[];
15443
+ };
15444
+ /**
15445
+ * How much memory a running game costs the machine it runs on, process by process.
15446
+ *
15447
+ * Read in the main process from Electron's process metrics (`app.getAppMetrics()`), which is the
15448
+ * operating system's own account: the same working set a task manager shows. Nothing a page can
15449
+ * read gives this. `performance.memory` describes one JavaScript heap, and in Electron it is also
15450
+ * rounded to coarse buckets - the two arms of a measured comparison both read exactly 92.9 MB -
15451
+ * while the memory a visual novel actually spends is mostly outside any heap:
15452
+ * decoded pictures, textures in the GPU process, audio buffers, the main process serving and
15453
+ * decrypting assets. The whole game is several processes, and only the process that owns them can
15454
+ * see all of them.
15455
+ *
15456
+ * `process.memoryUsage()` in the main process was the other candidate and answers a narrower
15457
+ * question - the main process's own heap and buffers - which is the one process a game's pictures
15458
+ * are not in.
15459
+ *
15460
+ * Comments in English per project convention.
15461
+ */
15462
+ /** What a process is for, in the words a reader of a memory report needs. */
15463
+ type GameProcessKind = "main" | "renderer" | "gpu" | "utility" | "other";
15464
+ type GameProcessMemoryEntry = {
15465
+ kind: GameProcessKind;
15466
+ /**
15467
+ * What the platform calls it, when it says: a utility process names its service ("Network
15468
+ * Service", "Audio Service", a sidecar's own name). Absent for the rest.
15469
+ */
15470
+ name?: string;
15471
+ /** Whether this is the process the asking page runs in. */
15472
+ current: boolean;
15473
+ /** Memory currently resident in physical RAM, in bytes. */
15474
+ workingSetBytes: number;
15475
+ /** The most this process has ever had resident, in bytes. */
15476
+ peakWorkingSetBytes: number;
15477
+ /** Memory no other process shares, in bytes. Windows reports it; other platforms do not. */
15478
+ privateBytes?: number;
15479
+ };
15480
+ type GameProcessMemoryReading = {
15481
+ /**
15482
+ * What the processes below add up to.
15483
+ *
15484
+ * - `game` - every process the game is made of. A packaged game and a preview.
15485
+ * - `window` - only the process that draws this window. Dev Mode: the window is Studio's, and
15486
+ * the processes around it (Studio's main process, its GPU process, its other windows) are
15487
+ * shared with everything else Studio has open, so counting them would report Studio.
15488
+ */
15489
+ scope: "game" | "window";
15490
+ processes: GameProcessMemoryEntry[];
15491
+ /** The sum of every process's working set, in bytes. */
15492
+ workingSetBytes: number;
15493
+ /** The sum of every process's private bytes, or null where the platform does not report them. */
15494
+ privateBytes: number | null;
15495
+ };
15496
+ declare const UI_INPUT_POINTER_GESTURES: readonly [
15497
+ "click",
15498
+ "doubleClick",
15499
+ "rightClick",
15500
+ "wheelUp",
15501
+ "wheelDown",
15502
+ "wheelLeft",
15503
+ "wheelRight",
15504
+ "longPress",
15505
+ "middleClick"
15506
+ ];
15507
+ type UIInputPointerGesture = (typeof UI_INPUT_POINTER_GESTURES)[number];
15508
+ type UIInputBinding = {
15509
+ kind: "pointer";
15510
+ gesture: UIInputPointerGesture;
15511
+ } | {
15512
+ kind: "key";
15513
+ key: string;
15514
+ } | {
15515
+ kind: "gamepad";
15516
+ button: string;
15517
+ };
15518
+ type UIInputActionDef = {
15519
+ id: string;
15520
+ /** Author-facing name. */
15521
+ name: string;
15522
+ /** The bindings a surface gets unless it overrides them. */
15523
+ bindings: UIInputBinding[];
15524
+ };
15525
+ type UISurfaceActionEnablement = {
15526
+ actionId: string;
15527
+ /** Whether firing this action stops the input here. Default true. */
15528
+ consume?: boolean;
15529
+ };
15530
+ declare const UI_PAGE_ANIMATION_PRESETS: readonly [
15531
+ "none",
15532
+ "fade",
15533
+ "slide",
15534
+ "push",
15535
+ "zoom",
15536
+ "pop",
15537
+ "blur"
15538
+ ];
15539
+ declare const UI_PAGE_ANIMATION_DIRECTIONS: readonly [
15540
+ "auto",
15541
+ "left",
15542
+ "right",
15543
+ "up",
15544
+ "down",
15545
+ "angle"
15546
+ ];
15547
+ type UIPageAnimationPreset = (typeof UI_PAGE_ANIMATION_PRESETS)[number];
15548
+ type UIPageAnimationDirection = (typeof UI_PAGE_ANIMATION_DIRECTIONS)[number];
15549
+ type UIPageAnimationSettings = {
15550
+ enter: UIPageAnimationPreset;
15551
+ exit: UIPageAnimationPreset;
15552
+ enterDirection: UIPageAnimationDirection;
15553
+ exitDirection: UIPageAnimationDirection;
15554
+ enterAngleDegrees: number;
15555
+ exitAngleDegrees: number;
15556
+ enterDurationSeconds: number;
15557
+ exitDurationSeconds: number;
15558
+ /** Held before this host's own enter starts, counted from the moment its subtree begins entering. */
15559
+ enterDelaySeconds: number;
15560
+ /** Held before this host's own exit starts, counted from the moment its subtree begins exiting. */
15561
+ exitDelaySeconds: number;
15562
+ /** Added to each successive direct child, so a row of children arrives (and leaves) one after another. */
15563
+ childStaggerSeconds: number;
15564
+ /** Hold this host's own exit until every child has finished leaving. Chains through nested parents. */
15565
+ exitWaitsForChildren: boolean;
15566
+ /** Surfaces only: hold the incoming Page until this exit has finished. */
15567
+ exitBlocking: boolean;
15568
+ };
15569
+ type UISurfaceBackgroundFillMode = "cover" | "contain" | "stretch" | "tile";
15570
+ type UISurfaceBackgroundImage = {
15571
+ /** Image library asset id. Absent background = absent field, so this is never empty. */
15572
+ assetId: string;
15573
+ fillMode: UISurfaceBackgroundFillMode;
15574
+ };
15575
+ type UIDocumentVersion = number;
15576
+ type UIDocumentId = string;
15577
+ type UISurfaceId = string;
15578
+ type UIElementId = string;
15579
+ type UIDocument = {
15580
+ schemaVersion: UIDocumentVersion;
15581
+ id: UIDocumentId;
15582
+ name: string;
15583
+ surfaces: UISurface[];
15584
+ components?: UIComponentDefinition[];
15585
+ elements: Record<UIElementId, UIElement>;
15586
+ /**
15587
+ * The shapes this document's widgets describe their data with, keyed by id.
15588
+ *
15589
+ * No authoring surface manages this table: an author edits the fields on the widget that uses
15590
+ * them, and a widget stores only the id. It is a document-level table rather than a copy on each
15591
+ * widget so that a shape has one spelling for the whole project - which is what lets one list's
15592
+ * content feed another's, and what a Break node's pins are generated from. The rules that keep
15593
+ * an implicit library safe to edit are in `struct.ts`.
15594
+ */
15595
+ structs?: Record<UIStructId, UIStructDef>;
15596
+ /**
15597
+ * What the gestures of this project mean, keyed by id.
15598
+ *
15599
+ * The vocabulary half of the input model: "advance", "skip", "open the menu" - named once for
15600
+ * the whole project, with the bindings a surface gets unless it says otherwise. A surface stores
15601
+ * only the id, in {@link UISurface.actions}, so renaming an action here renames it everywhere
15602
+ * and rebinding it rebinds every surface that took the default. The rules are in
15603
+ * `inputAction.ts`.
15604
+ */
15605
+ actions?: Record<string, UIInputActionDef>;
15606
+ /**
15607
+ * The page the game starts on, by id.
15608
+ *
15609
+ * Absent means the page carrying `MAIN_APP_SURFACE_ID` - the id a new document gives its first
15610
+ * page, and the only entry a document had before this field existed - so a document whose author
15611
+ * never moved the entry stays exactly as short as it was. Read only through `resolveEntrySurface`
15612
+ * (`entrySurface.ts`), which also says what a pointer that has lost its page falls back to.
15613
+ *
15614
+ * A pointer rather than an id swap: a page's id is what its blueprints are filed under and what
15615
+ * every `Go Page` names, so making another page the entry must not change which id it has.
15616
+ *
15617
+ * Carries no schema bump, for the reason `actions` and `structs` did not: absence already means a
15618
+ * defined answer, so a document written before this field reads correctly without one.
15619
+ */
15620
+ entrySurfaceId?: UISurfaceId;
15621
+ meta?: UIDocumentMeta;
15622
+ };
15623
+ type UIDocumentMeta = {
15624
+ createdAt?: string;
15625
+ updatedAt?: string;
15626
+ };
15627
+ type UIHost = "app" | "player";
15628
+ type UISurfaceKind = "appSurface" | "stageSurface";
15629
+ type UIStageSlotId = "onStage" | "dialog" | "notification" | "choice" | "nvl";
15630
+ type UIStageSurfaceMount = {
15631
+ kind: "slot";
15632
+ slotId: UIStageSlotId;
15633
+ };
15634
+ type UIAppSurface = {
15635
+ id: UISurfaceId;
15636
+ name: string;
15637
+ host: "app";
15638
+ kind: "appSurface";
15639
+ designSize: UISurfaceDesignSize;
15640
+ rootElementId: UIElementId;
15641
+ settings?: UISurfaceSettings;
15642
+ /**
15643
+ * Which of the project's actions this surface answers. An id here names an entry of
15644
+ * {@link UIDocument.actions}; absent means it answers none of them.
15645
+ *
15646
+ * Input that lands on this surface's content stops here whether or not an action answered it,
15647
+ * unless the action that answered says otherwise. There is no surface-wide setting for that: a
15648
+ * surface drawn over another is drawn over it, and an action is the only thing that knows
15649
+ * whether what it just did leaves anything for the surface behind to do.
15650
+ */
15651
+ actions?: UISurfaceActionEnablement[];
15652
+ /** The values this page is opened with, in author order. See {@link UIPageParam}. */
15653
+ params?: UIPageParam[];
15654
+ };
15655
+ type UIPageParam = {
15656
+ id: string;
15657
+ name: string;
15658
+ type: UIPageParamType;
15659
+ /**
15660
+ * For a `list`: the shape each row has, as a struct id (an engine shape such as `nl.confirmButton`,
15661
+ * a plugin's, or one of the project's). It types the value on both ends - the inputs that give it
15662
+ * and `Get Page Param` - and a list on the page bound to it is checked against it. Absent for
15663
+ * every other type, and for a list of rows of no declared shape.
15664
+ */
15665
+ struct?: string;
15666
+ /**
15667
+ * What the page reads when whoever opened it gave nothing for this name, in the declared type.
15668
+ * Absent reads as the type's empty value (`uiPageParamDefaultValue`).
15669
+ */
15670
+ defaultValue?: unknown;
15671
+ };
15672
+ type UIPageParamType = "string" | "text" | "number" | "boolean" | "list" | "json";
15673
+ type UIStageSurface = {
15674
+ id: UISurfaceId;
15675
+ name: string;
15676
+ host: "player";
15677
+ kind: "stageSurface";
15678
+ designSize: UISurfaceDesignSize;
15679
+ rootElementId: UIElementId;
15680
+ settings?: UISurfaceSettings;
15681
+ mount: UIStageSurfaceMount;
15682
+ slots?: Record<string, UISlotDefinition>;
15683
+ /** @see UIAppSurface.actions */
15684
+ actions?: UISurfaceActionEnablement[];
15685
+ };
15686
+ type UISurface = UIAppSurface | UIStageSurface;
15687
+ type UISurfaceDesignSize = {
15688
+ width: number;
15689
+ height: number;
15690
+ };
15691
+ type UISurfaceSettings = {
15692
+ backgroundColor?: string;
15693
+ /**
15694
+ * Painted over {@link backgroundColor}, inside the design area only - the letterbox bars around
15695
+ * a scaled Surface keep showing the colour, so the two settings are not alternatives.
15696
+ */
15697
+ backgroundImage?: UISurfaceBackgroundImage;
15698
+ pageAnimation?: UIPageAnimationSettings;
15699
+ /**
15700
+ * What the asset set this Surface's own background names resolves to, per locale. Build-written
15701
+ * exactly as {@link UIElement.assetVariants} is, and here rather than on the Surface so the
15702
+ * answer sits beside the only asset a Surface names by itself.
15703
+ */
15704
+ assetVariants?: AssetVariantMap;
15705
+ };
15706
+ type UISlotDefinition = {
15707
+ id: string;
15708
+ name: string;
15709
+ rootElementId?: UIElementId;
15710
+ };
15711
+ type UIComponentId = string;
15712
+ type UIComponentDefinition = {
15713
+ id: UIComponentId;
15714
+ name: string;
15715
+ rootElementId: UIElementId;
15716
+ elements: Record<UIElementId, UIElement>;
15717
+ /** Declared in the component's own inspector; each instance supplies its own values. */
15718
+ params?: UIComponentParam[];
15719
+ previewMeta?: {
15720
+ width?: number;
15721
+ height?: number;
15862
15722
  };
15723
+ createdAt?: string;
15724
+ updatedAt?: string;
15725
+ };
15726
+ type UIComponentParam = {
15727
+ /** Stable identity. Blueprints and instance values reference this, so renaming `name` is free. */
15728
+ id: string;
15729
+ /** What the author called it, shown in the instance's inspector. */
15730
+ name: string;
15731
+ type: UIComponentParamType;
15732
+ /**
15733
+ * Used by an instance that has not overridden it, and by the definition's own preview. For a
15734
+ * `text` parameter these are words written directly, translated through the definition's own unit
15735
+ * (`uiComponentParamUnitId(componentId, paramId)`).
15736
+ */
15737
+ defaultValue: string;
15738
+ };
15739
+ type UIComponentParamType = "string" | "text" | "audioTrack";
15740
+ type UIElement = {
15741
+ id: UIElementId;
15742
+ type: string;
15743
+ name?: string;
15744
+ parentId: UIElementId | null;
15745
+ childrenIds: UIElementId[];
15746
+ layout: UILayout;
15747
+ style?: UIStyle;
15748
+ props?: Record<string, unknown>;
15749
+ valueBindings?: Record<string, UIElementValueBinding>;
15750
+ /**
15751
+ * How this element arrives and leaves - the same record a Surface uses for its page animation.
15752
+ * Absent means it does neither. Read it through `elementAnimation.ts` rather than directly.
15753
+ *
15754
+ * Its own field rather than a `props` key, because it belongs to the placement and not to the
15755
+ * widget: every type has one, no renderer reads it, and a linked component instance may set it
15756
+ * even though its props come from the definition.
15757
+ */
15758
+ animation?: UIPageAnimationSettings;
15759
+ extra?: Record<string, unknown>;
15760
+ /**
15761
+ * What each asset set THIS element names resolves to, per locale.
15762
+ *
15763
+ * **Never authored and never on disk under `editor/`.** It is written while a package is being
15764
+ * assembled (`@shared/build/uiAssetSets`) and exists only in the bundle a game runs from, which
15765
+ * is why editing a widget cannot produce one and why version control never sees it.
15766
+ *
15767
+ * It sits on the element rather than in a document-wide table for the reason the story row's
15768
+ * copy gives: a table would enumerate every localized asset the project has, which is the one
15769
+ * thing a protected pack refuses to disclose. One element discloses the variants of the sets
15770
+ * that element itself uses, which is what drawing the widget already discloses.
15771
+ *
15772
+ * Keyed by the set id the props still name - the prop keeps the set id, so a widget whose map is
15773
+ * missing fails loudly at a resolver rather than quietly fetching one language's picture.
15774
+ */
15775
+ assetVariants?: AssetVariantMap;
15863
15776
  };
15864
- type Messages = typeof en;
15865
- type Flatten<T, Prefix extends string = ""> = {
15866
- [K in keyof T & string]: T[K] extends string ? `${Prefix}${K}` : Flatten<T[K], `${Prefix}${K}.`>;
15867
- }[keyof T & string];
15868
- type TranslationKey = Flatten<Messages> | (string & {});
15777
+ type UIElementValueBindingValueType = "string" | "json" | "float" | "boolean";
15778
+ type UIElementValueBinding = {
15779
+ kind: "blueprintValue";
15780
+ blueprintId: string;
15781
+ valueType: UIElementValueBindingValueType;
15782
+ }
15783
+ /**
15784
+ * Read one declared field of the list item this element is being drawn for.
15785
+ *
15786
+ * The whole reason the struct model exists. Reaching a row's own data used to take a private
15787
+ * blueprint per element - Get List Item Props into Get JSON Field with the key typed into a
15788
+ * string pin - which is three abstractions deep for "show the title". This is the same read with
15789
+ * none of them: the field is picked from a dropdown, and an element outside an item template
15790
+ * simply has nothing to bind to.
15791
+ *
15792
+ * No `valueType` of its own. The field declares one and the bound prop declares one, and the
15793
+ * conversion between them belongs to the target, not to the binding - the same reason the
15794
+ * slider's binding does not restate that a slider holds a number.
15795
+ */
15796
+ | {
15797
+ kind: "listItemField";
15798
+ fieldId: string;
15799
+ }
15800
+ /**
15801
+ * Show the value one placement gives a text parameter of the component this element is part of.
15802
+ *
15803
+ * The component's counterpart of `listItemField`, and the same kind of read: no graph, nothing
15804
+ * kept alive - the value is taken from the placement being drawn (`resolveUIComponentTextParams`),
15805
+ * so the editing canvas draws each placement with its own words, and the game translates each
15806
+ * through the placement's own unit or reads the key it names. Only a text parameter answers one,
15807
+ * and only a widget's words take one; outside a placement (the component's own editor) the
15808
+ * element's own words are drawn, as sample text.
15809
+ */
15810
+ | {
15811
+ kind: "componentParam";
15812
+ paramId: string;
15813
+ }
15814
+ /**
15815
+ * Show the value the page this element is on was opened with for one of its text parameters.
15816
+ *
15817
+ * The page's counterpart of `componentParam`, read the same way: no graph - the value is taken from
15818
+ * the props the page was opened with (`resolveUIPageTextParams`), words given at the opening shown
15819
+ * as given, the declared default translated through the page's own unit. Only a text parameter
15820
+ * answers one, only a widget's words take one, and only on a page: a component's insides and a
15821
+ * Game UI have no page parameters to show. The editing canvas, where the page has not been opened,
15822
+ * draws the default, or the element's own words as sample text while the default is empty.
15823
+ */
15824
+ | {
15825
+ kind: "pageParam";
15826
+ paramId: string;
15827
+ };
15828
+ type UILayout = {
15829
+ x: number;
15830
+ y: number;
15831
+ width: number;
15832
+ height: number;
15833
+ rotation?: number;
15834
+ opacity?: number;
15835
+ visible?: boolean;
15836
+ /** When true, width and height stay proportional in the inspector and on-canvas resize. */
15837
+ lockAspectRatio?: boolean;
15838
+ };
15839
+ type UIStyle = Record<string, unknown>;
15840
+ type GameTestLogLevel = "verbose" | "info" | "success" | "warning" | "error";
15841
+ type GameTestExitReason =
15842
+ /** Closed from inside the game - the window's own close, or the engine quitting itself. */
15843
+ "closed-by-user"
15844
+ /** The host asked: an explicit stop, or the run being cancelled. */
15845
+ | "stopped-by-host"
15846
+ /** Non-zero exit code, a fatal signal, or an uncaught exception in the game's main process. */
15847
+ | "crashed"
15848
+ /** Never got far enough to run: compile failed, or the runner binary would not spawn. */
15849
+ | "failed-to-start";
15850
+ type GameTestExit = {
15851
+ reason: GameTestExitReason;
15852
+ code: number | null;
15853
+ signal: string | null;
15854
+ };
15855
+ type GameTestChoiceOption = {
15856
+ index: number;
15857
+ text: string;
15858
+ /** Offered but not selectable: the option's `disabledWhen` condition holds. */
15859
+ disabled: boolean;
15860
+ };
15861
+ type GameTestEvent = {
15862
+ kind: "console";
15863
+ level: GameTestLogLevel;
15864
+ source: string;
15865
+ message: string;
15866
+ }
15867
+ /**
15868
+ * An uncaught error inside the running game - `window.onerror` / `unhandledrejection` in its
15869
+ * renderer, `uncaughtException` / `unhandledRejection` in its main process. The game runtime had
15870
+ * no such hook before this pipeline; an uncaught renderer exception was reported nowhere.
15871
+ */
15872
+ | {
15873
+ kind: "runtime-error";
15874
+ scope: "renderer" | "main";
15875
+ message: string;
15876
+ stack?: string;
15877
+ }
15878
+ /** The engine reached an ending (`event:state.end`), forwarded out of the game's renderer. */
15879
+ | {
15880
+ kind: "game-end";
15881
+ }
15882
+ /**
15883
+ * An `/ending` row ran, naming the ending it declares.
15884
+ *
15885
+ * Beside `game-end` rather than replacing it: `game-end` also fires for a story that simply ran
15886
+ * out of rows, which has no ending to name. A test whose pass condition is "*this* ending" can
15887
+ * only be written against this one.
15888
+ */
15889
+ | {
15890
+ kind: "ending";
15891
+ endingId: string;
15892
+ name: string;
15893
+ }
15894
+ /**
15895
+ * A choice menu is on screen, with every option it evaluated.
15896
+ *
15897
+ * Fires each time a menu mounts, so a rollback into the same choice reports it again. The list
15898
+ * is what `choose` addresses; see {@link GameTestChoiceOption}.
15899
+ */
15900
+ | {
15901
+ kind: "choice";
15902
+ options: GameTestChoiceOption[];
15903
+ } | {
15904
+ kind: "exit";
15905
+ exit: GameTestExit;
15906
+ };
15907
+ type GameTestCommand =
15908
+ /**
15909
+ * Begin a story at a scene.
15910
+ *
15911
+ * Necessary because a test session boots to the main app surface - the title screen - and no
15912
+ * story runs until something starts one. The caller supplies the target, exactly as the
15913
+ * blueprint behind a Start button does.
15914
+ */
15915
+ {
15916
+ kind: "start";
15917
+ storyId: string;
15918
+ sceneId: string;
15919
+ }
15920
+ /** One click on the dialogue: finish the line being typed, or move to the next. */
15921
+ | {
15922
+ kind: "advance";
15923
+ }
15924
+ /** Pick an option of the choice currently on screen, by the index the `choice` event reported. */
15925
+ | {
15926
+ kind: "choose";
15927
+ index: number;
15928
+ };
15929
+ type UITemplateSurfacePlacement = {
15930
+ kind: UISurfaceKind;
15931
+ /** Required for `stageSurface`; the game-UI slot the surface mounts into. */
15932
+ slotId?: UIStageSlotId;
15933
+ };
15934
+ interface AssetTransferManifestEntry {
15935
+ /**
15936
+ * The asset's id in the source project, which the importing project reuses.
15937
+ *
15938
+ * Reusing it is what lets the rows that travelled alongside keep pointing at their files
15939
+ * without being rewritten, and it makes a second paste of the same clipboard a no-op: the id
15940
+ * is already in the library, so there is nothing to import.
15941
+ */
15942
+ assetId: string;
15943
+ /** Author-facing file name, extension included: what the file is called once imported. */
15944
+ fileName: string;
15945
+ /**
15946
+ * The `AssetType` value as a string.
15947
+ *
15948
+ * Structural for the reason `assetSet.ts` gives for the same field: the enum lives under the
15949
+ * renderer and cannot be imported here.
15950
+ */
15951
+ type: string;
15952
+ /** Size in bytes, when the offering side already knew it. */
15953
+ size?: number;
15954
+ /**
15955
+ * Whether the payload is a directory rather than a single file.
15956
+ *
15957
+ * True for model bundles, the one asset type whose contents are a tree. It decides both halves
15958
+ * of the transfer: the grant covering the entry reaches everything below the path, and the
15959
+ * importing project copies the tree instead of writing bytes.
15960
+ */
15961
+ isDirectory?: boolean;
15962
+ }
15869
15963
  type PrivilegedWriteBatchEntry = {
15870
15964
  path: string;
15871
15965
  encoding?: FsTextEncoding;
@@ -16160,6 +16254,19 @@ export type SearchJumpTarget = {
16160
16254
  unitId?: string;
16161
16255
  storyId?: string;
16162
16256
  }
16257
+ /**
16258
+ * A translation table showing its orphans: the translations of one language whose line is no
16259
+ * longer in the game (deleted, or disabled), where they are listed and can be deleted.
16260
+ */
16261
+ | {
16262
+ kind: "translationOrphans";
16263
+ locale: string;
16264
+ }
16265
+ /** A voice table showing its orphans: the takes of one voice language whose line is no longer in the game. */
16266
+ | {
16267
+ kind: "voiceOrphans";
16268
+ locale: string;
16269
+ }
16163
16270
  /** A story motion, open in its own editor. */
16164
16271
  | {
16165
16272
  kind: "storyMotion";
@@ -17497,7 +17604,14 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
17497
17604
  updateElementLayouts(layoutPatches: Record<string, Partial<UILayout>>, options?: {
17498
17605
  mergeKey?: string;
17499
17606
  }): void;
17500
- updateElementProps(elementId: string, propsPatch: Record<string, unknown>): void;
17607
+ /**
17608
+ * `skipHistory` is for bookkeeping writes nobody asked for - an inspector filling in the
17609
+ * appearance keys an element predates, the moment it is selected. Recorded, that left an undo
17610
+ * step behind every first selection, so Ctrl+Z after looking around a page did nothing visible.
17611
+ */
17612
+ updateElementProps(elementId: string, propsPatch: Record<string, unknown>, options?: {
17613
+ skipHistory?: boolean;
17614
+ }): void;
17501
17615
  /**
17502
17616
  * Every widget that reads its words from `keyName`, turned into one that holds `words` itself.
17503
17617
  *
@@ -17755,11 +17869,16 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
17755
17869
  * (`resolveWorkspaceUndoScope`). A definition's own stack holds edits *inside* it, and a deleted
17756
17870
  * definition has no tab to press Ctrl+Z in.
17757
17871
  *
17872
+ * `scopeId` names another stack for the one library operation that is made from inside a page's
17873
+ * editor - {@link createComponentFromElements}, from the canvas or the outline. The same rule
17874
+ * puts that step on the page's stack: Ctrl+Z pressed where the gesture was made has to take it
17875
+ * back, and on the project's stack it instead undid whatever the page's last edit had been.
17876
+ *
17758
17877
  * Each step is a command over whole records, not a snapshot of the library: whichever direction
17759
17878
  * runs reads the records as they stand at that moment and writes them back exactly, and nothing
17760
17879
  * else in the library is touched. Tagged, so a live session drops these steps with the interface
17761
17880
  * editors' stacks (`LiveSessionService`): taking back an addition after a session would remove
17762
- * whatever the room built inside it.
17881
+ * whatever the room built inside it. (A page's stack is cleared whole when a session starts.)
17763
17882
  */
17764
17883
  private pushLibraryStep;
17765
17884
  /**
@@ -17992,8 +18111,13 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
17992
18111
  /**
17993
18112
  * A new definition made of copies of elements on a page, with the logic they carry.
17994
18113
  *
17995
- * The page is not changed, so the step that takes this back is the library's - the project's
17996
- * stack, the one {@link createEmptyComponent} uses - rather than the page's.
18114
+ * The page is not changed, but the step that takes this back goes on the page's stack all the
18115
+ * same: it is made from the page's canvas or outline, and that is where Ctrl+Z and the Edit menu
18116
+ * reach (`resolveWorkspaceUndoScope`). On the project's stack - the one {@link createEmptyComponent}
18117
+ * uses, from the rail - Ctrl+Z right after it undid the page's previous edit and left the copy.
18118
+ *
18119
+ * `name` names the definition; without one it takes the element's name, or the catalog's word for
18120
+ * a component when there are several elements or the one has no name.
17997
18121
  */
17998
18122
  createComponentFromElements(surfaceId: string, elementIds: string[], name?: string): UIComponentDefinition | null;
17999
18123
  /** Recorded in the definition's own undo stack, as a page's name is in the page's. */
@@ -18088,7 +18212,10 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
18088
18212
  * element on a page has no parameters to show and is left alone.
18089
18213
  */
18090
18214
  setElementComponentParamBinding(elementId: string, propPath: string, paramId: string | null): void;
18091
- updateComponentElementProps(componentId: string, elementId: string, propsPatch: Record<string, unknown>): void;
18215
+ /** `skipHistory` as {@link updateElementProps} takes it. */
18216
+ updateComponentElementProps(componentId: string, elementId: string, propsPatch: Record<string, unknown>, options?: {
18217
+ skipHistory?: boolean;
18218
+ }): void;
18092
18219
  updateComponentElementAnimation(componentId: string, elementId: string, animation: UIPageAnimationSettings | null, options?: {
18093
18220
  mergeKey?: string;
18094
18221
  }): void;
@@ -18107,6 +18234,23 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
18107
18234
  * document made of the component alone, as `moveComponentElements` does.
18108
18235
  */
18109
18236
  groupComponentElements(componentId: string, elementIds: readonly string[]): string | null;
18237
+ /**
18238
+ * Make an element its definition's root, taking off the container it sat alone in.
18239
+ *
18240
+ * One step in the definition's history, and the one step there that reaches the pages: every
18241
+ * placement is given the box the element took up inside it (`promoteElementToComponentRoot`), and
18242
+ * taking the step back gives the boxes back with the definition.
18243
+ */
18244
+ promoteComponentElementToRoot(componentId: string, elementId: string): boolean;
18245
+ /**
18246
+ * Put an empty container around a definition's root, as its new root. Returns the container's id.
18247
+ *
18248
+ * The container takes the root's size and draws nothing of its own, so the definition and every
18249
+ * placement look as they did, and the root can now be given siblings.
18250
+ */
18251
+ wrapComponentRoot(componentId: string): string | null;
18252
+ /** An element as an Edit menu step names it: its own name, or its widget's. */
18253
+ private describeElementForHistory;
18110
18254
  createComponentElement(componentId: string, parentId: string, type: string, layoutPatch?: Partial<UILayout>): UIElement | null;
18111
18255
  pasteComponentClipboardPayload(componentId: string, targetParentId: string, beforeChildId: string | null, incoming: UIEditorClipboardPayload): {
18112
18256
  ok: true;
@@ -18145,6 +18289,15 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
18145
18289
  private createNotificationStageTemplate;
18146
18290
  private createChoiceStageTemplate;
18147
18291
  private createNvlStageTemplate;
18292
+ /**
18293
+ * The actions the project's dialogue box reads on with, as far as a new NVL page can follow them.
18294
+ *
18295
+ * Read from the box's graphs the way the engine's own NVL page reads them
18296
+ * (`resolveDialogueAdvanceActionIds`), and kept to actions the project still defines - an
18297
+ * enablement naming a missing one does nothing, and copying it onto a new surface would only
18298
+ * carry the dead entry further.
18299
+ */
18300
+ private resolveDialogueAdvanceActionsForStarter;
18148
18301
  /** Value graph: `Init -> Return Value` fed by `Get List Item Props -> Get JSON Field(propsPath)`. */
18149
18302
  private createListItemPropsValueGraph;
18150
18303
  /**
@@ -18159,8 +18312,24 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
18159
18312
  * Panel (`nl.container`) because collection widgets like `nl.nvl.list` do not expose a
18160
18313
  * `Mouse Click` head; the panel's own Mouse Click plus Element Click on the interaction layer
18161
18314
  * and the list cover every click region.
18315
+ *
18316
+ * The page's wiring when the project has no action its dialogue box reads on with; see
18317
+ * {@link createNvlActionNextGraph} for the one it gets when it has.
18162
18318
  */
18163
18319
  private createNvlNextGraph;
18320
+ /**
18321
+ * Event graph for an NVL page in a project whose dialogue box reads on with actions: each of
18322
+ * those actions runs `Next`, and so does a click on the NVL list.
18323
+ *
18324
+ * The actions bring the keys, the gamepad and a click anywhere on the page that is not on a
18325
+ * control - the same presses that read the box on. The list is the exception: it is a control,
18326
+ * and an action stands down over a control (`pointerInputClaimedByControl`), so a click on the
18327
+ * lines would otherwise do nothing. `Element Click` on the list hears exactly that click and no
18328
+ * other, so no press reads on twice and no widget answers a gesture the page answers as well.
18329
+ *
18330
+ * On the surface's blueprint, because only a surface answers an action.
18331
+ */
18332
+ private createNvlActionNextGraph;
18164
18333
  private configureDefaultNotificationBlueprints;
18165
18334
  private configureDefaultChoiceBlueprints;
18166
18335
  private configureDefaultNvlBlueprints;
@@ -19465,7 +19634,9 @@ interface IUIDocumentService extends IService {
19465
19634
  updateElementLayouts(layoutPatches: Record<string, Partial<UILayout>>, options?: {
19466
19635
  mergeKey?: string;
19467
19636
  }): void;
19468
- updateElementProps(elementId: string, propsPatch: Record<string, unknown>): void;
19637
+ updateElementProps(elementId: string, propsPatch: Record<string, unknown>, options?: {
19638
+ skipHistory?: boolean;
19639
+ }): void;
19469
19640
  ensureElementBlueprintValueBinding(elementId: string, propPath: string, input: {
19470
19641
  valueType: UIElementValueBindingValueType;
19471
19642
  displayName?: string;
@@ -19505,7 +19676,9 @@ interface IUIDocumentService extends IService {
19505
19676
  updateComponentElementLayouts(componentId: string, layoutPatches: Record<string, Partial<UILayout>>, options?: {
19506
19677
  mergeKey?: string;
19507
19678
  }): void;
19508
- updateComponentElementProps(componentId: string, elementId: string, propsPatch: Record<string, unknown>): void;
19679
+ updateComponentElementProps(componentId: string, elementId: string, propsPatch: Record<string, unknown>, options?: {
19680
+ skipHistory?: boolean;
19681
+ }): void;
19509
19682
  updateComponentElementExtra(componentId: string, elementId: string, extraPatch: Record<string, unknown>): void;
19510
19683
  renameComponentElement(componentId: string, elementId: string, name: string): void;
19511
19684
  reorderComponentChildren(componentId: string, parentId: string, orderedChildIds: string[]): void;