@fresh-editor/fresh-editor 0.3.5 → 0.3.6

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.
@@ -116,6 +116,18 @@ type TextPropertyEntry = {
116
116
  * Optional sub-range styling within this entry
117
117
  */
118
118
  inlineOverlays?: Array<InlineOverlay>;
119
+ /**
120
+ * See `TextPropertyEntry::pad_to_chars`.
121
+ */
122
+ padToChars?: number;
123
+ /**
124
+ * See `TextPropertyEntry::truncate_to_chars`.
125
+ */
126
+ truncateToChars?: number;
127
+ /**
128
+ * See `TextPropertyEntry::segments`.
129
+ */
130
+ segments?: Array<StyledSegment>;
119
131
  };
120
132
  type TsCompositeLayoutConfig = {
121
133
  /**
@@ -420,6 +432,20 @@ type BufferInfo = {
420
432
  */
421
433
  splits: number[];
422
434
  };
435
+ type WindowInfo = {
436
+ /**
437
+ * Stable session id. The base session is always `1`.
438
+ */
439
+ id: number;
440
+ /**
441
+ * User-visible label (defaults to root basename).
442
+ */
443
+ label: string;
444
+ /**
445
+ * Absolute project root.
446
+ */
447
+ root: string;
448
+ };
423
449
  type JsDiagnostic = {
424
450
  /**
425
451
  * Document URI
@@ -500,6 +526,17 @@ type ActionPopupOptions = {
500
526
  */
501
527
  actions: Array<TsActionPopupAction>;
502
528
  };
529
+ type TsLspMenuItem = {
530
+ /**
531
+ * Stable identifier used as the `action_id` in the resulting
532
+ * `action_popup_result` event (prefixed by `{plugin_id}|`).
533
+ */
534
+ id: string;
535
+ /**
536
+ * Display label shown in the popup row.
537
+ */
538
+ label: string;
539
+ };
503
540
  type FileExplorerDecoration = {
504
541
  /**
505
542
  * File path to decorate
@@ -582,6 +619,16 @@ type CreateTerminalOptions = {
582
619
  * across editor restarts.
583
620
  */
584
621
  persistent?: boolean;
622
+ /**
623
+ * Optional session id to attach the new terminal buffer to.
624
+ * Defaults to the active session at creation time. Setting this
625
+ * lets Orchestrator and similar plugins spawn a terminal *into* an
626
+ * inactive session (e.g. an agent in a worktree the user hasn't
627
+ * dived into yet). The terminal's split is created in that
628
+ * session's stashed split tree; the buffer is attached to the
629
+ * target session's membership set rather than the active one's.
630
+ */
631
+ windowId?: WindowId;
585
632
  };
586
633
  type CursorInfo = {
587
634
  /**
@@ -635,11 +682,11 @@ type OverlayOptions = {
635
682
  type OverlayColorSpec = [number, number, number] | string;
636
683
  type InlineOverlay = {
637
684
  /**
638
- * Start byte offset within the entry's text
685
+ * Start offset within the entry's text. See `unit`.
639
686
  */
640
687
  start: number;
641
688
  /**
642
- * End byte offset within the entry's text (exclusive)
689
+ * End offset within the entry's text (exclusive). See `unit`.
643
690
  */
644
691
  end: number;
645
692
  /**
@@ -650,6 +697,29 @@ type InlineOverlay = {
650
697
  * Optional properties for this sub-range (e.g., click target metadata)
651
698
  */
652
699
  properties?: Record<string, any>;
700
+ /**
701
+ * Unit for `start` / `end`. Defaults to `byte`.
702
+ */
703
+ unit?: OffsetUnit;
704
+ };
705
+ type OffsetUnit = "byte" | "char";
706
+ type StyledSegment = {
707
+ /**
708
+ * Verbatim text for this segment.
709
+ */
710
+ text: string;
711
+ /**
712
+ * When set, the host emits an `InlineOverlay` covering this
713
+ * segment's text in the final entry.
714
+ */
715
+ style?: Partial<OverlayOptions>;
716
+ /**
717
+ * Additional overlays inside this segment. Offsets are in
718
+ * the overlay's own `unit`, relative to the segment's start
719
+ * (NOT the final entry text); the host shifts them by the
720
+ * segment's position during concatenation.
721
+ */
722
+ overlays?: Array<InlineOverlay>;
653
723
  };
