@mudlet/mudlet-web 0.3.1 → 0.4.1

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 (97) hide show
  1. package/README.md +8 -0
  2. package/dist-lib/assets/qt-resources/index.d.ts +16 -0
  3. package/dist-lib/branding.d.ts +1 -1
  4. package/dist-lib/hooks/useMudSession.d.ts +1 -1
  5. package/dist-lib/hooks/useOutput.d.ts +3 -1
  6. package/dist-lib/import/defaultPackages.d.ts +35 -2
  7. package/dist-lib/import/defaults/generic_mapper/generic_mapper.mpackage +0 -0
  8. package/dist-lib/import/defaults/gui-drop/gui-drop.mpackage +0 -0
  9. package/dist-lib/import/defaults/{run-lua-code.mpackage → mudlet-base-ui/mudlet-base-ui.mpackage} +0 -0
  10. package/dist-lib/import/defaults/run-lua-code/run-lua-code.mpackage +0 -0
  11. package/dist-lib/import/packageExport.d.ts +71 -0
  12. package/dist-lib/import/packageInstaller.d.ts +4 -1
  13. package/dist-lib/index.js +45929 -40688
  14. package/dist-lib/logging/SessionLogger.d.ts +66 -1
  15. package/dist-lib/map/MapStore.d.ts +32 -4
  16. package/dist-lib/map/mapImageExport.d.ts +1 -1
  17. package/dist-lib/map/mapZoom.d.ts +71 -0
  18. package/dist-lib/mud/ItemIdSequence.d.ts +23 -0
  19. package/dist-lib/mud/MudSession.d.ts +113 -11
  20. package/dist-lib/mud/PatternEngine.d.ts +6 -1
  21. package/dist-lib/mud/commandSplit.d.ts +16 -0
  22. package/dist-lib/mud/connection/LineAssembler.d.ts +56 -0
  23. package/dist-lib/mud/connection/MudClient.d.ts +120 -16
  24. package/dist-lib/mud/connection/TelnetNegotiator.d.ts +39 -0
  25. package/dist-lib/mud/connection/telnetByteTags.d.ts +35 -0
  26. package/dist-lib/mud/events.d.ts +19 -4
  27. package/dist-lib/mud/games/bundledGames.d.ts +29 -0
  28. package/dist-lib/mud/games/gameIcons.d.ts +5 -0
  29. package/dist-lib/mud/keybindings/KeyEngine.d.ts +9 -2
  30. package/dist-lib/mud/keybindings/browserReservedKeys.d.ts +5 -1
  31. package/dist-lib/mud/keybindings/keyEventTarget.d.ts +17 -0
  32. package/dist-lib/mud/keybindings/qtKeys.d.ts +2 -0
  33. package/dist-lib/mud/protocol/charLoginFlow.d.ts +68 -0
  34. package/dist-lib/mud/protocol/charset.d.ts +27 -5
  35. package/dist-lib/mud/protocol/constants.d.ts +1 -0
  36. package/dist-lib/mud/protocol/gmcp.d.ts +19 -3
  37. package/dist-lib/mud/protocol/index.d.ts +1 -1
  38. package/dist-lib/mud/protocol/mxp.d.ts +28 -5
  39. package/dist-lib/mud/replay/ReplayPlayer.d.ts +17 -0
  40. package/dist-lib/mud/text/Console.d.ts +64 -0
  41. package/dist-lib/mud/text/FormatState.d.ts +66 -3
  42. package/dist-lib/mud/text/hyperlinkVisibility.d.ts +35 -0
  43. package/dist-lib/mud/text/osc8Docs.d.ts +23 -0
  44. package/dist-lib/mud/text/serverWrap.d.ts +191 -0
  45. package/dist-lib/mud/timers/TimerEngine.d.ts +43 -12
  46. package/dist-lib/mud/triggers/TriggerEngine.d.ts +112 -3
  47. package/dist-lib/scripting/EngineHost.d.ts +56 -3
  48. package/dist-lib/scripting/IScriptingRuntime.d.ts +16 -0
  49. package/dist-lib/scripting/MxpFrameManager.d.ts +118 -0
  50. package/dist-lib/scripting/ScriptingAPI.d.ts +323 -54
  51. package/dist-lib/scripting/ScriptingEngine.d.ts +180 -23
  52. package/dist-lib/scripting/http/HttpService.d.ts +51 -1
  53. package/dist-lib/scripting/lua/LuaRuntime.d.ts +151 -2
  54. package/dist-lib/scripting/lua/bindings/map.d.ts +0 -12
  55. package/dist-lib/scripting/lua/utf8Patterns.d.ts +59 -0
  56. package/dist-lib/storage/index.d.ts +1 -1
  57. package/dist-lib/storage/persistentStorage.d.ts +10 -0
  58. package/dist-lib/storage/schema.d.ts +116 -0
  59. package/dist-lib/styles.css +1 -1
  60. package/dist-lib/ui/BundledGameGrid.d.ts +21 -0
  61. package/dist-lib/ui/CommandBar.d.ts +7 -1
  62. package/dist-lib/ui/ConnectionGrid.d.ts +4 -1
  63. package/dist-lib/ui/FilePickerModal.d.ts +3 -3
  64. package/dist-lib/ui/HelpModal.d.ts +11 -0
  65. package/dist-lib/ui/LogBrowserModal.d.ts +4 -1
  66. package/dist-lib/ui/PlayerMarkerPreview.d.ts +20 -0
  67. package/dist-lib/ui/Toolbar.d.ts +2 -1
  68. package/dist-lib/ui/VfsPickerModal.d.ts +26 -0
  69. package/dist-lib/ui/components/FileSourceButton.d.ts +47 -0
  70. package/dist-lib/ui/components/index.d.ts +1 -0
  71. package/dist-lib/ui/helpTopics.d.ts +19 -0
  72. package/dist-lib/ui/labels/LabelManager.d.ts +7 -0
  73. package/dist-lib/ui/layout/ScriptWindow.d.ts +11 -2
  74. package/dist-lib/ui/markdown.d.ts +10 -1
  75. package/dist-lib/ui/output/OutputArea.d.ts +11 -1
  76. package/dist-lib/ui/output/OutputContextMenu.d.ts +3 -1
  77. package/dist-lib/ui/output/OutputRenderer.d.ts +7 -1
  78. package/dist-lib/ui/output/OutputSearchBar.d.ts +25 -0
  79. package/dist-lib/ui/output/StickyOutputPanel.d.ts +5 -1
  80. package/dist-lib/ui/output/outputSearch.d.ts +130 -0
  81. package/dist-lib/ui/search/matcher.d.ts +15 -0
  82. package/dist-lib/ui/search/useDebounced.d.ts +3 -0
  83. package/dist-lib/ui/tts/TtsManager.d.ts +3 -0
  84. package/dist-lib/ui/useCommandHistory.d.ts +4 -2
  85. package/dist-lib/ui/useOpenProfiles.d.ts +26 -0
  86. package/dist-lib/ui/video/VideoManager.d.ts +17 -0
  87. package/dist-lib/ui/windows/WindowManager.d.ts +132 -2
  88. package/dist-lib/ui/windows/panels/MapPanel.d.ts +4 -1
  89. package/dist-lib/ui/windows/panels/PackageExportModal.d.ts +18 -0
  90. package/dist-lib/ui/windows/panels/TextPanel.d.ts +3 -1
  91. package/dist-lib/ui/windows/types.d.ts +35 -0
  92. package/dist-lib/utils/describeThrown.d.ts +22 -0
  93. package/dist-lib/utils/fontLoader.d.ts +5 -0
  94. package/dist-lib/vite.js +22 -0
  95. package/package.json +10 -6
  96. package/dist-lib/scripting/lua/mudlet-lua/generic-mapper/generic_mapper.mpackage +0 -0
  97. package/dist-lib/ui/windows/panels/VfsModulePickerModal.d.ts +0 -8
