dsh-model-think-level 1.0.6 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -143,6 +143,22 @@ export declare function ladderIntent(nextThinking: boolean, built: EffortIntent,
143
143
  * @returns the draft, or undefined when it holds no field at all.
144
144
  */
145
145
  export declare function compatDraftOf(compat: CompatSuggestion | undefined): CompatSuggestion | undefined;
146
+ /**
147
+ * The compat advice three MANUAL field values spell, from the values handed in.
148
+ *
149
+ * The editor's three compat controls are its own edit surface, so the handler
150
+ * that just changed one must report the value it computed: re-reading the
151
+ * component state there reports the PRE-update field, because React commits
152
+ * that state after the event — the queued write then carries the previous
153
+ * advice while the control already shows the new one.
154
+ * @param parts - the three field values as they will read after the event.
155
+ * @returns the draft, or undefined when all three are empty.
156
+ */
157
+ export declare function manualCompatOf(parts: {
158
+ budgetField: string;
159
+ priorityText: string;
160
+ maxOutput: string;
161
+ }): CompatSuggestion | undefined;
146
162
  /**
147
163
  * Assemble one complete pending write from the parts a caller resolved: the
148
164
  * compat merge (the applied suggestion's block with the caller's own fields on
@@ -47,6 +47,21 @@ export declare function createModelOrderState(): ModelOrderState;
47
47
  * `.modelList { display: flex; flex-direction: column; gap: 8px }` lays out.
48
48
  */
49
49
  export declare function modelListsOf(root: HTMLElement): HTMLElement[];
50
+ /**
51
+ * Whether a mutation burst added or removed model ROWS.
52
+ *
53
+ * The official editor keys its model rows by ARRAY INDEX (`models.map((model,
54
+ * index) => <ModelRow ... />, index)`), so deleting one model commits as the
55
+ * LAST row element leaving the list and every row after the deleted one
56
+ * swapping its contents INSIDE its existing element. Those elements keep the
57
+ * CSS `order` this module wrote for the model that moved out of them: the
58
+ * list paints its tail shifted into the wrong slots, and re-sorts itself
59
+ * when the debounced scan finally corrects the order a frame or more later —
60
+ * the "delete one model and the whole list flashes" report. The observer
61
+ * answers that burst with an order pass in its own microtask, before the
62
+ * first painted frame; the same reasoning that made the tab pass synchronous.
63
+ */
64
+ export declare function modelListMutation(records: readonly MutationRecord[]): boolean;
50
65
  /**
51
66
  * One pass over the page: name every model row in every provider editor, mount a
52
67
  * grip in its grid, and place the rows in the order the user stored for that
@@ -64,5 +79,3 @@ export declare function reconcileModelOrder(root: HTMLElement, deps: ModelOrderD
64
79
  * persisting that order — on behalf of a fiber that no longer exists.
65
80
  */
66
81
  export declare function teardownModelOrder(state: ModelOrderState): void;
67
- /** The stored sequence for one provider, for callers that only need to read it. */
68
- export declare function storedModelOrder(provider: string): readonly string[];
@@ -103,6 +103,8 @@ export interface InjectorDeps {
103
103
  * editing" is the only moment this side may safely write the document.
104
104
  */
105
105
  onIdle?(): void;
106
+ /** Wake the debounced scan after provider-wide adaptation stages a row. */
107
+ onPendingChanged?(): void;
106
108
  /**
107
109
  * Arm a timed retry after a failed idle pass. The backoff clock only helps
108
110
  * if something wakes the injector when it expires; a settled page has no DOM
@@ -414,11 +416,94 @@ export declare function inputValueByLabel(card: HTMLElement, labels: readonly st
414
416
  * @param state - mutable scan state: the adapting guard and the held ledger.
415
417
  * @param deps - the injection dependencies.
416
418
  * @param route - the provider route whose models are adapted.
419
+ * @param state - mutable scan state: the adapting guard and the held ledger.
420
+ * @param deps - the injection dependencies.
421
+ * @param route - the provider route whose models are adapted.
422
+ * @param visibleRows - the models the route's card shows on screen, from the
423
+ * scan's own rows (id and display name). The document cannot know the rows
424
+ * the card holds but has not saved yet, so the pass extends its walk with
425
+ * them: their holds land with the card's own Save, exactly like a row's own
426
+ * auto-adapt.
417
427
  * @returns what the pass did, so the seat can say something true about the
418
428
  * click: how many models it held an adaptation for, how many had no
419
429
  * suggestion, and -- when nothing could be adapted at all -- why.
420
430
  */
421
- export declare function adaptEveryModel(state: ScanState, deps: InjectorDeps, route: string): Promise<AutoAdaptReport>;
431
+ export declare function adaptEveryModel(state: ScanState, deps: InjectorDeps, route: string, visibleRows?: ReadonlyArray<{
432
+ id: string;
433
+ name: string;
434
+ }>): Promise<AutoAdaptReport>;
435
+ /**
436
+ * Hand a viewport delta back to whichever container actually moved the card.
437
+ *
438
+ * The card's distance from the top of the viewport is the sum of every scroll
439
+ * offset between it and the document, so the correction belongs to the
440
+ * container that really moved: usually the nearest scrollable one, but a
441
+ * deletion near the bottom of a long page moves the PAGE's offset instead. Each
442
+ * container is asked for as much of the delta as it can still give, the ones
443
+ * this window has already written first and the rest from the inside out, and
444
+ * every container that answers is watched from then on. A container whose
445
+ * offset is no longer the one this loop left is being scrolled by somebody
446
+ * else, and is skipped rather than fought.
447
+ */
448
+ export declare function absorbDelta(chain: readonly HTMLElement[], watched: Set<HTMLElement>, lastSet: Map<HTMLElement, number>, delta: number): void;
449
+ /**
450
+ * Start recording where a click inside a model list leaves its scroll host.
451
+ *
452
+ * Document-level and capture-phase, like the stand-down listeners a re-pin
453
+ * window installs: the position has to be read before the official handler
454
+ * (and React's commit behind it) can shorten the list. Reference counted, so
455
+ * a reload that lands a second fiber over a page that already has one does not
456
+ * take the listeners away from the first.
457
+ */
458
+ export declare function watchModelListOffsets(): void;
459
+ /**
460
+ * Stop watching, drop every blank, and forget the last position.
461
+ *
462
+ * The blanks are elements this plugin owns inside the official page: a fiber
463
+ * that goes away must not leave a scroll container holding a gap nothing
464
+ * maintains. Removing one can move the view (the range it held goes with it),
465
+ * which is the right thing to do to a page whose watcher is gone.
466
+ */
467
+ export declare function unwatchModelListOffsets(): void;
468
+ /**
469
+ * Hold every open provider card's top across a model row burst, and hold the
470
+ * scroll host's own range open while it does.
471
+ *
472
+ * The official list keys rows by array index, so a deletion removes the LAST
473
+ * element and re-fills the survivors: the card loses a row's height INSIDE
474
+ * itself, above whatever its tail holds. Left to its anchoring the browser
475
+ * keeps a node near the hole visually stable instead — it buys the shrink
476
+ * back by moving the whole view, and deleting a model slides the page DOWN
477
+ * rather than letting the content below the list contract upward into the
478
+ * hole it opened. Suppressing anchoring from the burst's own microtask (the
479
+ * injector runs this before the browser has laid the deletion out) holds the
480
+ * card's top where it was: the header does not move, and the tail rises.
481
+ *
482
+ * Anchoring is only half of it, and the half that shows up when the list is
483
+ * scrolled near its end. There the shrink also takes the scroll RANGE away,
484
+ * and the browser clamps the offset down to the new maximum on the first
485
+ * flush that sees the shorter content — which is this very microtask.
486
+ * Measured on the live page (list scrolled to its end, eight deletions): every
487
+ * step moved the card's top by exactly the amount the clamp took, 54px per
488
+ * row, and no anchor suppression could stop it because nothing was anchoring:
489
+ * the range was simply gone. {@link holdListSlack} is what gives it back, from
490
+ * the position the row's own click recorded before the change.
491
+ *
492
+ * The window also covers the burst's second height change. The re-filled rows
493
+ * leave the mounted editor of the model that moved out of them STALE, and the
494
+ * debounced scan re-renders (or mounts) it a frame or two after the deletion
495
+ * itself — the same two-stage change {@link pinCardTop} was built for, now
496
+ * opened by the mutation rather than by a click.
497
+ *
498
+ * The card vocabulary is the same three flavours {@link wireEditRepin} knows:
499
+ * a deletion inside a NEW provider's setup card (or the add card) has no
500
+ * rowCard to climb into at all, and a pin that names only saved providers
501
+ * silently skips the very card the user is editing.
502
+ *
503
+ * @param root - the panel to search for model lists.
504
+ * @param windowMs - how long each found card stays pinned.
505
+ */
506
+ export declare function pinModelListCards(root: HTMLElement, windowMs?: number): void;
422
507
  /**
423
508
  * Scan the settings DOM for official model rows and reconcile the injected
424
509
  * editors. Idempotent: existing editors are left alone, new disclosures get
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The folded group of switched-off providers on the official Models page.
3
+ *
4
+ * A switched-off provider is not deleted: its row is the only place it can be
5
+ * switched back on, and its configuration is still the user's. It does not
6
+ * belong in the list the user reads either — a page whose built-in providers all
7
+ * stay in the way makes the two or three real ones hard to find. So the rows of
8
+ * the switched-off providers fold away behind one control at the end of the
9
+ * list, and that control says how many are folded and unfolds them again.
10
+ *
11
+ * Unfolding does not scatter them back among the providers that are on: the
12
+ * folded rows are ORDERED behind the control, so the group reads as one group
13
+ * under its own control. The order comes from the plugin's stylesheet
14
+ * (`.bre-row-folded`, `.bre-provider-disabled-seat`), where `!important` outranks
15
+ * the inline orders the drag passes write — this module therefore never has to
16
+ * fight them for the attribute, and the rows are never moved or rebuilt: their
17
+ * DOM slot, which the drag passes read back out of the DOM, stays exactly where
18
+ * the host put them.
19
+ *
20
+ * The only node this module inserts is its own control, as the list's last item:
21
+ * a control outside the list could not head rows inside it, and the list is a
22
+ * flex column the host itself lays out. Both the class writes and the insertion
23
+ * are guarded on the state they would produce: a scan that changes nothing
24
+ * writes nothing, because every write here would otherwise feed the observer
25
+ * that scheduled the scan.
26
+ *
27
+ * The folded state is per session and deliberately not stored. The point of the
28
+ * group is that the switched-off providers are out of the way, and a reload
29
+ * showing them folded again is the behaviour that was asked for.
30
+ *
31
+ * @module dsh-model-think-level/client/injection/provider-disabled-group
32
+ */
33
+ import type { HostLabels } from './models-page-editor.js';
34
+ /** Per-scan bookkeeping (one instance lives in `models-page.ts`). */
35
+ export interface ProviderDisabledGroupState {
36
+ /** Whether the folded rows are currently unfolded. */
37
+ expanded: boolean;
38
+ }
39
+ export interface ProviderDisabledGroupDeps {
40
+ /** The plugin's own translator, for the control's label and tooltip. */
41
+ t: (key: string, params?: Record<string, string | number>) => string;
42
+ /** The official `settings.models` label sets, resolved fresh on every scan. */
43
+ labels: () => HostLabels;
44
+ }
45
+ export declare function createProviderDisabledGroupState(): ProviderDisabledGroupState;
46
+ /**
47
+ * Fold the switched-off providers away — or put them back when the control says
48
+ * so — and keep the control's own label, count and position in step.
49
+ *
50
+ * Idempotent: a row already folded stays folded, and the control is only ever
51
+ * written when a value genuinely changed, so a scan never mutates the page on
52
+ * behalf of a state that is already on it.
53
+ */
54
+ export declare function reconcileProviderDisabledGroup(root: HTMLElement, deps: ProviderDisabledGroupDeps, state: ProviderDisabledGroupState): void;
55
+ /** Remove the control and unfold every row it hid. */
56
+ export declare function teardownProviderDisabledGroup(): void;
@@ -62,6 +62,13 @@ export interface DragSession {
62
62
  frame: number | undefined;
63
63
  dropped: boolean;
64
64
  }
65
+ /** What a list looked like the last time a scan read it. */
66
+ export interface ListSighting {
67
+ /** The ids in the sequence they screened, in screen order. */
68
+ readonly ids: readonly string[];
69
+ /** The row elements that were on screen. */
70
+ readonly rows: readonly Element[];
71
+ }
65
72
  /** Per-scan bookkeeping; one instance per module that owns a list. */
66
73
  export interface DragState {
67
74
  /** The drag sources already carrying listeners. */
@@ -70,6 +77,16 @@ export interface DragState {
70
77
  readonly lists: WeakSet<Element>;
71
78
  /** The insertion line; created on the first drag and reused until teardown. */
72
79
  line: HTMLElement | undefined;
80
+ /**
81
+ * What each list looked like on the last pass, by list element.
82
+ *
83
+ * Needed because a removal cannot be told from a rename or an addition by
84
+ * looking at the current DOM: when the host keys its rows by index, deleting
85
+ * a model leaves every row below it drawn with the next model, so the rows on
86
+ * screen are the same elements holding the same `order`s they always had. See
87
+ * {@link arrangement} for what that does to the screen order.
88
+ */
89
+ readonly sighted: WeakMap<Element, ListSighting>;
73
90
  drag: DragSession | undefined;
74
91
  }
75
92
  export declare function createDragState(): DragState;
@@ -78,9 +95,11 @@ export declare function rowElements(parent: HTMLElement): HTMLElement[];
78
95
  /** The rows in the sequence they currently screen. */
79
96
  export declare function visualRows(list: HTMLElement): HTMLElement[];
80
97
  /**
81
- * Put `sequence` on screen: each row it names takes the slot a named row
82
- * already held, and a row it does not name keeps the slot it has. Every child
83
- * gets an explicit `order` once anything moves, so no two rows share a slot.
98
+ * Put `sequence` on screen against the order the rows currently screen in.
99
+ *
100
+ * Only for a gesture in flight: the pointer's arrangement IS the screen order,
101
+ * so that is the sequence the rows are placed against. Everything outside a
102
+ * gesture goes through {@link applyIdOrder}, which places against the DOM.
84
103
  */
85
104
  export declare function placeRows(list: HTMLElement, sequence: readonly HTMLElement[]): void;
86
105
  /**
@@ -96,6 +115,14 @@ export declare function orderedIds(list: HTMLElement, idOf: (row: HTMLElement) =
96
115
  /**
97
116
  * Place the rows in `ids` order. Rows without a known id are left exactly where
98
117
  * they are: a row we cannot name is a row we must not move.
118
+ *
119
+ * The slots come from the DOM order. React owns the child list and keeps it
120
+ * compacted and truthful across a removal, while the inline `order`s still on
121
+ * the survivors describe where those models were before it. Taking the slots
122
+ * from the DOM and the sequence from {@link arrangement} is what lets the two
123
+ * meet: the rows land in the order the user had, in the slots the host now
124
+ * genuinely has, so the gap a deletion left closes instead of leaving a row
125
+ * behind the end of the list.
99
126
  */
100
127
  export declare function applyIdOrder(list: HTMLElement, ids: readonly string[], idOf: (row: HTMLElement) => string | undefined): void;
101
128
  /** Hand every row back the inline geometry a drag wrote on it. */
@@ -121,6 +148,21 @@ export declare function dropLineTop(row: HTMLElement, after: boolean): number;
121
148
  export declare function makeGhost(row: HTMLElement, label?: string): HTMLElement;
122
149
  /** The nearest ancestor that actually scrolls, for the edge auto-scroll. */
123
150
  export declare function scrollHostOf(node: HTMLElement): HTMLElement | undefined;
151
+ /**
152
+ * Every container that can hold `node` in place, nearest first.
153
+ *
154
+ * {@link scrollHostOf} answers the one container an edge-drag has to drive. A
155
+ * window that CORRECTS the view needs the rest of the chain as well: the offset
156
+ * a change takes away is held by whichever ancestor the browser is using as the
157
+ * scrolling box, and the nearest container is only the innermost candidate for
158
+ * that — an ancestor the nearest one delegates to can be the one that moved.
159
+ *
160
+ * The page's own scroller is the last candidate even though it never declares
161
+ * an overflow of its own: a long settings page scrolls there when nothing
162
+ * inside it does, and a row deletion near the bottom of that page takes its
163
+ * range away exactly like a deletion inside a small panel would.
164
+ */
165
+ export declare function scrollChainOf(node: HTMLElement): HTMLElement[];
124
166
  /** The pixels per frame `host` should scroll while the pointer is at `clientY`. */
125
167
  export declare function edgeScrollStep(host: HTMLElement, clientY: number): number;
126
168
  /**
@@ -148,6 +148,9 @@ export declare const en: {
148
148
  providerReorderHint: string;
149
149
  providerToggleAria: string;
150
150
  providerToggleHint: string;
151
+ providerDisabledGroupShow: string;
152
+ providerDisabledGroupHide: string;
153
+ providerDisabledGroupHint: string;
151
154
  providerRemoveDisabled: string;
152
155
  providerRemoveLocked: string;
153
156
  modelReorderAria: string;
@@ -54,5 +54,3 @@ export declare function resolveModelOrder(provider: string, ids: readonly string
54
54
  export declare function orderModelEntries<T extends {
55
55
  readonly id: string;
56
56
  }>(entries: readonly T[], preferred: readonly string[]): readonly T[];
57
- /** Drop everything one provider stored (used when its last model is removed). */
58
- export declare function clearModelOrder(provider: string): void;