654
724
  type GrammarInfoSnapshot = {
655
725
  /**
@@ -682,6 +752,296 @@ type PluginAnimationKind = {
682
752
  durationMs: number;
683
753
  delayMs: number;
684
754
  };
755
+ type HintEntry = {
756
+ /**
757
+ * The key chord, e.g. `"Tab"`, `"Alt+P"`, `"Esc"`.
758
+ */
759
+ keys: string;
760
+ /**
761
+ * The human-readable label for the action.
762
+ */
763
+ label: string;
764
+ };
765
+ type ButtonKind = "normal" | "primary" | "danger";
766
+ type TreeNode = {
767
+ /**
768
+ * The pre-rendered row content (text + per-row overlays).
769
+ * The host renders this verbatim after the indent + disclosure
770
+ * prefix; plugin overlays are byte-shifted by the prefix
771
+ * length.
772
+ */
773
+ text: TextPropertyEntry;
774
+ /**
775
+ * 0-based depth — controls leading indent (`depth * 2` spaces).
776
+ */
777
+ depth: number;
778
+ /**
779
+ * When true, render a disclosure glyph (`▶` collapsed / `▼`
780
+ * expanded) and emit a hit area over it that fires the `expand`
781
+ * event. Leaf nodes (`false`) get no glyph and no expand hit;
782
+ * the row width occupies the full row.
783
+ */
784
+ hasChildren: boolean;
785
+ /**
786
+ * Per-node checkbox state. Only rendered when the parent
787
+ * `Tree` has `checkable: true`. `None` = no checkbox glyph;
788
+ * `Some(true)` = `[v]`; `Some(false)` = `[ ]`. The plugin
789
+ * owns the truth — the host fires `widget_event { event_type:
790
+ * "toggle" }` and the plugin pushes the new state back via
791
+ * `WidgetMutation::SetCheckedKeys`.
792
+ */
793
+ checked?: boolean | null;
794
+ };
795
+ type WidgetSpec = {
796
+ "kind": "row";
797
+ children: Array<WidgetSpec>;
798
+ key?: string | null;
799
+ } | {
800
+ "kind": "col";
801
+ children: Array<WidgetSpec>;
802
+ key?: string | null;
803
+ } | {
804
+ "kind": "hintBar";
805
+ entries: Array<HintEntry>;
806
+ key?: string | null;
807
+ } | {
808
+ "kind": "toggle";
809
+ checked: boolean;
810
+ label: string;
811
+ focused: boolean;
812
+ key?: string | null;
813
+ } | {
814
+ "kind": "button";
815
+ label: string;
816
+ focused: boolean;
817
+ intent: ButtonKind;
818
+ key?: string | null;
819
+ } | {
820
+ "kind": "spacer";
821
+ cols: number;
822
+ flex: boolean;
823
+ key?: string | null;
824
+ } | {
825
+ "kind": "list";
826
+ items: Array<TextPropertyEntry>;
827
+ itemKeys: Array<string>;
828
+ selectedIndex: number;
829
+ /**
830
+ * Number of rows of the panel's available height the list
831
+ * should occupy. Plugin computes from its viewport. The
832
+ * host shows up to this many items per render.
833
+ */
834
+ visibleRows: number;
835
+ /**
836
+ * Whether `Tab` / `Shift+Tab` will land focus on this
837
+ * list. Defaults to `true` (lists are normal tabbable
838
+ * widgets). Picker-style usage typically sets this to
839
+ * `false` so Tab moves between the filter input and
840
+ * the action buttons, while Up/Down on the focused
841
+ * filter still forwards to the list via host smart-key
842
+ * dispatch.
843
+ */
844
+ focusable: boolean;
845
+ key?: string | null;
846
+ } | {
847
+ "kind": "tree";
848
+ nodes: Array<TreeNode>;
849
+ itemKeys: Array<string>;
850
+ selectedIndex: number;
851
+ visibleRows: number;
852
+ /**
853
+ * Initial-only set of expanded item keys. Once the widget
854
+ * has rendered, the host's instance-state `expanded_keys`
855
+ * is authoritative; updating this field on subsequent specs
856
+ * has no effect (use `WidgetMutation::SetExpandedKeys` to
857
+ * override host state).
858
+ */
859
+ expandedKeys: Array<string>;
860
+ /**
861
+ * When true, every node with `checked: Some(_)` renders a
862
+ * `[v]` / `[ ]` glyph and emits a `toggle` hit area over
863
+ * the glyph. Click on the glyph fires `widget_event {
864
+ * event_type: "toggle", payload: { key, checked: <new> } }`;
865
+ * the plugin updates its model and pushes the new state
866
+ * back via `WidgetMutation::SetCheckedKeys`.
867
+ */
868
+ checkable: boolean;
869
+ key?: string | null;
870
+ } | {
871
+ "kind": "text";
872
+ /**
873
+ * Initial text. Spec value is read at first render only;
874
+ * instance state takes over thereafter.
875
+ */
876
+ value: string;
877
+ /**
878
+ * Initial byte-offset cursor within `value`. Negative
879
+ * (encoded as `i32` in JSON) means "no cursor" — clamped
880
+ * to `[0, value.len()]` host-side.
881
+ */
882
+ cursorByte: number;
883
+ /**
884
+ * Whether this widget has visual focus.
885
+ */
886
+ focused: boolean;
887
+ /**
888
+ * Optional label rendered before / above the editing
889
+ * region. Empty = omitted.
890
+ */
891
+ label?: string;
892
+ /**
893
+ * Placeholder shown when unfocused and `value` is empty.
894
+ */
895
+ placeholder?: string | null;
896
+ /**
897
+ * Number of visible rows of editing region. `0` falls back
898
+ * to `1` (single-line). `1` = single-line behaviour;
899
+ * `>= 2` = multi-line behaviour. See the type-level doc
900
+ * for the per-mode semantics.
901
+ */
902
+ rows: number;
903
+ /**
904
+ * Visible column width. `0` = auto-fit (single-line) or
905
+ * panel width (multi-line). When set, single-line
906
+ * head-truncates with `…` and multi-line tail-truncates
907
+ * per-line.
908
+ */
909
+ fieldWidth: number;
910
+ /**
911
+ * Single-line soft cap on visible chars after the
912
+ * `field_width` pad. `0` = no cap. Ignored when `rows > 1`.
913
+ */
914
+ maxVisibleChars: number;
915
+ /**
916
+ * Stretch the visible field to fill the available
917
+ * width of the enclosing container. Overrides
918
+ * `field_width` when set: the renderer computes
919
+ * `panel_width - label_overhead - bracket_overhead` as
920
+ * the effective visible width. Multi-line widgets
921
+ * already fill the panel width by default; this flag is
922
+ * most useful for single-line inputs inside a
923
+ * `LabeledSection` or a flexible row.
924
+ */
925
+ fullWidth: boolean;
926
+ key?: string | null;
927
+ } | {
928
+ "kind": "labeledSection";
929
+ /**
930
+ * Legend text printed in the top border. Empty = no
931
+ * legend (the top border becomes one unbroken line).
932
+ */
933
+ label: string;
934
+ /**
935
+ * The single wrapped widget. Boxed because `WidgetSpec`
936
+ * is recursive.
937
+ */
938
+ child: WidgetSpec;
939
+ /**
940
+ * When this section is a Block child of a Row, request
941
+ * `width_pct` percent of the row's `panel_width` instead
942
+ * of the equal-split default. Multiple siblings with
943
+ * `width_pct` set sum to ≤ 100; the remainder splits
944
+ * equally among siblings without an explicit width.
945
+ * Out-of-range values (0 or > 100) fall back to the
946
+ * equal-split path.
947
+ */
948
+ widthPct?: number | null;
949
+ key?: string | null;
950
+ } | {
951
+ "kind": "windowEmbed";
952
+ /**
953
+ * Numeric editor-window id, matching `WindowId(N).0`.
954
+ * `0` (or any unknown id) renders empty placeholder
955
+ * rows without dispatching the per-window render.
956
+ * `u32` rather than `u64` to keep the TS binding a
957
+ * plain `number`; window ids never exceed 4B in
958
+ * practice.
959
+ */
960
+ windowId: number;
961
+ /**
962
+ * Number of visible rows the embed should occupy.
963
+ */
964
+ rows: number;
965
+ key?: string | null;
966
+ } | {
967
+ "kind": "raw";
968
+ entries: Array<TextPropertyEntry>;
969
+ key?: string | null;
970
+ };
971
+ type WidgetAction = {
972
+ "kind": "focusAdvance";
973
+ delta: number;
974
+ } | {
975
+ "kind": "activate";
976
+ } | {
977
+ "kind": "selectMove";
978
+ delta: number;
979
+ } | {
980
+ "kind": "textInputKey";
981
+ key: string;
982
+ } | {
983
+ "kind": "textInputChar";
984
+ text: string;
985
+ } | {
986
+ "kind": "key";
987
+ key: string;
988
+ };
989
+ type WidgetMutation = {
990
+ "kind": "setValue";
991
+ widgetKey: string;
992
+ value: string;
993
+ cursorByte?: number | null;
994
+ } | {
995
+ "kind": "setChecked";
996
+ widgetKey: string;
997
+ checked: boolean;
998
+ } | {
999
+ "kind": "setSelectedIndex";
1000
+ widgetKey: string;
1001
+ index: number;
1002
+ } | {
1003
+ "kind": "setItems";
1004
+ widgetKey: string;
1005
+ items: Array<TextPropertyEntry>;
1006
+ itemKeys: Array<string>;
1007
+ } | {
1008
+ "kind": "setExpandedKeys";
1009
+ widgetKey: string;
1010
+ keys: Array<string>;
1011
+ } | {
1012
+ "kind": "setCheckedKeys";
1013
+ widgetKey: string;
1014
+ checked: boolean;
1015
+ keys: Array<string>;
1016
+ };
1017
+ type SearchTakeResult = {
1018
+ /**
1019
+ * Matches discovered since the previous take()
1020
+ */
1021
+ matches: Array<GrepMatch>;
1022
+ /**
1023
+ * Whether the producer has finished (no more matches will arrive)
1024
+ */
1025
+ done: boolean;
1026
+ /**
1027
+ * Total number of matches the producer has emitted across all batches
1028
+ * (including ones already drained on prior take() calls)
1029
+ */
1030
+ totalSeen: number;
1031
+ /**
1032
+ * Whether the producer stopped early because it hit `maxResults`
1033
+ */
1034
+ truncated: boolean;
1035
+ /**
1036
+ * Producer error, if any (e.g., invalid regex). When set, `done` is also true.
1037
+ */
1038
+ error?: string | null;
1039
+ };
1040
+ interface SearchHandle {
1041
+ searchId: number;
1042
+ take(): SearchTakeResult;
1043
+ cancel(): void;
1044
+ }
685
1045
  type AuthorityFilesystem = {
686
1046
  kind: "local";
687
1047
  };