@@ -0,0 +1,21 @@
1
+ import { type BundledGame } from '../mud/games/bundledGames';
2
+ import type { MudConnection } from '../storage';
3
+ /** The connection record a game turns into. Mudlet's catalogue is host/port, so
4
+ * these are proxied `mud` profiles — the same thing the Add form builds. */
5
+ export declare function connectionFromGame(game: BundledGame): Omit<MudConnection, 'id'>;
6
+ interface Props {
7
+ /** Profiles that already exist, so a game someone has played is offered by
8
+ * its profile tile above rather than a second time down here. */
9
+ connections: MudConnection[];
10
+ busy: boolean;
11
+ /** Create the profile and start playing. */
12
+ onPlay: (game: BundledGame) => void;
13
+ }
14
+ /**
15
+ * The games Mudlet ships with, offered the way Mudlet offers them: a browsable
16
+ * list you can start playing without filling in a form. Picking one is what
17
+ * turns it into a profile — until then nothing is stored, so the list is not a
18
+ * pile of empty profiles a player has to tidy up.
19
+ */
20
+ export declare function BundledGameGrid({ connections, busy, onPlay }: Props): import("react/jsx-runtime").JSX.Element | null;
21
+ export {};
@@ -10,8 +10,14 @@ interface CommandBarProps {
10
10
  /** Tab-completion suggestions added via Mudlet's addCmdLineSuggestion API.
11
11
  * Merged ahead of command history (dedup, case-insensitive). */
12
12
  suggestions?: string[];
13
+ /** Words Mudlet's addCmdLineBlacklist API has taken out of Tab completion.
14
+ * Subtractive across every source below, not just `suggestions`. */
15
+ blacklist?: string[];
16
+ /** Mudlet's per-command-line setSaveCommandHistory. False keeps the history
17
+ * for this session but stops persisting it. */
18
+ saveHistory?: boolean;
13
19
  /** Recency-ordered words seen in output, for argument-word Tab completion. */
14
20
  bufferWords?: BufferWordIndex | null;
15
21
  }
16
- export declare function CommandBar({ command, onCommandChange, passwordMode, commandInputRef, onSubmit, cmdLineMenu, suggestions, bufferWords }: CommandBarProps): import("react/jsx-runtime").JSX.Element;
22
+ export declare function CommandBar({ command, onCommandChange, passwordMode, commandInputRef, onSubmit, cmdLineMenu, suggestions, blacklist, saveHistory, bufferWords }: CommandBarProps): import("react/jsx-runtime").JSX.Element;
17
23
  export {};
