release: v1.0.1 - #204
Merged
Merged
release: v1.0.1#204
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DISCLOSURE: LLM-GENERATED TEXT
Release PR for
v1.0.1, prepared by the release-prepare workflow.Checklist
patch; explicit:none)release: v1.0.1; GitHub appends(#N), which auto-tagging acceptsChangelog excerpt
[1.0.1] - 2026-10-02
Added
nothing the console showed could be taken back: a UI Toolkit text field has no history of its
own, and the package supplied none, so a typo in a long command was corrected one Backspace at
a time - the worst case for the longest commands - on every supported editor. Undo is one state
per change rather than one per run of typing, and a state is any change to the field, including
the ones the terminal made: a Tab completion, a line recalled from history, a paste, and the
clear a command run performs. So a completion you did not want is one Ctrl+Z away, and one more
after a run brings the executed command back onto the line without running it again. The key is
consumed only when there is a state to move to, so one with nothing left to undo does nothing
rather than clearing the line. The history is per surface and holds 64 states, and closing the
console clears the command line's, because the field it described is gone. See
Undo.
find <text>searches the log view: it keeps only the lines holding the text, jumps to the first match, and reports how many lines matched out of how many the search ranged over.findwith no argument steps to the next match and wraps at the end, F3 and Shift+F3 do the same from the keyboard, andclear-filtershows every line again. Until now a log too long to read had no way in but dragging a 10px scrollbar: with the default 256-entry buffer, finding one error meant paging through every line that was not it, and the log view is the only place a message ever appears. The count is the answer to "did my search hit", which a view showing some lines cannot give. A search that matched nothing reports that and stays set, so a typo does not silently put the whole log back on screen, and the line the search writes to say so is never one of its own results - a query that happened to be a word in that line (search,log,clear-filter) otherwise matched it, and the count grew with every repeat. A query is the arguments joined back into one string, sofind "two words"searches for two words, and an empty or whitespace-only query is refused so it cannot drop the search already in place. The search runs over the text as the log shows it, so what is on screen is what can be found, and it ignores case. It does not find commands you ran: both console surfaces echo the typed line into the log as anInputentry before the handler runs, and a search that matched its own echo reported a hit for every query, including one that appears nowhere.clear-consoledoes not drop the search - the view goes empty until the nextclear-filter- and the query is recorded only as thefindline in the log, so it is not recoverable once that line rotates out of the buffer.copy-lastandcopy-log [n]put the console log on the system clipboard: the newest line, or the last N, joined with newlines. Until now nothing could be read out of the console at all - the log view is the only place a message, a command echo, or a failing value ever appears, and none of it could be selected, so a developer who sawCommand 'give' threw ArgumentExceptionin a device build had to retype it or screenshot the game window. Both surfaces echo the line you typed into the log before the command runs, so a copy skips the echoes at the newest end:copy-lastreturns the line you were reading, not the wordcopy-last. An echo further back stays, because a transcript of a session wants the commands in it as much as the output. A copied line is the line the log shows, so the stack trace is not included (traceis how you read one) and copying 256 lines does not carry 256 traces. A clipboard write is a request a platform can decline - tvOS has none, and a browser may refuse without a user gesture - so the write is read back and compared, and a copy that did not happen says so instead of silently doing nothing. An empty log, a log holding only commands, a count that is not a number, and a count below one are each reported rather than ignored.CommandLog.CopyTo(LogItem[] destination)copies the log's visible window, oldest first, into a caller-owned array and returns how many entries it wrote. ReadingCommandLog.Logscounts and then indexes as two separate reads, so a log written from another thread between them can be missed or, if the buffer was cleared or shrunk, throw. OneCopyTocall is one consistent view. A destination shorter than the window truncates to its oldest entries, so size it for the largest buffer the session configures.set name "two words"pastes as one argument, but the paste normalizes before the tokenizer sees the text, so quoted whitespace survives typing and not pasting. The clipboard is unavailable on tvOS, and a platform that will not answer without a user gesture may read empty; neither is an error, the key is left alone, and anything the platform does with it still happens. One limit: a control character that is not whitespace stays in the field, where a single line cannot show it, and reaches the argument as the character that was copied.TerminalUI.SetCursorBlinkPaused(bool)freezes the caret blink schedule with the caret pinned visible (or restores normal blinking), so screenshots, video capture, and tests produce deterministic caret pixels regardless of when the frame lands.TerminalStackTraceModesetting onTerminalSettings(and the matchingTerminalUIfield) chooses which log entries record a caller stack trace —All(default; unchanged behavior),ErrorsAndWarnings(error, assert, exception, and warning entries only), orDisabled(never). Trace extraction dominates the cost of a logged line, so games that log frequently during gameplay can skip it for routine messages; skipped entries store no trace text and their writes become allocation-free. Entries forwarded from Unity's own log callback follow the same rule.TerminalThemeAsset(menuAssets > Create > Wallstop Studios > DxCommandTerminal > Theme Asset) exposes color pickers for the 19 custom properties every terminal theme sheet defines, and the Editor automatically writes (and keeps updating) a sibling.ussnamed after the asset — rename or recolor the asset and the sheet follows. The generated sheet drops into aTerminalThemePack's Themes list like any hand-written sheet, soset-theme, the shipped packs, and runtime behavior are unchanged.TerminalSettingsasset (menuAssets > Create > Wallstop Studios > DxCommandTerminal > Terminal Settings): assign one onTerminalUIorCommandPaletteUIand its values win over the component's serialized values when the component wakes — buffer sizes, input caret, caret blink rate, on-screen button text, hint mode, history dedup, built-in command toggles, ignored log types, disabled commands, Unity log forwarding, and the palette hotkey. An empty slot keeps existing scenes behaving exactly as before; window geometry, animation curves, ids, and persisted theme/font preferences stay per-component.CommandShell.IncludeDiscoveryAssembly(Assembly)scans an assembly the default discovery filter would skip (a runtime-emitted assembly, or a precompiled DLL without a package reference) through the normal catalog, provider, and reflection stages. The returned handle removes the assembly again; registering before the first command request applies immediately, later registrations apply on the next registration cycle. The readiness log counts explicitly included assemblies.CommandArg.TryGetRawand builder.RawParser(...)pass uncleaned token contents to explicit parsers. Scene-object samples use this path so CR/LF names do not resolve to a different object; existing parsers keep their cleanup behavior.SceneObjectArgumentAdapter<T>resolves GameObject and Component arguments by name, with fresh completion choices and configurable duplicate-name handling. New sample commands show object lookup and component access.Samples~/TerminalCommandsships runnable typed-builder examples (importable through Package Manager) covering required/optional arguments with range and choice validation, bool/enum arguments, nested subcommands with a bare-invocation fallback, live dynamic completion against game state, the unbounded trailing argument, execution contexts with Edit Mode opt-in, enable/disable registration lifetime, object lookup with component access, component listing plus a scene-wide missing-script scan, and layer/tag filtering whose choices complete the project's defined layers and the tags in use in the live scene.CommandPaletteUIcomponent opens a compact command search bar on a configurable hotkey (default Ctrl+Space, legacy and new Input System). A blank query shows only the bar; results appear as you type, ranked exact-first, then prefix, then fuzzy subsequence (ordinal ties). Up/Down selects a result and auto-loads its name into the input without re-filtering the list, Tab commits the selected name, Enter executes (closing the palette; failures keep it open with visible feedback), Escape closes and restores the previous focus. Once the input names a command and reaches an argument, the bar lists that command's completion candidates (static choices and dynamic providers, staged across arguments): Tab applies the selected candidate to the active token, quoting values that contain whitespace when the token is not already quoted, and clicking a row applies it without running. Opening one command surface closes the other, andOpened/Closedevents let gameplay code pause its input maps. Styling is theme-token driven through the shared stylesheet chain (BaseStyles.uss, as the terminal uses): panel, input, rows, hover/selected states, feedback, and a slim rounded scrollbar follow every shippedTerminalThemePacktheme automatically, andvar()fallbacks keep the bar readable when no theme is assigned.CommandBuilder.Create(name).Help(...).Arg<int>(...)...registers commands intoCommandShell.AddCommandwith typed required/optional arguments, defaults, per-argument validation (numericRange, customValidate), and static/dynamic/bool/enum choices. Argument bounds, the usage hint (heal <amount:int> [target:string]), and staged completion derive from the same definition. Parsing reuses the existingCommandArgparsers (no new runtime reflection); user-input mistakes surface as controlled shell errors and never run the handler.CommandBuilder.Subcommand(name, configure)routes one registered command through its first argument, each subcommand with its own typed arguments, validation, and completion (inventory add pickaxe 3,inventory remove pickaxeunder oneinventorycommand). Tab completion offers subcommand names first and the routed subcommand's choices beyond it; a bare invocation runs the parent handler when set, otherwise an error lists the available subcommands. Subcommands nest to any depth, and errors name the full routed path ('inventory add': ...).CommandBuilder.Remaining<T>(name, configure)collects every trailing token into one typed array (arguments.Get<T[]>(name)), parsing and validating each element with the argument's own rules. The usage hint marks the variadic argument (say <message:string...>), its choices complete every trailing stage, and.Required()on it demands at least one token.CommandConfigurationException, thrown for command-authoring mistakes at definition time (missing handlers, duplicate names, required-after-optional ordering, unparseable argument types, defaults failing their own validation, subcommand conflicts). It derives fromInvalidOperationException, so existing handlers keep working, and exposes the offendingCommandNamefor programmatic handling.CommandConfigurationExceptionnow also carries aFailureclassification (CommandConfigurationFailure), so callers branch on the failure kind instead of parsing messages, plus theArgumentName,SubcommandName, andArgumentTypethe failure concerns. All builder and argument-spec misconfigurations report the same structured identity, including spec-level errors made inside an argument's configure callback or a subcommand's configure callback (the routed path is composed onto the command name, matching build-time diagnostics); idiomatic null-argument contracts keep the standardArgumentNullException.CommandArgumentTypeMismatchException, thrown byCommandArguments.Get<T>when the stored parsed value is not readable as the requested type. It derives fromInvalidOperationException, so existing catchers keep working, and exposesArgumentName,StoredType, andRequestedTypefor programmatic handling; remaining arguments store an array of their element type, so reading the element type itself reportsStoredTypeas that array type.CommandShell.AddCommand(CommandBuilder, out CommandRegistrationHandle)returns a handle that removes exactly its own registration onDispose— never a later replacement with the same name — for instance-owned commands that register on enable and dispose on disable.[UnityEngine.Scripting.Preserve]on every generated command catalog - on the catalog type and itsCollectentry method - so the linker keeps the catalog and everything reachable from it, and commands registered with[RegisterCommand]stay available through managed code stripping (IL2CPP/WebGL) at every Managed Stripping Level. Private handlers in partial types are bound through an emitted partial companion of the declaring type instead of reflection, so they are inside that reachable graph as well. Commands bound only through reflection — handlers in private, non-partial types and precompiled DLLs without a catalog — needed[Preserve], alink.xml, or manual registration atMediumor higher; the player compatibility bake (under Fixed) now covers them automatically.CommandShell.InitializeAutoRegisteredCommandsaccepts adeferRegistrationflag. The terminal applies its ignored/default command configuration when enabled and defers the discovery scan and delegate materialization to the first command request, the first read ofCommandShell.Commands, or an explicitCommandShell.EnsureAutoCommandsRegisteredcall.CommandShell.AutoCommandsRegisteredreports whether registration has been applied. Clearing auto commands cancels a pending registration. Note thatCommandShell.AutoRegisteredCommandsreflects only applied registration, so it stays empty until first use for deferred shells.CommandCataloginto every assembly declaring[RegisterCommand]methods.CommandShellbinds those catalogs without walking assembly types; assemblies without a catalog (precompiled DLLs, analyzers unavailable) fall back to the previous reflection discovery with identical results. The publicCommandShell.RegisteredCommandssurface is unchanged.CommandExecutionContexts,CommandExecutionContext,CommandDefinition,CommandHandler,BorrowedCommandArgumentstypes, registered throughCommandShell.AddCommand(CommandDefinition). Definitions default toCommandExecutionContextSets.Gameplay; Edit Mode execution is opt-in.[RegisterCommand]gains aContextsproperty (defaultCommandExecutionContextSets.All, so existing attributed commands keep their availability).CommandDefinition.MaxArgCountisint?(null= unbounded).CommandShell.TryCompletebuilds aCommandCompletionContextfor the command under the caret and fills a caller-owned buffer. Custom replacement ranges use the validatedCommandCompletionReplacementstruct; provider exceptions are contained; results dedupe ordinally. Commands without a provider keep the previous history-based completion.TerminalUITab now cycles provider token completions: only the active token is replaced, trailing text is preserved, space- or quote-containing insertions are quoted, and the caret lands after the insertion.CommandTokenizerfor execution and completion, parity-pinned againstTryEatArgumentby a data-driven corpus.CommandDefinitioncommands withAddToHistory = falsedispatch without rebuilding the history line.CommandArgParsers, a public static class exposing the culture-invariant parsers behindCommandArg.TryGet, one method per built-in type (CommandArgParsers.Float,.Int,.DateTime, ...), callable directly from command handlers and test code.Bounds,BoundsInt,RectOffset,Plane, andRay(Unity) plusSystem.Numerics.Complex. Each type parses positional components separated by a single console delimiter — boundscenter x,y,z size x,y,z(so0,0,0,1,1,1is a unit bounds at the origin), boundsIntposition x,y,z size x,y,z, rectOffset inRectOffset(left, right, top, bottom)order, planenormal x,y,z distance, rayorigin x,y,z direction x,y,z, and complexreal, imaginary— and the UnityToString()forms of bounds, boundsInt, plane, and ray (theirCenter:/Extents:/Position:/Size:/normal:/distance:/Origin:/Dir:labels are stripped; bounds extents are doubled to size). The composite parsers (Vector2throughRectInt,Color,Quaternion) are now public methods onCommandArgParsers, so handlers and tests can call them directly.Changed
traceno longer prints a command line. It read a fixed entry back, which is the console's echo oftraceitself - so it showed the message before it correctly only when the command abovetracehad said something, and printed that command's echo when it had not. It now reads the newest entry that is not a command echo, which also fixestracedispatched programmatically (shell.RunCommand("trace")), where the old fixed offset skipped the newest message.timeno longer drops argument quoting. It rejoined its arguments with single spaces and re-tokenized, so a quoted argument arrived as several:time set-variable greet "two words"was rejected withset-variable requires exactly 2 argumentsbefore the timed command ran at all. It now dispatches the parsed arguments it already has, sotimecan time anything the console can run.logandlog-terminalare unchanged - they want the flattened form, solog "a b"still printsa b. A$variableis now substituted once rather than twice, so a stored value that is itself a variable reference is no longer expanded again.doc.mddocumented four APIs whose real signatures take required arguments or do not exist at all. The completion guide promised layer and tag completion from an adapter whose constructor throws for those types. The themes and fonts guide was reachable only through the documentation sidebar.`and#`, pressing the console key while the terminal is open types a backtick or a tilde instead of closing the terminal (and the character used to be dropped from the line as well), and typing the console key in a palette search no longer closes the palette and opens the terminal behind it. Bindings that press no character are unchanged and keep working while you type: navigation and editing keys, function keys, modifiers, lock and media keys, mouse or joystick buttons, and anyctrl+chord. Escape still closes an open terminal; acloseHotkeybound to a character cannot. PlayerInput bindings are unaffected and still fire while a field has focus, because the keyboard controller reads the binding string rather than the key an action pressed.Terminal.Logcall to its caller now runs as a single pass over the trace instead of splitting it into lines and rejoining them. The recorded stack trace text is unchanged.[RegisterCommand]counts: reflection-discovered commands (precompiled DLLs, assemblies without a generated catalog, and Editor assemblies served through TypeCache) now bind their handler delegate on that command's first invocation instead of binding every declared command upfront, so the first command request no longer pays one delegate bind per declared command. Generic method definitions now register as rejected commands with the standard invalid-signature diagnostics instead of logging a console error at readiness; other degenerate-but-bindable signatures keep registering and running exactly as before. A reflection-discovered command whose delegate cannot bind on the running platform (previously a console error at readiness) now registers but reports the contained bind error at its first invocation.CommandExecutionContexts.NoneandCommandConfigurationFailure.Noneare obsolete source names; typeddefaultstill represents an empty mask or an unclassified failure. Existing numeric values are unchanged.TerminalUIcomponents now follow defined ownership rules. The newest enabled component owns the shared session configuration, andTerminalUI.Instancetracks the newest enabled component. Disabling or destroying the owner hands both to a remaining enabled terminal: the shared log buffer, history, and command configuration revert to the remaining terminal's settings, and built-in UI commands (list-themes,set-theme, and similar) keep working after the owner is destroyed instead of reporting "No Terminal UI found" while a live terminal remains.TerminalUIthat starts closed (the default) constructs its theme, font, log list, and input on the first open instead. Components that never open pay no UI-construction cost, and re-enabling a terminal rebuilds on the next open rather than on every enable. Opening still closes any command palette on the same document exactly as before.TerminalFontPackandTerminalThemePackassets keep their full command sets; terminal default font selection,SetFont,SetRandomFont, andlist-fontsbehavior are unchanged. Packs that referenced a removed weight (for example a customTerminalFontPackentry pointing atFiraCode-Bold) show a missing entry in the inspector and need that slot reassigned.int?instead of the-1 = unboundedsentinel:CommandInfo.maxArgCountis nowint?andCommandShell.AddCommandtakesint? maxArgs(defaultnull). Legacy callers passing-1keep compiling and mean unbounded.[RegisterCommandAttribute].MaxArgCountstaysint(attribute properties cannot be nullable) andCommandInfonormalizes negatives tonull.TerminalUIno longer runs command discovery on its enable frame. Commands register at the first command request (typically the first command run or completion query). The terminal re-applies its command configuration on every refresh, so auto commands cleared throughCommandShell.ClearAutoRegisteredCommandsreturn on the next first use after a terminal enable, instead of the previous enable-time registration.already definederror for the first command request. This also applies at enable time when a user command shadows a built-in command.-warnaserror), so a compiler warning inside these assemblies fails compilation for consumers too. A per-assembly response file replaces any project-levelcsc.rspfor these assemblies only.No font assignedorPersisted theme not foundwarning). Warnings remain for a font pack containing no fonts, a stale persisted theme or font name, and misconfiguration; the missing-font fallback to an OS font is now a warning instead of an error. Theme persistence progress messages only appear in the Editor and in development builds.Media/(README screenshots and the demo GIF). The README references them through absolute repository URLs so rendering on GitHub and npm is unchanged. This cuts 7.5 MB from the tarball consumers download.DateTime,DateTimeOffset,TimeSpan, andBigIntegerarguments parse withCultureInfo.InvariantCultureregardless of the device locale, so1.5means one and a half on every machine. Locale-formatted input (for example1,5on a comma-decimal locale, or locale-formatted dates) is no longer accepted for these types.RegisterCommandAttribute.NormalizeNamevalidates its method argument and throwsArgumentNullExceptionfor null instead of failing later at registration.Fixed
A background thread can no longer break the console's log. Unity's threaded log callback, and any
Terminal.Logfrom a worker thread, wrote the shared log buffer while the terminal read it every frame, and nothing guarded either side. The buffer's write position, its entry count, and its backing list are separate read-modify-writes, so a backgroundDebug.Logcould leave the reader computing an index from a position and a length that no longer agreed, and the console threw an out-of-range exception on that frame - with nothing in the message pointing at threads. Two threads reducing a stack trace at once could also splice one thread's frames onto the other's line, or erase them outright. The buffer now guards its own state, the write counter is atomic, and the trace reduction has its own lock. The terminal reads one consistent window per frame instead of a count and separate per-line reads, and the text is still escaped before the buffer is touched, so a log write costs what it did: 0.40 ms median with stack-trace capture and 0.00 ms without it, no allocation in either (300 samples in the Unity Editor on Mono; a player build is not measured). One limit: readingCommandLog.Logsstill counts and then indexes as two separate reads, so clearing or shrinking the buffer between them can still throw. The new publicCommandLog.CopyTo(LogItem[])is the one-call consistent read, and is what the terminal uses.Resizing a log or history buffer to the size it already had no longer breaks it. The write position was set one slot past the end of a full buffer, so the next entry grew the buffer past its capacity and every later read ran off the end of it - the same out-of-range exception, reachable with no thread at all through the public
Resize(Terminal.Buffer.Resize(Terminal.Buffer.Capacity)). The position is now always a slot inside the buffer. Lowering a buffer's size still keeps the oldest entries it was showing rather than the newest, as it always has.The log view follows its own output again. The console asked for a scroll only when the log view gained or lost a child, which misses two cases a Play Mode session hits in seconds. A burst of output that arrives in one frame asks for the scroll before the panel has laid the new content out, so the request lands on a stale (or, in a freshly opened console, empty) extent, scrolls to that instead, and is never made again. And a full log buffer keeps its count, so every line after the buffer filled rotated the ring without adding or removing a child: a line taller than the one it replaced - a wrapped message, a different font size, a resized window - grew the content with nothing for the trigger to see, and the view rested short of the newest line. New output now scrolls into view whenever the view is at its own end, a scroll away from the end detaches so the view holds the line the developer is reading, scrolling back to the end follows again, and running a command from the terminal shows its output whether the view was parked or not. "At the end" is the scroller's own value against its extent, so the mouse wheel, the scrollbar, and a scripted scroll all read the same. One limit: a command run from the quick-launch bar writes to the same log, so a parked view stays parked - the bar is its own surface.
The package's own inspectors now escape the names they print. The custom inspectors for
TerminalUI,TerminalThemePack, andTerminalFontPackbuilt their tooltips and dropdown labels from theme names, font names, command names, and asset paths with no escaping, so a name carrying a bidirectional override, a zero-width joiner, an invisible tag character, or a control character read one way there and another way in the value it selected. Those tooltips and popup labels render the same visible escapes the console shows. The value an index selects is still the raw name, soSetTheme,SetFont, the theme and font dropdowns, and the disabled-command list are unchanged. One limit, the same one the log has: a name holding the literal textAdmin\u202Eexeprints the same as a name holding a real override.A command that throws now says so in the console. A handler exception used to escape the terminal's input path, so in a device or standalone build it produced no in-game output at all and only Unity's own logger saw it. It is now contained and reported like every other command failure - one line in the log and in the quick-launch bar's error bar, naming the command, the exception type, and its message - while
Debug.LogExceptionkeeps the stack trace in the Editor console and the player log. Later commands still run, andRunCommandstill answerstrue, because the command ran and the failure is on the error queue.The quick-launch bar no longer closes over its own error. A command that ran and then reported a controlled error through the shell's error queue - a builder validation failure, a rejected value, a thrown handler - returned success to the bar, which closed and took the error text with it, so the developer saw nothing at all. The bar now keeps the error visible, keeps the rejected line editable, and reports the submission as failed. A command that printed output still keeps the bar open, unchanged.
History recall now parks the caret at the end of the recalled line. Pressing Up or Down in a line the developer had already typed left the caret where it was, so the next character landed in the middle of the command they had just recalled. A recalled line is a new value, so the caret moves with it; applying a suggestion from the bar does the same. As with every other caret write, this needs Unity 2022.1 or newer: on 2021.3 the engine places the caret after the value lands.
The suggestion bar no longer shows the previous keystroke's candidates. In
HintDisplayMode.Alwaysthe bar re-rendered on the number of rows and the selected index only, so typing a character that changed the candidates without changing how many there are -zaponarrowed tozapt- left the earlier candidates on screen, and clicking a row applied the candidate the row was built with instead of the one it showed. The bar now compares what its rows carry against the candidates the current input resolves to on every refresh, so it always shows and applies the current set.The suggestion bar and the quick-launch result rows now escape what they render. Log text is escaped where it enters the terminal, and both row lists printed a completion candidate - a history line, a
GameObjectname, a command description - verbatim, so the same text read one way in the log and another in the bar. Both now render a bidirectional override, a zero-width joiner, an invisible tag character, or a control character as the same visible\uXXXXescape the log shows, and the row still applies and runs the raw text: the escape is for the reader, and the command is what you chose.A caret inside a character no longer splits it. A text field moves its caret one UTF-16 code unit at a time, so a caret could sit between the two halves of an emoji, an accented letter, a joined emoji sequence, or a flag. The terminal then cut the argument at that offset: a completion provider was handed half a character, which no candidate can start with, so it filtered everything out and Tab did nothing at all without a word; and a paste landing on such a caret left half a surrogate in the field, which is not text a command can run. Every caret the console reads is now snapped out to the start of the character it is in, and every caret it computes is snapped where it is computed, so a provider is handed whole text, the replacement range still covers the whole character, a paste lands beside it, and an argument that holds an emoji no longer ends in half of one. Two limits, unchanged in kind: the snap moves the caret to the front of the character rather than past it, the jamo sequence Unicode composes into a Hangul syllable is the one character it does not join, and a paste leaves its caret at the end of the pasted text - a clipboard cut mid-emoji would otherwise snap the caret in front of the character just pasted.
A whitespace character, not only a space, now ends an unquoted argument. A pasted block, a log line, or a message from another system that reached the console as
give\nitem\t42ran as one argument holding two newlines and a tab; it now runs as the three arguments it reads as. This closes the class for every caller, not just paste: execution,CommandShell.TryEatArgument, completion, and the paste handler all read one shared definition of a token boundary, and a non-breaking space separates as well. Quoting is unchanged, so whitespace inside quotes is still one argument, and a completion candidate containing any whitespace is now quoted rather than only one containing a space. One limit, unchanged and intended: a$variableis substituted after the split, so a value you set withset-variableorCommandShell.SetVariableis substituted whole and keeps the whitespace it holds.Logged text is now escaped where it enters the terminal.
Terminal.Log, the Unity log callback, and any directCommandLogwrite now render bidirectional overrides, zero-width joiners, invisible tag characters, line and paragraph separators, and control characters as visible\uXXXXescapes instead of passing them through, so aGameObjectnamed"Admin\u202Eexe"no longer reads one way and copies out as another. Newlines, tabs, and carriage returns are kept (a CRLF pair folds to one line break, so multi-frame stack traces stay clean on Windows), and readable text - paths, regexes, JSON, emoji - is left exactly as written. A quick-launch error bar that quotes the token you typed gets the same treatment, and both quick-launch panels now bound long text with a visible(+N more chars), because neither has a scroller of its own. Sanitizing adds no allocation to a log write that allocated none before. Nothing is truncated: the log list keeps what you logged. One limit: a name holding the literal textAdmin\u202Eexeprints the same as a name holding a real override, so the escape is readable but not machine-decodable.A
PlayerInputaction bound to a character key no longer runs its message while a console field has focus. Every routed message (ToggleSmall,ToggleFull,Close,EnterCommand,HandlePrevious,HandleNext,CompleteCommand,ReverseCompleteCommand) now leaves its character to the field, matching the polled-hotkey path, so aToggleSmallaction on`cannot close the console mid-sentence. The console reads the control that performed the action, so a gamepad button, a mouse button, actrl+chord, a composite driven by any of those, and the same character key with no field focused all work as before. Bind a message to aButtonaction: aValueaction also sends a message on release, so a toggle on a key that types no character opens the console and closes it again.Published npm and Unity package artifacts no longer include the repository's test assemblies. Installing the Test Framework no longer breaks headless
-nographicscompilation. The test suites remain available in the source checkout.Unity 2021.3 compiles again:
TerminalUIandCommandPaletteUIwrote the input field'scursorIndex/selectIndexcaret properties, which only gained setters in Unity 2022.1, so the package failed to compile on the supported minimum editor. Caret placement (after a Tab completion, and the palette's panel re-clamp re-assert) is unchanged on Unity 2022.1+; on 2021.3 the engine places the caret after the input lands.Unity 2021.3 and 2022.3 compile again:
SceneObjectArgumentAdapter<T>'s sort-key field and the theme asset postprocessor's asset-identity check used Unity 6000.4+ entity-id APIs outside version checks, so the package failed to compile on the supported minimum editors. The guarded code paths are unchanged on Unity 6000.4+.A
CommandPaletteUIwithout anyTerminalUIin the scene now bootstraps the shared session on enable, so the palette lists and runs commands andTerminal.Logworks with no terminal present. Previously a palette-only setup showed no commands and logged nothing ("No command shell is available."). The bootstrap uses the assignedTerminalSettingsasset, or the default capacities without one, and only when no other component has already created the session — an existing session's buffers, filters, and registrations are never reconfigured by a palette. Command discovery stays deferred to first use.Attributed commands no longer vanish in player builds with managed stripping (
Mediumor higher, IL2CPP/WebGL). A new editor build hook feeds the stripping stage an additionallink.xml(written underTemp, never underAssets/) that preserves exactly the handlers reached only through reflection: private or protected handlers in non-partial types, handlers whose shape the generated catalog cannot bind directly (non-void returns, generic methods, invalid signatures), and handlers in precompiled assemblies without a generated catalog. Handlers the generated code binds as direct delegates or names through a partial companion are already rooted and are not preserved;EditorOnlyhandlers never register in players and are skipped. If the manifest cannot be written, the build proceeds with a warning and today's behavior.Swapping the
TerminalFontPackon a liveTerminalUInow re-resolves and reapplies the font immediately. Previously the pack change only took effect after a rebuild (set-font, a persisted font assignment, or a disable/re-enable), so the old pack's font kept rendering. Clearing the pack on a live terminal logs an error and keeps the last applied font, matching a rebuild.The first time a terminal opens, it now renders the resolved
TerminalFontPackfont. Previously the first UI build never wrote the font definition to the fresh document root (the resolved font applied only after a rebuild,set-font, a persisted font assignment, or a palette open), so a fresh terminal drew Unity's default OS font even with a font pack assigned. The quick-launch bar now also re-resolves the font each time it opens, so a bar opened before the terminal's first build picks the pack font up too.Reopening a terminal whose component was disabled and re-enabled no longer logs a spurious
Cannot set null font.error. The rebuild now reapplies the resolved font to the fresh visual tree instead of dropping the font definition silently.Terminal and quick-launch completions preserve literal
$names, close open quotes, and switch conflicting quote delimiters. Values that cannot form one literal token are not applied. Manually typed variables keep their existing behavior.Attributed commands with multidimensional
CommandArgarrays no longer cause generated-code compilation errors. They remain rejected commands, matching reflection-based registration.Quick-launch bar caret parking under panel resets: the caret for an applied completion or auto-loaded command name could land away from its target when the text field's own deferred caret reset ran after the first parking pass, leaving the caret mid-token (visible with quoted insertions). The queued caret is now retried until it holds across two panel passes, a user edit cancels any still-queued write, and the caret is not written to positions the field does not hold yet.
Dangling font-pack entries no longer throw: a
TerminalFontPacklist containing a destroyed font reference (for example after upgrading and a referenced weight no longer ships) crashedlist-fonts,set-font,SetRandomFont, default font selection, and theme persistence with aNullReferenceException. Those paths now skip the invalid entry, keeping font selection,list-fonts, and persistence working until the slot is reassigned in the inspector.Quick-launch bar output visibility: commands that print output (
list-fonts,list-themes,help, ...) now clear the executed command from the bar, display the output inside the palette, and keep it open instead of closing silently on success; silent successes still close percloseOnSuccessfulExecution, Enter with the cleared bar does not re-run, and typing a new query clears the shown output.Quick-launch bar shared-surface layout: with a
TerminalUIandCommandPaletteUIon the same UIDocument, the terminal's per-frame window-height clamp no longer tweens the palette to the top of the screen while it is open; the terminal yields the shared document root while the palette owns it and reclaims it when the palette closes.Quick-launch bar input focus: auto-loading a command name into the input now reliably parks the caret at the end (the text field's own deferred caret reset raced the write and could leave the caret mid-name or visibly select the loaded text), and clicking the results scrollbar no longer steals panel focus from the search input.
TerminalUI.SetStateno longer throws a NullReferenceException when the terminal component is disabled before its UI setup completed.Dynamic choice providers on subcommand arguments now receive a subcommand-relative completion context:
ActiveArgumentIndexcounts from the subcommand's first argument andPrecedingArgumentscontains exactly the subcommand's own arguments, matching what the same provider sees on a top-level command. Previously router tokens leaked into both.CommandShell.ClearCustomCommandsno longer leaves the auto-registered tracking state stale: it now also cancels a pending deferred registration and reportsAutoRegisteredCommands/ClearAutoRegisteredCommandscounts that match the actually registered commands. The returned count still covers every registered command (auto and custom), andClearAllCommandsreturns the same total.The input caret no longer jumps to line end when a scheduled focus pass re-fires; accepted completions place the caret after the insertion.
Scene transitions that detach the text input from its panel no longer throw a per-frame
NullReferenceException.TerminalUIguardsusing UnityEditor;with#if UNITY_EDITOR; the unguarded directive fails player-target compilation on Unity 2021.3/2022 (Unity 6 tolerates it).