@@ -1211,6 +1571,16 @@ interface EditorAPI {
1211
1571
  */
1212
1572
  openFile(path: string, line: number | null, column: number | null): boolean;
1213
1573
  /**
1574
+ * Open a file in the background — no focus change, no
1575
+ * active-split mutation. `windowId` defaults to the active
1576
+ * session. Setting it to an inactive session id loads the
1577
+ * file's buffer and adds it as a tab in that session's
1578
+ * stashed split tree, ready to be revealed on next dive.
1579
+ * Orchestrator uses this to populate worktree sessions with
1580
+ * preselected files.
1581
+ */
1582
+ openFileInBackground(path: string, windowId?: number): boolean;
1583
+ /**
1214
1584
  * Open a file in a specific split
1215
1585
  */
1216
1586
  openFileInSplit(splitId: number, path: string, line: number, column: number): boolean;
@@ -1750,6 +2120,24 @@ interface EditorAPI {
1750
2120
  */
1751
2121
  setPromptTitle(title: StyledText[]): boolean;
1752
2122
  /**
2123
+ * Set the footer chrome row of the floating-overlay prompt's
2124
+ * results pane. Plugins use this for hotkey-hint banners
2125
+ * (Orchestrator's `[n] new [d] dive [Esc] close` row).
2126
+ * Empty array clears the footer. Has no visible effect on
2127
+ * non-overlay prompts.
2128
+ */
2129
+ setPromptFooter(footer: StyledText[]): boolean;
2130
+ /**
2131
+ * Override the currently-highlighted suggestion row in the
2132
+ * open prompt. The editor clamps `index` to the suggestion
2133
+ * list's bounds and the renderer scrolls it into view on
2134
+ * the next frame. No-op when no prompt is open or the
2135
+ * suggestion list is empty. Typical use: re-opening a
2136
+ * picker and pre-selecting the entry the user last acted on
2137
+ * (Orchestrator highlights the active session).
2138
+ */
2139
+ setPromptSelectedIndex(index: number): boolean;
2140
+ /**
1753
2141
  * Define a buffer mode (takes bindings as array of [key, command] pairs)
1754
2142
  */
1755
2143
  defineMode(name: string, bindingsArr: string[][], readOnly?: boolean, allowTextInput?: boolean, inheritNormalBindings?: boolean): boolean;
@@ -1774,6 +2162,81 @@ interface EditorAPI {
1774
2162
  */
1775
2163
  focusSplit(splitId: number): boolean;
1776
2164
  /**
2165
+ * Create a new editor session rooted at `root`. `root` must be
2166
+ * an absolute path; relative paths are rejected by the editor
2167
+ * (logged, no session created). The new session's id is
2168
+ * reported via the `window_created` hook payload — plugins
2169
+ * that need the id should listen for that event rather than
2170
+ * polling `listWindows`.
2171
+ *
2172
+ * Returns `false` only when the IPC channel to the editor is
2173
+ * closed (editor is shutting down).
2174
+ */
2175
+ createWindow(root: string, label: string): boolean;
2176
+ /**
2177
+ * Make the session with id `id` the active one. No-op if
2178
+ * already active. Errors (id not found) are logged on the
2179
+ * editor side; the JS caller can verify by reading
2180
+ * `activeWindow()` after.
2181
+ */
2182
+ setActiveWindow(id: number): boolean;
2183
+ /**
2184
+ * Close session `id`. Refuses to close the active session or
2185
+ * the base session (id 1). Logs and no-ops on failure.
2186
+ */
2187
+ closeWindow(id: number): boolean;
2188
+ /**
2189
+ * Eagerly initialise an inactive session's per-session state
2190
+ * (file tree walk, ignore matcher, etc.) without diving.
2191
+ * No-op for the active session or unknown id.
2192
+ */
2193
+ prewarmWindow(id: number): boolean;
2194
+ /**
2195
+ * Register a `notify`-backed watch on `path`. Returns a
2196
+ * promise that resolves to a numeric `handle` (also passed
2197
+ * in subsequent `path_changed` event payloads). The promise
2198
+ * rejects on `notify` errors (path missing, kernel limit).
2199
+ *
2200
+ * `recursive` defaults to `false`. Non-recursive watches
2201
+ * cover the path itself plus its direct children for
2202
+ * directories — see `services/file_watcher.rs` for the
2203
+ * rationale.
2204
+ */
2205
+ watchPath(path: string, recursive?: boolean): Promise<number>;
2206
+ /**
2207
+ * Drop a watcher by its handle. Unknown handles are
2208
+ * silently ignored.
2209
+ */
2210
+ unwatchPath(handle: number): boolean;
2211
+ /**
2212
+ * Tell the editor that the floating-overlay prompt's
2213
+ * preview pane should render the entire split tree of
2214
+ * session `id` natively. `0` (or any unknown id) clears the
2215
+ * override and the preview falls back to the existing
2216
+ * path-based phantom-leaf renderer.
2217
+ *
2218
+ * Orchestrator calls this on each prompt-selection-change so
2219
+ * the right pane shows the highlighted session's full
2220
+ * editor UI live — splits, terminals, syntax highlighting,
2221
+ * decorations — at native rendering cost.
2222
+ */
2223
+ previewWindowInRect(id: number): boolean;
2224
+ /**
2225
+ * Clear the session-preview override. Equivalent to
2226
+ * `previewWindowInRect(0)` but reads better at call sites.
2227
+ */
2228
+ clearWindowPreview(): boolean;
2229
+ /**
2230
+ * All editor sessions, sorted by id (creation order). Always
2231
+ * non-empty (the base session is always present).
2232
+ */
2233
+ listWindows(): WindowInfo[];
2234
+ /**
2235
+ * The currently active session id. Always present in
2236
+ * `listWindows()`.
2237
+ */
2238
+ activeWindow(): number;
2239
+ /**
1777
2240
  * Set scroll position of a split
1778
2241
  */
1779
2242
  setSplitScroll(splitId: number, topByte: number): boolean;
@@ -1855,6 +2318,22 @@ interface EditorAPI {
1855
2318
  */
1856
2319
  getGlobalState(key: string): unknown;
1857
2320
  /**
2321
+ * Set per-session state on the **active** session. Same
2322
+ * shape as `setGlobalState` (write-through to snapshot +
2323
+ * dispatched to editor; null/undefined deletes), but the
2324
+ * underlying storage lives on `Session.plugin_state` and
2325
+ * swaps with the rest of session state on `setActiveWindow`.
2326
+ * Plugins that genuinely want per-project state use this;
2327
+ * Orchestrator itself uses `setGlobalState` because its session
2328
+ * list lives above session boundaries.
2329
+ */
2330
+ setWindowState(key: string, value: unknown): boolean;
2331
+ /**
2332
+ * Get per-session state from the **active** session
2333
+ * (snapshot read). `undefined` if missing.
2334
+ */
2335
+ getWindowState(key: string): unknown;
2336
+ /**
1858
2337
  * Create a scroll sync group for anchor-based synchronized scrolling
1859
2338
  */
1860
2339
  createScrollSyncGroup(groupId: number, leftSplit: number, rightSplit: number): boolean;
@@ -1879,6 +2358,12 @@ interface EditorAPI {
1879
2358
  */
1880
2359
  showActionPopup(opts: ActionPopupOptions): boolean;
1881
2360
  /**
2361
+ * Contribute (or replace, or clear) menu rows for the LSP-Servers
2362
+ * popup. Pass an empty `items` to clear this plugin's slice for
2363
+ * the given language. See `PluginCommand::SetLspMenuContributions`.
2364
+ */
2365
+ setLspMenuContributions(pluginId: string, language: string, items: TsLspMenuItem[]): boolean;
2366
+ /**
1882
2367
  * Disable LSP for a specific language
1883
2368
  */
1884
2369
  disableLspForLanguage(language: string): boolean;
@@ -1934,6 +2419,60 @@ interface EditorAPI {
1934
2419
  */
1935
2420
  getTextPropertiesAtCursor(bufferId: number): TextPropertiesAtCursor;
1936
2421
  /**
2422
+ * Mount a declarative widget panel inside a virtual buffer.
2423
+ *
2424
+ * `spec` is a `WidgetSpec` JSON tree (see fresh.d.ts for the
2425
+ * shape). The host renders the spec into the buffer; subsequent
2426
+ * `updateWidgetPanel` calls re-render the panel against the
2427
+ * previously-mounted spec.
2428
+ *
2429
+ * Returns true on successful queue, false if the IPC channel is
2430
+ * closed.
2431
+ */
2432
+ mountWidgetPanel(panelId: number, bufferId: number, specObj: unknown): boolean;
2433
+ /**
2434
+ * Replace the spec of a previously-mounted widget panel.
2435
+ * No-op if the panel id was never mounted.
2436
+ */
2437
+ updateWidgetPanel(panelId: number, specObj: unknown): boolean;
2438
+ /**
2439
+ * Unmount a previously-mounted widget panel. The plugin retains
2440
+ * ownership of the underlying virtual buffer.
2441
+ */
2442
+ unmountWidgetPanel(panelId: number): boolean;
2443
+ /**
2444
+ * Route a keystroke / nav action to the panel's focused widget.
2445
+ *
2446
+ * `action` is a `WidgetAction` JSON object — see fresh.d.ts for
2447
+ * the shapes (`{kind: "focusAdvance", delta: 1}` etc.). Plugin's
2448
+ * `defineMode` bindings dispatch into here for keys handled by
2449
+ * the widget layer; the host runtime acts on the panel's
2450
+ * currently focused widget and fires `widget_event` as
2451
+ * appropriate.
2452
+ */
2453
+ widgetCommand(panelId: number, actionObj: unknown): boolean;
2454
+ /**
2455
+ * Apply a targeted mutation to a mounted widget panel — the
2456
+ * IPC fast path. Use instead of `updateWidgetPanel` when the
2457
+ * model change touches a single widget; the host applies the
2458
+ * mutation in place without re-transmitting the full spec.
2459
+ * See `WidgetMutation` in fresh.d.ts for the shapes.
2460
+ */
2461
+ widgetMutate(panelId: number, mutationObj: unknown): boolean;
2462
+ /**
2463
+ * Mount a declarative widget panel as a centered floating
2464
+ * overlay (not bound to any virtual buffer).
2465
+ */
2466
+ mountFloatingWidget(panelId: number, specObj: unknown, widthPct: number, heightPct: number): boolean;
2467
+ /**
2468
+ * Replace the spec of the currently-mounted floating widget panel.
2469
+ */
2470
+ updateFloatingWidget(panelId: number, specObj: unknown): boolean;
2471
+ /**
2472
+ * Tear down the floating widget panel.
2473
+ */
2474
+ unmountFloatingWidget(panelId: number): boolean;
2475
+ /**
1937
2476
  * Spawn a process (async, returns request_id)
1938
2477
  */
1939
2478
  spawnProcess(command: string, args: string[], cwd?: string): ProcessHandle<SpawnResult>;
@@ -2006,18 +2545,17 @@ interface EditorAPI {
2006
2545
  */
2007
2546
  grepProject(pattern: string, fixedString: boolean | null, caseSensitive: boolean | null, maxResults: number | null, wholeWords: boolean | null): Promise<GrepMatch[]>;
2008
2547
  /**
2009
- * Streaming project-wide grep search
2010
- * Returns a thenable with a searchId property. The progressCallback is called
2011
- * with batches of matches as they are found.
2548
+ * Begin a streaming project-wide search and return a `SearchHandle`.
2549
+ * The producer (host) writes matches at full speed into shared state;
2550
+ * the consumer drains via `handle.take()` at its own cadence. Call
2551
+ * `handle.cancel()` to abort.
2012
2552
  */
2013
- grepProjectStreaming(pattern: string, opts?: {
2553
+ beginSearch(pattern: string, opts?: {
2014
2554
  fixedString?: boolean;
2015
2555
  caseSensitive?: boolean;
2016
2556
  maxResults?: number;
2017
2557
  wholeWords?: boolean;
2018
- }, progressCallback?: (matches: GrepMatch[], done: boolean) => void): PromiseLike<GrepMatch[]> & {
2019
- searchId: number;
2020
- };
2558
+ }): SearchHandle;
2021
2559
  /**
2022
2560
  * Replace matches in a file's buffer (async)
2023
2561
  * Opens the file if not already in a buffer, applies edits via the buffer model,
@@ -2049,6 +2587,15 @@ interface EditorAPI {
2049
2587
  */
2050
2588
  closeTerminal(terminalId: number): boolean;
2051
2589
  /**
2590
+ * Send `signal` ("SIGTERM" / "SIGKILL" / "SIGINT" / "SIGHUP")
2591
+ * to every process group the window `id` is tracking. The
2592
+ * window's authority decides delivery; this is the
2593
+ * canonical entry point for "stop everything this window
2594
+ * owns" rather than reaching at the terminal level. Returns
2595
+ * `false` only when the command channel is closed.
2596
+ */
2597
+ signalWindow(id: number, signal: string): boolean;
2598
+ /**
2052
2599
  * Force refresh of line display
2053
2600
  */
2054
2601
  refreshLines(bufferId: number): boolean;
@@ -2321,6 +2868,56 @@ interface HookEventMap {
2321
2868
  action: string;
2322
2869
  }[];
2323
2870
  };
2871
+ // ── PTY terminals (see crates/fresh-core/src/hooks.rs) ───────────────────
2872
+ terminal_output: {
2873
+ terminal_id: number;
2874
+ last_line: string;
2875
+ };
2876
+ terminal_exit: {
2877
+ terminal_id: number;
2878
+ exit_code: number | null;
2879
+ };
2880
+ // ── filesystem watching (watchPath plugin API) ────────────────────────────
2881
+ path_changed: {
2882
+ handle: number;
2883
+ path: string;
2884
+ /** "modify" | "create" | "delete" | "rename" | "other" */
2885
+ kind: string;
2886
+ };
2887
+ // ── editor sessions (Orchestrator; see orchestrator-sessions-design.md) ────────
2888
+ window_created: {
2889
+ id: number;
2890
+ label: string;
2891
+ root: string;
2892
+ };
2893
+ window_closed: {
2894
+ id: number;
2895
+ };
2896
+ active_window_changed: {
2897
+ previous_id: number | null;
2898
+ active_id: number;
2899
+ };
2900
+ // ── widget runtime ───────────────────────────────────────────────────────
2901
+ /**
2902
+ * A widget mounted via `editor.mountWidgetPanel` emitted a
2903
+ * semantic event. Fired when the host's hit-test routes a mouse
2904
+ * click to a `Toggle` / `Button` widget node within a mounted
2905
+ * widget panel. See `docs/internal/plugin-widget-library-design.md`.
2906
+ *
2907
+ * Routing is by `panel_id` (matches the id the plugin allocated
2908
+ * at mount time) plus `widget_key` (the stable `key` set on the
2909
+ * widget spec node, or empty when the spec did not assign one).
2910
+ *
2911
+ * `event_type` and `payload` shapes:
2912
+ * * Toggle: `event_type = "toggle"`, `payload = { checked: <new> }`.
2913
+ * * Button: `event_type = "activate"`, `payload = {}`.
2914
+ */
2915
+ widget_event: {
2916
+ panel_id: number;
2917
+ widget_key: string;
2918
+ event_type: string;
2919
+ payload: Record<string, unknown>;
2920
+ };
2324
2921
  }
2325
2922
  /**
2326
2923
  * Typed overloads of `editor.on` / `editor.off`.