@@ -4,6 +4,9 @@ interface Props {
4
4
  connecting: boolean;
5
5
  connectingId: string | null;
6
6
  editingId: string | null;
7
+ /** Ids already open in another browser tab — one tab per profile, so these
8
+ * can't be opened here until that tab releases them. */
9
+ openIds: Set<string>;
7
10
  onConnect: (c: MudConnection) => void;
8
11
  onOpen: (c: MudConnection) => void;
9
12
  onEdit: (c: MudConnection) => void;
@@ -14,5 +17,5 @@ interface Props {
14
17
  /** Draggable, animated grid of profile cards. Reordering lifts the dragged tile
15
18
  * into a floating clone, opens a dashed placeholder at the target slot, and
16
19
  * slides the remaining tiles into their new positions with a FLIP animation. */
17
- export declare function ConnectionGrid({ connections, connecting, connectingId, editingId, onConnect, onOpen, onEdit, onDelete, onReorder, onAddClick, }: Props): import("react/jsx-runtime").JSX.Element;
20
+ export declare function ConnectionGrid({ connections, connecting, connectingId, editingId, openIds, onConnect, onOpen, onEdit, onDelete, onReorder, onAddClick, }: Props): import("react/jsx-runtime").JSX.Element;
18
21
  export {};
@@ -5,9 +5,9 @@ import type { ProfileVFS } from '../scripting/vfs/ProfileVFS';
5
5
  * header ✕) must call `onDone` — with the picked absolute VFS path, or '' for
6
6
  * cancel (Mudlet's return value for a dismissed dialog).
7
7
  *
8
- * `mode === 'file'` picks a file; `mode === 'folder'` picks a directory and
9
- * hides files entirely, mirroring QFileDialog::getOpenFileName vs
10
- * getExistingDirectory in Mudlet.
8
+ * The tree itself is VfsPickerModal, shared with the app's "pick from profile
9
+ * files" buttons; this wrapper only narrows it to the single-path contract Lua
10
+ * expects.
11
11
  */
12
12
  interface FilePickerModalProps {
13
13
  vfs: ProfileVFS | null;
@@ -0,0 +1,11 @@
1
+ import './HelpModal.css';
2
+ interface HelpModalProps {
3
+ /** Bounds are remembered per profile; omitted on the start screen, where
4
+ * there is no active connection to key them by. */
5
+ connectionId?: string;
6
+ /** Topic to open on — defaults to the migration guide. */
7
+ initialTopic?: string;
8
+ onClose: () => void;
9
+ }
10
+ export declare function HelpModal({ connectionId, initialTopic, onClose }: HelpModalProps): import("react/jsx-runtime").JSX.Element;
11
+ export {};
@@ -1,7 +1,10 @@
1
+ import type { ProfileVFS } from '../scripting/vfs/ProfileVFS';
1
2
  interface LogBrowserModalProps {
2
3
  connectionId: string;
3
4
  connectionName: string;
5
+ /** Lets "Import…" read a log export the profile already holds. */
6
+ vfs?: ProfileVFS | null;
4
7
  onClose: () => void;
5
8
  }
6
- export declare function LogBrowserModal({ connectionId, connectionName, onClose }: LogBrowserModalProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function LogBrowserModal({ connectionId, connectionName, vfs, onClose }: LogBrowserModalProps): import("react/jsx-runtime").JSX.Element;
7
10
  export {};
@@ -0,0 +1,20 @@
1
+ import { type MapperSettings } from '../storage';
2
+ interface PlayerMarkerPreviewProps {
3
+ /** The profile's live mapper settings — the preview honours all of them
4
+ * (room shape/size, borders, exit colour, background, grid), not just the
5
+ * marker, so what it shows is what the map will look like. */
6
+ mapper: MapperSettings | undefined;
7
+ }
8
+ /**
9
+ * Live preview of the player-position marker, rendered by the *actual*
10
+ * `MapRenderer` against a five-room stub map.
11
+ *
12
+ * Deliberately not a hand-drawn SVG mock: the marker's geometry (size relative
13
+ * to `roomSize`, dash phase, the rect/circle switch driven by `matchRoomShape`
14
+ * + `roomShape`, the rounded-rectangle corner radius) all lives in the
15
+ * renderer, and a reimplementation here would drift silently the first time
16
+ * the library changes it. Konva is already in the bundle for MapPanel, so this
17
+ * costs a stage, not a chunk.
18
+ */
19
+ export declare function PlayerMarkerPreview({ mapper }: PlayerMarkerPreviewProps): import("react/jsx-runtime").JSX.Element;
20
+ export {};
@@ -12,6 +12,7 @@ interface ToolbarProps {
12
12
  onOpenFiles: () => void;
13
13
  onOpenLogs: () => void;
14
14
  onOpenDocs: () => void;
15
+ onOpenHelp: () => void;
15
16
  onOpenSettings: () => void;
16
17
  /** Mudlet-format replay recording toggle state + handler. */
17
18
  replayRecording: boolean;
@@ -25,5 +26,5 @@ interface ToolbarProps {
25
26
  /** Capabilities handed to brand toolbar buttons (send / raiseEvent). */
26
27
  brandContext?: BrandToolbarContext;
27
28
  }
28
- export declare function Toolbar({ connectionName, status, ping, onDisconnect, onReconnect, onNewConnection, onOpenMap, onOpenScripts, onOpenFiles, onOpenLogs, onOpenDocs, onOpenSettings, replayRecording, onToggleReplayRecording, replaySpeed, onReplaySpeedChange, onReplayStop, onContextMenu, brandContext }: ToolbarProps): import("react/jsx-runtime").JSX.Element;
29
+ export declare function Toolbar({ connectionName, status, ping, onDisconnect, onReconnect, onNewConnection, onOpenMap, onOpenScripts, onOpenFiles, onOpenLogs, onOpenDocs, onOpenHelp, onOpenSettings, replayRecording, onToggleReplayRecording, replaySpeed, onReplaySpeedChange, onReplayStop, onContextMenu, brandContext }: ToolbarProps): import("react/jsx-runtime").JSX.Element;
29
30
  export {};
@@ -0,0 +1,26 @@
1
+ import type { ProfileVFS } from '../scripting/vfs/ProfileVFS';
2
+ /**
3
+ * The profile-VFS file/folder picker. Backs both Lua's `invokeFileDialog`
4
+ * (single file or folder — see FilePickerModal) and the "pick from profile
5
+ * files" half of every place the app would otherwise only take an upload from
6
+ * the user's computer.
7
+ *
8
+ * `mode`:
9
+ * - `file` — one file (QFileDialog::getOpenFileName)
10
+ * - `files` — several files, checkbox per row
11
+ * - `folder` — one directory, files hidden (getExistingDirectory)
12
+ */
13
+ export type VfsPickerMode = 'file' | 'files' | 'folder';
14
+ interface VfsPickerModalProps {
15
+ vfs: ProfileVFS | null;
16
+ mode: VfsPickerMode;
17
+ title?: string;
18
+ /** VFS path to reveal on open; '' or invalid falls back to the root. */
19
+ location?: string;
20
+ /** Filename filter — non-matching files are hidden (folders always show). */
21
+ accept?: RegExp;
22
+ /** Picked absolute VFS paths; an empty array means the user cancelled. */
23
+ onDone: (paths: string[]) => void;
24
+ }
25
+ export declare function VfsPickerModal({ vfs, mode, title, location, accept, onDone }: VfsPickerModalProps): import("react").ReactPortal;
26
+ export {};
@@ -0,0 +1,47 @@
1
+ import { type ButtonHTMLAttributes, type ReactNode } from 'react';
2
+ import type { ProfileVFS } from '../../scripting/vfs/ProfileVFS';
3
+ export interface PickedFile {
4
+ file: File;
5
+ /** Absolute VFS path — set only when the file came from the profile. */
6
+ vfsPath?: string;
7
+ }
8
+ export interface FileSourceOptions {
9
+ vfs: ProfileVFS | null;
10
+ /** `accept` for the OS picker; also derives the VFS filter when `vfsAccept` is absent. */
11
+ accept?: string;
12
+ /** Filename filter for the VFS tree. Pass `null` to show every file. */
13
+ vfsAccept?: RegExp | null;
14
+ multiple?: boolean;
15
+ /** Start directory for the VFS picker. */
16
+ location?: string;
17
+ /** Title of the VFS picker window. */
18
+ pickerTitle?: string;
19
+ onPick: (files: PickedFile[]) => void | Promise<void>;
20
+ /** Surfaces a VFS read failure; falls back to console.warn when absent. */
21
+ onError?: (message: string) => void;
22
+ }
23
+ /**
24
+ * Turn an `accept` attribute into a filename filter for the VFS tree.
25
+ * Extensions and `type/*` wildcards translate; a concrete MIME type
26
+ * (`application/json`) doesn't, since VFS entries carry no content type — those
27
+ * contribute nothing, and an accept list of only such entries filters nothing
28
+ * rather than hiding every file.
29
+ */
30
+ export declare function acceptToRegExp(accept?: string): RegExp | undefined;
31
+ /**
32
+ * Headless half of the dual-source picker, for call sites whose trigger isn't a
33
+ * plain `Button` (the map overlay's own buttons, hamburger menu entries).
34
+ * Render `elements` anywhere in the subtree and call `open(triggerElement)`.
35
+ */
36
+ export declare function useFileSource({ vfs, accept, vfsAccept, multiple, location, pickerTitle, onPick, onError, }: FileSourceOptions): {
37
+ open: (anchor: HTMLElement | DOMRect | null) => void;
38
+ elements: ReactNode;
39
+ };
40
+ type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'icon' | 'danger';
41
+ interface FileSourceButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onChange' | 'onError'>, FileSourceOptions {
42
+ label: ReactNode;
43
+ variant?: ButtonVariant;
44
+ size?: 'sm' | 'md';
45
+ }
46
+ export declare function FileSourceButton({ vfs, label, accept, vfsAccept, multiple, location, pickerTitle, onPick, onError, variant, size, ...buttonProps }: FileSourceButtonProps): import("react/jsx-runtime").JSX.Element;
47
+ export {};
@@ -2,6 +2,7 @@ export { Button } from './Button';
2
2
  export { Input } from './Input';
3
3
  export { FormField, CheckField } from './FormField';
4
4
  export { ContextMenu } from './ContextMenu';
5
+ export { FileSourceButton, useFileSource, acceptToRegExp, type PickedFile } from './FileSourceButton';
5
6
  export { ConfirmDialog, ConfirmProvider, useConfirm } from './ConfirmDialog';
6
7
  export type { ConfirmButton, ConfirmOptions } from './ConfirmDialog';
7
8
  export { FontPicker } from './FontPicker';
@@ -0,0 +1,19 @@
1
+ export interface HelpTopic {
2
+ /** Stable id used in state and (eventually) deep links. */
3
+ id: string;
4
+ /** Sidebar label. Kept short — the markdown's own H1 is the real title. */
5
+ title: string;
6
+ /** One line under the label, so the sidebar answers "which one do I want?" */
7
+ blurb: string;
8
+ /** Basename in docs/help/, for resolving relative links between topics. */
9
+ file: string;
10
+ markdown: string;
11
+ }
12
+ export declare const HELP_TOPICS: HelpTopic[];
13
+ export declare const DEFAULT_HELP_TOPIC: string;
14
+ /** Resolve a markdown link href to a topic id, or null if it isn't one of ours.
15
+ * Handles `./storage.md`, `storage.md`, `docs/help/storage.md` and a `#anchor`
16
+ * suffix — the shapes a relative link between these files can take. */
17
+ export declare function topicForHref(href: string): string | null;
18
+ /** Topics whose title, blurb or body mention `query`. Empty query = everything. */
19
+ export declare function searchHelpTopics(query: string): HelpTopic[];
@@ -167,6 +167,13 @@ export declare class LabelManager {
167
167
  /** Mudlet setMovie — install (or replace) the label's GIF player. */
168
168
  setMovie(name: string, player: MoviePlayer): boolean;
169
169
  getMovie(name: string): MoviePlayer | null;
170
+ /** Mudlet's `getProfileStats().gifs`: how many labels carry a movie, and
171
+ * how many of those are actually running. Deleting a label takes its movie
172
+ * out of both counts, since the label is where a movie lives. */
173
+ movieStats(): {
174
+ total: number;
175
+ active: number;
176
+ };
170
177
  /** Mudlet scaleMovie — autoscale tracks the label size (CSS 100%);
171
178
  * autoscale=false freezes the scale at the label's current geometry.
172
179
  * False when the label has no movie (matches Mudlet's warning case). */
@@ -1,4 +1,4 @@
1
- import type { DockSide, DragState } from '../windows/types';
1
+ import type { DockSide, DragState, MxpTabPage } from '../windows/types';
2
2
  import type { WindowManager } from '../windows/WindowManager';
3
3
  interface ScriptWindowProps {
4
4
  id: string;
@@ -17,6 +17,15 @@ interface ScriptWindowProps {
17
17
  /** When true, dragging the titlebar still moves the window but never
18
18
  * enters a dock zone — mirrors Mudlet's openUserWindow(..., autoDock=false). */
19
19
  lockFloating?: boolean;
20
+ /** MXP `<FRAME TITLE=…>` tab strip drawn above the content. The first page
21
+ * is this window's own console; the rest are `DOCK`ed frames nested inside
22
+ * its viewport. See MxpFrameManager. */
23
+ frameTabs?: {
24
+ pages: MxpTabPage[];
25
+ active: string;
26
+ };
27
+ /** Console backing an MXP `<FRAME>` — outlines it, like Mudlet does. */
28
+ isMxpFrame?: boolean;
20
29
  onFocus: () => void;
21
30
  onMoved: (x: number, y: number) => void;
22
31
  onResized: (w: number, h: number) => void;
@@ -25,5 +34,5 @@ interface ScriptWindowProps {
25
34
  onTitlebarContextMenu: (e: React.MouseEvent) => void;
26
35
  onHide: () => void;
27
36
  }
28
- export declare function ScriptWindow({ id, title, visible, x, y, width, height, zIndex, manager, isMiniConsole, lockFloating, onFocus, onMoved, onResized, onDock, onDragStateChange, onTitlebarContextMenu, onHide, }: ScriptWindowProps): import("react/jsx-runtime").JSX.Element;
37
+ export declare function ScriptWindow({ id, title, visible, x, y, width, height, zIndex, manager, isMiniConsole, lockFloating, frameTabs, isMxpFrame, onFocus, onMoved, onResized, onDock, onDragStateChange, onTitlebarContextMenu, onHide, }: ScriptWindowProps): import("react/jsx-runtime").JSX.Element;
29
38
  export {};
@@ -1 +1,10 @@
1
- export declare function renderMarkdown(src: string): string;
1
+ interface RenderOptions {
2
+ /** Turn a single newline into a `<br>`. On for user-written notes, where
3
+ * `gfm + breaks` matches the behavior most note apps render. Off for
4
+ * hard-wrapped prose documents, where source line breaks are just wrapping
5
+ * and rendering them would shred every paragraph — which is also how GitHub
6
+ * renders a `.md` file. */
7
+ breaks?: boolean;
8
+ }
9
+ export declare function renderMarkdown(src: string, { breaks }?: RenderOptions): string;
10
+ export {};
@@ -4,6 +4,16 @@ interface OutputAreaProps {
4
4
  session: MudSession;
5
5
  stickyLines?: number;
6
6
  commandInputRef?: React.RefObject<HTMLInputElement | HTMLTextAreaElement | null>;
7
+ /** Extra insets claimed by edge-aligned MXP `<FRAME>`s, on top of the
8
+ * profile's setBorder* sizes. Mudlet keeps the two apart the same way
9
+ * (Host::mUserBorders + mMxpBorders) so a server layout and a script never
10
+ * clobber each other's borders. */
11
+ mxpBorders?: {
12
+ top: number;
13
+ right: number;
14
+ bottom: number;
15
+ left: number;
16
+ };
7
17
  }
8
- export declare function OutputArea({ session, stickyLines, commandInputRef }: OutputAreaProps): import("react/jsx-runtime").JSX.Element;
18
+ export declare function OutputArea({ session, stickyLines, commandInputRef, mxpBorders }: OutputAreaProps): import("react/jsx-runtime").JSX.Element;
9
19
  export {};
@@ -12,6 +12,8 @@ interface OutputContextMenuProps {
12
12
  onCopy: () => void;
13
13
  onCopyHtml: () => void;
14
14
  onCopyImage: () => void;
15
+ /** Opens the find bar — only the main console passes this. */
16
+ onFind?: () => void;
15
17
  /** Timestamp toggle — only the main console passes these. */
16
18
  showTimestamps?: boolean;
17
19
  onToggleTimestamps?: () => void;
@@ -19,5 +21,5 @@ interface OutputContextMenuProps {
19
21
  extraItems?: OutputMenuExtraItem[];
20
22
  onClose: () => void;
21
23
  }
22
- export declare function OutputContextMenu({ x, y, hasSelection, onSelectAll, onCopy, onCopyHtml, onCopyImage, showTimestamps, onToggleTimestamps, extraItems, onClose, }: OutputContextMenuProps): import("react/jsx-runtime").JSX.Element;
24
+ export declare function OutputContextMenu({ x, y, hasSelection, onSelectAll, onCopy, onCopyHtml, onCopyImage, onFind, showTimestamps, onToggleTimestamps, extraItems, onClose, }: OutputContextMenuProps): import("react/jsx-runtime").JSX.Element;
23
25
  export {};
@@ -47,6 +47,12 @@ type OutputHandlerOptions = {
47
47
  maxElements?: number | (() => number);
48
48
  trimSlack?: number;
49
49
  suppressSplitView?: (durationMs: number) => void;
50
+ /** Whether new output should scroll the view to the tail. True for a
51
+ * scrollback; false for a console the writer rewrites wholesale — an MXP
52
+ * frame written with `<DEST … EOF>` — where following the tail makes the
53
+ * content slide as a multi-flush redraw lands. Read live on every write, so
54
+ * a console can become top-anchored after it is already mounted. */
55
+ followTail?: () => boolean;
50
56
  /** Called once the cursor ops are ready; wire them to ScriptingAPI/session. */
51
57
  onCursorReady?: (ops: CursorOps) => void;
52
58
  };
@@ -60,5 +66,5 @@ export type OutputRendererControls = {
60
66
  push: (message: string | AnsiAwareBuffer, type?: string, timestamp?: number) => void;
61
67
  clear: () => void;
62
68
  };
63
- export declare function setupOutputRenderer(source: MessageSource | null, { outputWrapper, sentinel, stickyArea, isSplitView, stickyLines, maxElements, trimSlack, suppressSplitView, onCursorReady, }: OutputHandlerOptions): OutputRendererControls;
69
+ export declare function setupOutputRenderer(source: MessageSource | null, { outputWrapper, sentinel, stickyArea, isSplitView, stickyLines, maxElements, trimSlack, suppressSplitView, followTail, onCursorReady, }: OutputHandlerOptions): OutputRendererControls;
64
70
  export {};
@@ -0,0 +1,25 @@
1
+ import type React from 'react';
2
+ import type { MudSession } from '../../mud/MudSession';
3
+ import './OutputSearchBar.css';
4
+ interface OutputSearchBarProps {
5
+ session: MudSession;
6
+ /** The `.output-wrapper` scroller holding the rendered scrollback. */
7
+ outputRef: React.RefObject<HTMLDivElement | null>;
8
+ /** Changes on every Ctrl+F. Re-focuses the box and re-seeds it from the
9
+ * output selection, so pressing the shortcut again behaves like the
10
+ * browser's find rather than doing nothing. */
11
+ focusNonce: number;
12
+ onClose: () => void;
13
+ /** Rebuilds the split-view panel from the (now highlighted) source lines —
14
+ * its contents are clones, so marks only reach it via a re-populate. */
15
+ refreshSticky?: () => void;
16
+ /** Focus target once the bar closes, so the player can keep typing. */
17
+ commandInputRef?: React.RefObject<HTMLInputElement | HTMLTextAreaElement | null>;
18
+ /** Mudlet's `f3SearchEnabled` — the accessibility mode for buffer search.
19
+ * Turns each step into a screen-reader event: only the current hit stays
20
+ * tinted, focus parks on it, and the whole matched line is announced.
21
+ * See docs/config-api.md. */
22
+ a11ySearch?: boolean;
23
+ }
24
+ export declare function OutputSearchBar({ session, outputRef, focusNonce, onClose, refreshSticky, commandInputRef, a11ySearch, }: OutputSearchBarProps): import("react/jsx-runtime").JSX.Element;
25
+ export {};
@@ -13,6 +13,8 @@ interface StickyOutputPanelProps {
13
13
  foreground?: string;
14
14
  showTimestamps?: boolean;
15
15
  onToggleTimestamps?: () => void;
16
+ /** Opens the find bar from the right-click menu — main console only. */
17
+ onFind?: () => void;
16
18
  /** Script-provided right-click entries (Mudlet addMouseEvent), evaluated
17
19
  * lazily when the menu opens since the registry can change. */
18
20
  getMenuExtraItems?: () => OutputMenuExtraItem[];
@@ -20,9 +22,11 @@ interface StickyOutputPanelProps {
20
22
  className?: string;
21
23
  fontSize?: number;
22
24
  fontFamily?: string;
25
+ /** Rendered row height in px, overriding the stylesheet line-height. */
26
+ lineHeight?: number;
23
27
  wrapAt?: number;
24
28
  wrapIndent?: number;
25
29
  wrapHangingIndent?: number;
26
30
  }
27
- export declare function StickyOutputPanel({ outputRef, sentinelRef, stickyAreaRef, isSplitView, scrollToBottom, background, backgroundExtra, foreground, showTimestamps, onToggleTimestamps, getMenuExtraItems, commandInputRef, className, fontSize, fontFamily, wrapAt, wrapIndent, wrapHangingIndent, }: StickyOutputPanelProps): import("react/jsx-runtime").JSX.Element;
31
+ export declare function StickyOutputPanel({ outputRef, sentinelRef, stickyAreaRef, isSplitView, scrollToBottom, background, backgroundExtra, foreground, showTimestamps, onToggleTimestamps, onFind, getMenuExtraItems, commandInputRef, className, fontSize, fontFamily, lineHeight, wrapAt, wrapIndent, wrapHangingIndent, }: StickyOutputPanelProps): import("react/jsx-runtime").JSX.Element;
28
32
  export {};
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Find-in-output: scans the rendered scrollback for a query and tints every hit.
3
+ *
4
+ * This works purely on the rendered DOM rather than on the `AnsiAwareBuffer`
5
+ * model behind each line. Two reasons:
6
+ *
7
+ * - Highlighting is transient. `buffer.applyFormat()` rewrites segment state,
8
+ * so undoing a search highlight would mean snapshotting and restoring every
9
+ * touched line's formatting — and a script that recolours a line mid-search
10
+ * would lose its change. Wrapping text nodes in `<mark>` leaves the model
11
+ * untouched, and unwrapping restores the DOM exactly.
12
+ * - Offsets line up by construction. `appendCells` can substitute text on the
13
+ * way to the DOM (tab-stop expansion, Mudlet control-character glyphs), so
14
+ * `buffer.text` offsets do not always index the rendered line. Searching the
15
+ * rendered text means we match what the player can actually see.
16
+ *
17
+ * The sticky split-view panel holds `cloneNode` copies of the last N lines, so
18
+ * it is not scanned — it lives outside the `.output-wrapper` scroller we walk.
19
+ * Re-populating it after highlighting copies the marks along with the lines.
20
+ */
21
+ import type { MatchRange, SearchMatcher } from '../search/matcher';
22
+ export declare const HIT_CLASS = "output-search-hit";
23
+ export declare const CURRENT_HIT_CLASS = "output-search-hit--current";
24
+ export interface OutputMatch {
25
+ /** The `.output-msg` line element containing this hit. */
26
+ line: HTMLElement;
27
+ /** Offsets into the line's rendered text — `[start, end)`. */
28
+ range: MatchRange;
29
+ }
30
+ /**
31
+ * Every match in the output, in document (i.e. chronological) order.
32
+ *
33
+ * Cheap enough to re-run on each keystroke: the per-line cost is one
34
+ * `textContent` read plus the matcher's scan, and only lines that actually hit
35
+ * get walked node-by-node later, in {@link applyHighlights}.
36
+ */
37
+ export declare function scanOutput(container: HTMLElement, matcher: SearchMatcher): OutputMatch[];
38
+ /**
39
+ * Tint every match, giving `matches[currentIndex]` the "current hit" treatment.
40
+ *
41
+ * Callers must {@link clearHighlights} first — marking an already-marked line
42
+ * would nest marks. Matches whose line has since been evicted from the DOM are
43
+ * skipped rather than treated as an error; the scrollback trims as new output
44
+ * arrives and a stale match is normal, not exceptional.
45
+ */
46
+ export declare function applyHighlights(matches: OutputMatch[], currentIndex: number): void;
47
+ /**
48
+ * Whether an Escape that reached the document should close the find bar.
49
+ *
50
+ * The bar's own input handles Escape directly, but the player usually leaves
51
+ * focus in the command line while reading results, and there Escape did nothing
52
+ * — the bar just stayed up. So the bar also listens on the document, in the
53
+ * BUBBLE phase, and defers to everything with a better claim on the key:
54
+ *
55
+ * - `defaultPrevented` — someone already acted on it. Covers the bar's own
56
+ * input, and layers correctly under CommandBar, which preventDefaults
57
+ * Escape only while an autocomplete ghost is showing: first Escape dismisses
58
+ * the ghost, a second one closes the bar.
59
+ * - Modals (`useModalFocus`) stopPropagation on their own node, and
60
+ * CaretReviewPanel uses capture + stopImmediatePropagation while focused, so
61
+ * neither ever reaches a document bubble listener in the first place.
62
+ * - The origin is then required to be inside `.app`, the profile-session shell
63
+ * that owns the bar — toolbar, output, command line, docked panels. Floating
64
+ * script windows portal to `<body>` instead (`.floating-window-root`), so
65
+ * they fall outside it structurally and keep their own Escape.
66
+ *
67
+ * Pure apart from reading the event's target, so it is unit-testable.
68
+ */
69
+ export declare function shouldCloseSearchOnEscape(e: KeyboardEvent): boolean;
70
+ /**
71
+ * Which way `e` steps through search results — `1` for F3, `-1` for Shift+F3,
72
+ * or null when it isn't the search-step key at all. Mudlet's binding
73
+ * (`TConsole::setF3SearchEnabled` wires F3 to slot_searchBufferDown and
74
+ * Shift+F3 to slot_searchBufferUp).
75
+ *
76
+ * Any other modifier disqualifies it, so Ctrl+F3 / Alt+F3 / Cmd+F3 stay
77
+ * available to the browser and the OS. Pure — reads only `key` and the modifier
78
+ * flags — and shared by the bar (which steps) and the output area (which opens
79
+ * the bar when it's closed), so the two can never drift apart.
80
+ */
81
+ export declare function searchStepDirection(e: KeyboardEvent): -1 | 1 | null;
82
+ /**
83
+ * The rendered text of a whole matched line, for announcing a hit to a screen
84
+ * reader. Mudlet's `focusOnSearchResultAndAnnounce` speaks the entire buffer
85
+ * line rather than the matched fragment, so a listener hears the hit in context.
86
+ * Excludes the timestamp column, like {@link scanOutput}.
87
+ */
88
+ export declare function matchLineText(match: OutputMatch): string;
89
+ /**
90
+ * Park keyboard focus on the current hit so a screen reader's virtual cursor
91
+ * lands *on* the match in the live output — the reachable half of Mudlet's
92
+ * `focusOnSearchResultAndAnnounce`, which moves its caret to the found column.
93
+ *
94
+ * The mark is made programmatically focusable (`tabIndex = -1`), so it takes
95
+ * focus without joining the tab order. Scrolling is suppressed: `revealLine`
96
+ * has already centred the line, and the browser's own focus scroll would
97
+ * fight it. Returns false when there is no current hit to focus.
98
+ */
99
+ export declare function focusCurrentHit(container: HTMLElement): boolean;
100
+ /** Unwrap every mark this module added, restoring the pristine rendered line.
101
+ * `normalize()` re-merges the text nodes `splitText` created so a subsequent
102
+ * scan sees the same node layout it started with. */
103
+ export declare function clearHighlights(container: HTMLElement): void;
104
+ /**
105
+ * Scroll the output so `line` sits mid-viewport.
106
+ *
107
+ * Deliberately not `scrollIntoView` — that walks up and scrolls every scrollable
108
+ * ancestor, which in a docked layout drags the whole panel around. Measuring
109
+ * against the wrapper's own rect keeps the movement inside the output.
110
+ */
111
+ export declare function revealLine(container: HTMLElement, line: HTMLElement): void;
112
+ /**
113
+ * Index of the match nearest the current viewport — where a fresh search should
114
+ * start so the first Enter moves from what the player is looking at rather than
115
+ * from the top of a thousand-line scrollback. Falls back to the last (most
116
+ * recent) match when nothing is on screen.
117
+ */
118
+ export declare function indexNearViewport(container: HTMLElement, matches: OutputMatch[]): number;
119
+ /**
120
+ * Text to prefill the find box with — whatever the player has selected *in the
121
+ * output*, mirroring the browser's own find.
122
+ *
123
+ * Scoped to `container` on purpose. The command line keeps its last command
124
+ * selected after sending, and `window.getSelection()` can surface that, which
125
+ * would seed the box with the command the player just ran.
126
+ */
127
+ export declare function seedFromSelection(container: HTMLElement | null): string;
128
+ /** Locate `target` in a freshly rescanned match list, so incoming MUD output
129
+ * does not move the player's place in the results. */
130
+ export declare function findMatchIndex(matches: OutputMatch[], target: OutputMatch | null): number;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The query compiler shared by every search bar in the app (the script editor's
3
+ * `ScriptSearch`, the output find bar). Turns a raw query plus the case/regex
4
+ * toggles into something that hands back match ranges for any string.
5
+ */
6
+ /** Half-open `[start, end)` character offsets of one match. */
7
+ export type MatchRange = [number, number];
8
+ export interface SearchMatcher {
9
+ /** False when `useRegex` was set and the pattern failed to compile — the
10
+ * caller shows the input as invalid rather than reporting "no results". */
11
+ valid: boolean;
12
+ ranges: (s: string) => MatchRange[];
13
+ }
14
+ /** Compile a search bar query into a matcher honouring the case / regex flags. */
15
+ export declare function buildMatcher(pattern: string, matchCase: boolean, useRegex: boolean): SearchMatcher;
@@ -0,0 +1,3 @@
1
+ /** Return `value` delayed by `delay` ms — collapses bursts of keystrokes so an
2
+ * expensive scan runs at most once per pause in typing. */
3
+ export declare function useDebounced<T>(value: T, delay: number): T;
@@ -64,5 +64,8 @@ export declare class TtsManager {
64
64
  private advance;
65
65
  private beginUtterance;
66
66
  private setState;
67
+ /** Text of the most recent ttsSpeechStarted, so speaking over a running
68
+ * utterance can tell a genuinely new one from a repeat. */
69
+ private lastStarted;
67
70
  }
68
71
  export {};
@@ -1,7 +1,9 @@
1
1
  /** Reactive MRU command history wrapper around localStorage. History is scoped
2
2
  * to `connectionId` so each profile keeps its own. `saveSize` caps how many
3
- * entries are persisted (Mudlet's `commandLineHistorySaveSize`). */
4
- export declare function useCommandHistory(connectionId: string | null, saveSize?: number): {
3
+ * entries are persisted (Mudlet's `commandLineHistorySaveSize`); `persist`
4
+ * is Mudlet's per-command-line `setSaveCommandHistory`, which stops the writing
5
+ * without touching the history this session already has. */
6
+ export declare function useCommandHistory(connectionId: string | null, saveSize?: number, persist?: boolean): {
5
7
  history: string[];
6
8
  add: (item: string) => void;
7
9
  };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Connection ids currently open in *another* browser tab.
3
+ *
4
+ * Every open profile holds an exclusive Web Lock named `mudix:profile:<id>`
5
+ * (see profileLock.ts), and `navigator.locks.query()` lists held locks across
6
+ * all same-origin tabs — so the held profile locks are exactly the open set.
7
+ * The connection screen renders with no profile active, so this tab holds no
8
+ * profile lock of its own and every hit belongs to someone else; no
9
+ * self-filtering is needed.
10
+ *
11
+ * Polled, because Web Locks has no change event. The refresh is also driven by
12
+ * focus/visibility so returning to the launcher tab after closing a profile
13
+ * elsewhere updates immediately instead of waiting out the interval.
14
+ *
15
+ * Returns an empty set where the API is unavailable (legacy browser, insecure
16
+ * context, test env) — the same "can't tell, so don't claim anything" fallback
17
+ * the lock itself uses.
18
+ */
19
+ /** The profile ids among a `navigator.locks.query()` held-lock list. Non-profile
20
+ * locks (any other feature using Web Locks) and unnamed entries are ignored. */
21
+ export declare function openProfileIds(held: readonly {
22
+ name?: string;
23
+ }[] | undefined): Set<string>;
24
+ /** Whether two id sets hold the same members — used to skip no-op state updates. */
25
+ export declare const sameIds: (a: ReadonlySet<string>, b: ReadonlySet<string>) => boolean;
26
+ export declare function useOpenProfiles(enabled?: boolean, refreshMs?: number): Set<string>;