@loomweaver/shell 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2280 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, Provider, Signal, EnvironmentProviders, Type, Injector, WritableSignal } from '@angular/core';
3
+ import { LwButtonVariant, LwButtonSize, NotificationKind, NotificationAction, NotificationInput, DialogRef, DialogTone, DialogSize, ConfirmOptions, AlertOptions, PromptOptions, OpenOptions, ProgressOptions, ProgressHandle, BarItem, RailItem, View, ContentRoute, Command, MenuItem, Disposable, SettingsSection, PaneArea, PaneAreaBase, PaneColumnArea, PaneRowArea, PaneTabArea, AuthSnapshot, AccessRequirement, ViewAction, ActiveContent, OpenTabInput, MenuContext, CommandArguments, BarSlot, Plugin, Capability, InvocableCommand, CommandOutcome } from '@loomweaver/plugin-sdk';
4
+ export { AlertOptions, BarButtonItem, BarComponentItem, BarItem, BarSlot, ConfirmOptions, DialogButton, DialogRef, DialogSize, DialogTone, Disposable, LwButtonSize, LwButtonVariant, NotificationAction, NotificationInput, NotificationKind, OpenOptions, Plugin, PluginContext, PluginHost, PluginManifest, PluginSession, PluginUi, ProgressHandle, ProgressOptions, PromptOptions, RailItem, RequireConfirmation, SelectOption, SettingButton, SettingComponent, SettingControl, SettingRow, SettingSelect, SettingSlider, SettingText, SettingToggle, SettingsSection, UiMenuItem, View, ViewAction } from '@loomweaver/plugin-sdk';
5
+ import { Routes } from '@angular/router';
6
+
7
+ /** Where a region docks in the border topology. */
8
+ type DockPosition = 'top' | 'bottom' | 'left' | 'right' | 'center';
9
+ /** Region anatomy types. */
10
+ type RegionType = 'bar' | 'rail' | 'panel' | 'content';
11
+ /** One region the distribution places into the border frame. */
12
+ interface LayoutRegion {
13
+ /** Stable id (slot targeting, collapse state, ordering). */
14
+ readonly id: string;
15
+ readonly type: RegionType;
16
+ readonly dock: DockPosition;
17
+ }
18
+ /** A distribution's declared base layout — the Core renders it (declarative, Stufe A). */
19
+ interface ShellLayout {
20
+ readonly regions: readonly LayoutRegion[];
21
+ }
22
+ /**
23
+ * Bare default: a top Bar (header) over the Content-Area, with a status Bar under it.
24
+ *
25
+ * The status bar is not decoration — the shell's own default contributions include one that docks
26
+ * there, and a default aimed at a region the default layout omits renders nothing and reports
27
+ * nothing. Every region a shell default targets belongs here; `tools/check-region-ids.mjs` fails
28
+ * the build when one does not.
29
+ */
30
+ declare const DEFAULT_LAYOUT: ShellLayout;
31
+ /** Active layout. Defaults to {@link DEFAULT_LAYOUT}; a distribution overrides it. */
32
+ declare const SHELL_LAYOUT: InjectionToken<ShellLayout>;
33
+ /** A distribution declares its base layout with this provider. */
34
+ declare function provideLayout(layout: ShellLayout): Provider;
35
+
36
+ declare class ViewportService {
37
+ private readonly document;
38
+ readonly compact: Signal<boolean>;
39
+ private watchCompact;
40
+ static ɵfac: i0.ɵɵFactoryDeclaration<ViewportService, never>;
41
+ static ɵprov: i0.ɵɵInjectableDeclaration<ViewportService>;
42
+ }
43
+
44
+ /**
45
+ * Opening a surface in its own browser window, and knowing whether this window **is** one.
46
+ *
47
+ * A pop-out boots the same app from a `/popout/…` URL and renders exactly one surface — no rail, no
48
+ * sidebars, no pane tree — so the main window stays the only writer of the layout keys. The mode is
49
+ * read from the location once at startup: a pop-out never becomes a main window, and the shell needs
50
+ * the answer before it renders anything.
51
+ */
52
+ declare class PopoutService {
53
+ /** Whether this browser window is a pop-out. Fixed for the window's lifetime. */
54
+ readonly active: boolean;
55
+ private readonly document;
56
+ private readonly dialogs;
57
+ private readonly transloco;
58
+ constructor();
59
+ /**
60
+ * Opens `paneTarget` — a `view:<viewId>` descriptor or a content-route path — in a new browser
61
+ * window. The tab it came from **stays** where it is: this duplicates, it does not move. If the
62
+ * pop-up blocker swallows the window, the user gets a dialog whose button is a fresh gesture that
63
+ * practically always gets through.
64
+ */
65
+ open(paneTarget: string): void;
66
+ private tryOpen;
67
+ static ɵfac: i0.ɵɵFactoryDeclaration<PopoutService, never>;
68
+ static ɵprov: i0.ɵɵInjectableDeclaration<PopoutService>;
69
+ }
70
+
71
+ /** Layout host: renders the declared regions into the border topology. */
72
+ declare class Shell {
73
+ private readonly layout;
74
+ private readonly panels;
75
+ protected readonly viewport: ViewportService;
76
+ protected readonly popout: PopoutService;
77
+ protected readonly topBars: LayoutRegion[];
78
+ protected readonly bottomBars: LayoutRegion[];
79
+ protected readonly leftRails: LayoutRegion[];
80
+ protected readonly leftPanels: LayoutRegion[];
81
+ protected readonly rightPanels: LayoutRegion[];
82
+ protected readonly rightRails: LayoutRegion[];
83
+ protected readonly hasContent: boolean;
84
+ protected readonly leftFloating: i0.Signal<LayoutRegion[]>;
85
+ protected readonly rightFloating: i0.Signal<LayoutRegion[]>;
86
+ protected readonly leftEdgeBorder: i0.Signal<boolean>;
87
+ protected readonly rightEdgeBorder: i0.Signal<boolean>;
88
+ protected readonly leftRailDivider: i0.Signal<boolean>;
89
+ protected readonly rightRailDivider: i0.Signal<boolean>;
90
+ private readonly theme;
91
+ private readonly fontScale;
92
+ protected panelCollapsed(panel: LayoutRegion): boolean;
93
+ protected isOverlayOpen(regionId: string): boolean;
94
+ protected anyOverlayOpen(): boolean;
95
+ protected closeOverlay(): void;
96
+ private floating;
97
+ private railDivider;
98
+ private edgeHasBody;
99
+ private regionsAt;
100
+ static ɵfac: i0.ɵɵFactoryDeclaration<Shell, never>;
101
+ static ɵcmp: i0.ɵɵComponentDeclaration<Shell, "lw-shell", never, {}, {}, never, never, true, never>;
102
+ }
103
+
104
+ type RetentionDefault = 'destroy' | 'retain';
105
+
106
+ /** Options a distribution can pass to {@link provideShell}. */
107
+ interface ShellOptions {
108
+ /**
109
+ * Contribution ids to hide — a distribution drops a default it does not want (e.g.
110
+ * `['shell.language']`). A **lasting** filter, not a one-time delete: an id a plugin
111
+ * registers later stays hidden too. To *replace* a default instead of hiding it, register
112
+ * your own contribution with the same id (last-in wins) and do **not** omit it.
113
+ *
114
+ * Covers **every** contribution kind. Chrome — commands, views, bar items, rail items — is
115
+ * addressed by bare id. The other kinds carry a **prefix**, because ids from different kinds may
116
+ * coincide and there is exactly one shared omit set: an unprefixed omit stays chrome-only and never
117
+ * silently takes a same-named contribution of another kind with it (`shell.language`, for one, is
118
+ * both a top-bar item and a settings row).
119
+ *
120
+ * - `menu:<commandId>` — one menu entry, leaving the command itself (palette/shortcut) alive.
121
+ * - `setting:<id>` — a whole section (`'setting:shell.permissions'`) or a single row
122
+ * (`'setting:shell.textSize'`); a section left without rows disappears.
123
+ * - `route:<surfaceId>` — a routable surface's content route. It leaves the tab strip,
124
+ * the auto-open on deep-link and the pane target picker; its URL renders a neutral "not available
125
+ * here" placeholder rather than falling back to home, so a shared deep-link explains itself.
126
+ * Addressed by the surface **id**, while a route is *overridden* by its `path` (registering your
127
+ * own surface on the same path wins) — two handles, deliberately, for two different operations.
128
+ */
129
+ readonly omit?: readonly string[];
130
+ /**
131
+ * Whether to register the application's service worker — `true` by default, and inert in dev
132
+ * either way. The shell owns the registration so that the update badge, the update
133
+ * toast and `ctx.host.checkForUpdate()` work in a distribution that only ships the build
134
+ * artefacts; you do **not** add `provideServiceWorker` yourself.
135
+ *
136
+ * Pass `false` when your build emits no `ngsw-worker.js` (no `serviceWorker` option in the build
137
+ * target). Registration would otherwise 404 in production and log a failure. Nothing else
138
+ * changes: `UpdateService` injects `SwUpdate` optionally, so it simply reports
139
+ * {@link UpdateService.enabled} as `false` and never offers an update.
140
+ *
141
+ * `@angular/service-worker` stays a peer dependency regardless — the opt-out removes the
142
+ * registration, not the import.
143
+ */
144
+ readonly serviceWorker?: boolean;
145
+ /**
146
+ * The app-wide retention default for **hidden** surfaces: `'destroy'` (the default) destroys a
147
+ * hidden, clean surface, so state that must survive belongs in `VIEW_STATE`; `'retain'` keeps
148
+ * every hidden instance alive at the price of memory growing with every surface ever shown. A
149
+ * surface's own `retain: 'always' | 'never'` declaration wins over this default. `iframe` and
150
+ * `container` surfaces are always rebuilt regardless.
151
+ *
152
+ * This is a storage policy for the developer, not a capability the user can see, which is why it
153
+ * lives here rather than in `provideShellFeatures`.
154
+ */
155
+ readonly retention?: RetentionDefault;
156
+ }
157
+ /**
158
+ * Wires the neutral shell host (theme, i18n, icons, error handling) for a
159
+ * distribution. A distribution adds its own router and product identity; the
160
+ * bare platform falls back to the LoomWeaver identity.
161
+ */
162
+ declare function provideShell(options?: ShellOptions): (Provider | EnvironmentProviders)[];
163
+
164
+ /**
165
+ * Gestures the content area offers on tabs and panes. Each field takes the **affordance and the
166
+ * gesture**: switching one off removes the button, the drag target and the keyboard shortcut alike,
167
+ * so a capability can never come back through a second door.
168
+ */
169
+ interface ContentFeatures {
170
+ /** Closing a tab: the × affordance, the `Delete` key and the close entries of the tab menu. */
171
+ readonly close: boolean;
172
+ /** Pinning a tab: the menu entry, the pin affordance and the pin step of the double-click cycle. */
173
+ readonly pin: boolean;
174
+ /**
175
+ * The double-click cycle on a tab (preview → keep → pin → unpin). The first step is the one users
176
+ * arrive expecting, and a preview tab says so itself — its tooltip reads "double-click to keep
177
+ * open", which is why the hint is drawn only while this is on. Switch it off and the tooltip drops
178
+ * the promise; every step of the cycle stays reachable from the tab's context menu either way.
179
+ */
180
+ readonly escalate: boolean;
181
+ /** Dragging a tab into another pane, and onto an empty pane. */
182
+ readonly moveTabs: boolean;
183
+ /** The single reused italic **preview** slot (VS-Code "Preview Editors"). With it off, `openContentTab({ preview: true })` opens a permanent tab. */
184
+ readonly preview: boolean;
185
+ /** The "+" button that opens the new-tab picker. */
186
+ readonly newTab: boolean;
187
+ /** Splitting a pane horizontally: the toolbar button, the left/right drop edges and `mod+\`. */
188
+ readonly splitRight: boolean;
189
+ /** Splitting a pane vertically: the toolbar button and the top/bottom drop edges. */
190
+ readonly splitDown: boolean;
191
+ /** Blowing a pane up to the whole content area. */
192
+ readonly maximize: boolean;
193
+ /** Collapsing a pane into the minimised strip. */
194
+ readonly minimize: boolean;
195
+ /** Reordering tabs within their band, by drag or `Alt+Arrow`. */
196
+ readonly reorderTabs: boolean;
197
+ }
198
+ /** Gestures the side panels offer on their docked views. */
199
+ interface SidebarFeatures {
200
+ /** Collapsing and expanding a panel. The close button of the compact drawer is not affected. */
201
+ readonly collapse: boolean;
202
+ /** Dragging a panel wider or narrower. */
203
+ readonly resize: boolean;
204
+ /** Reordering view tabs **within** one panel, by drag or `Alt+Arrow`. */
205
+ readonly reorderViews: boolean;
206
+ /** Moving a view to the other sidebar: the menu entry, the drag and `Alt+Shift+Arrow`. */
207
+ readonly moveViews: boolean;
208
+ /** Hiding a view from this workspace (menu entry). */
209
+ readonly hideViews: boolean;
210
+ /** The checklist on the icon strip that says which views live here. */
211
+ readonly curate: boolean;
212
+ /** Stacking a second view below the first: the menu entry and the drop edges of a panel. */
213
+ readonly stackViews: boolean;
214
+ /** Parking a foreign tab in a sidebar, by dragging it onto the strip or a drop edge. */
215
+ readonly acceptTabs: boolean;
216
+ /** Opening a docked view in the content area (menu entry). */
217
+ readonly openViewInContent: boolean;
218
+ /** Resetting the stored state of a view (menu entry). */
219
+ readonly resetViewState: boolean;
220
+ /** The named-instance switcher in the panel header, where a surface declares `instanceable`. */
221
+ readonly instances: boolean;
222
+ }
223
+ /** Gestures the rail offers on its items. */
224
+ interface RailFeatures {
225
+ /** Reordering rail items **within** their band, by drag or `Alt+Arrow`. */
226
+ readonly reorder: boolean;
227
+ /** Moving an item to the other rail: the menu entry, the drag and `Alt+Shift+Arrow`. */
228
+ readonly moveItems: boolean;
229
+ /** Hiding a rail item (menu entry). */
230
+ readonly hideItems: boolean;
231
+ /** The checklist on empty rail space that says which items live here. */
232
+ readonly curate: boolean;
233
+ }
234
+ /** Whether named workspaces exist for this product at all. */
235
+ interface WorkspaceFeatures {
236
+ /**
237
+ * The workspace machinery the shell offers on its own: the manage and reset commands and the
238
+ * workspace entries in the rail. Switching it off leaves the storage scoping untouched — the
239
+ * active workspace still names the layout keys, the user just never meets the concept.
240
+ */
241
+ readonly enabled: boolean;
242
+ /**
243
+ * Whether the user may put a workspace **they saved** in the rail. A saved workspace is never
244
+ * there by itself — the user places it from *Customize activity bar* — and switching this off
245
+ * withdraws that offer, so the rail holds what your product put there and nothing else. Their
246
+ * placements are kept rather than erased, so switching it back on restores what each user had.
247
+ *
248
+ * Everything else about a saved workspace is untouched: saving, renaming, resetting and switching
249
+ * all work, with the workspace dialog as the way to them. This says nothing about **whether** a
250
+ * user may save workspaces, which is a different question and has no switch.
251
+ */
252
+ readonly savedInRail: boolean;
253
+ }
254
+ /** Whether a tab or a view may be torn off into a browser window of its own. */
255
+ interface WindowFeatures {
256
+ /** "Open in new window" on a tab and on a docked view. */
257
+ readonly popout: boolean;
258
+ }
259
+ /** How commands reach the user beyond the buttons that name them. */
260
+ interface CommandFeatures {
261
+ /**
262
+ * Keyboard shortcuts at all: the global listener that turns a chord into a command, and the
263
+ * chord hints the shell prints beside menu entries, palette rows and bar buttons. With it off a
264
+ * command is still reachable by its button and by the palette, and no hint promises a key that
265
+ * does nothing.
266
+ */
267
+ readonly shortcuts: boolean;
268
+ /** The "recently used" section at the top of the command palette (and the list behind it). */
269
+ readonly recentlyUsed: boolean;
270
+ }
271
+ /**
272
+ * Which shell capabilities a distribution offers its users. The platform ships the full workbench —
273
+ * every capability here is on by default — and a product that would overwhelm its users switches
274
+ * parts off.
275
+ *
276
+ * This is the home for **gestures**. A *contribution* (a command, a bar or rail item, a settings row,
277
+ * a menu entry) is not a gesture and is removed with `provideShell({ omit })` instead. Where a
278
+ * capability has both — a menu entry *and* a drag — the feature switch wins and takes the entry with
279
+ * it, while `omit` stays the finer tool for "entry gone, gesture stays".
280
+ */
281
+ interface ShellFeatures {
282
+ readonly content: ContentFeatures;
283
+ readonly sidebar: SidebarFeatures;
284
+ readonly rail: RailFeatures;
285
+ readonly workspaces: WorkspaceFeatures;
286
+ readonly windows: WindowFeatures;
287
+ readonly commands: CommandFeatures;
288
+ }
289
+ /** The default workbench: every capability on. */
290
+ declare const DEFAULT_SHELL_FEATURES: ShellFeatures;
291
+ declare const SHELL_FEATURES: InjectionToken<ShellFeatures>;
292
+ /** A partial override: name only what you switch off, group by group. */
293
+ type ShellFeaturesInput = {
294
+ readonly [Group in keyof ShellFeatures]?: Partial<ShellFeatures[Group]>;
295
+ };
296
+ /**
297
+ * Switches shell capabilities off for this distribution, e.g.
298
+ * `provideShellFeatures({ content: { splitDown: false, maximize: false } })`. Omit for the full
299
+ * workbench; fields merge group by group, so a partial override leaves the rest of that group alone.
300
+ */
301
+ declare function provideShellFeatures(features: ShellFeaturesInput): EnvironmentProviders;
302
+
303
+ /**
304
+ * Extra translation namespaces a distribution composes on top of the host base. Each is served at `/i18n/<name>/<lang>.json` and nested under its key, so its strings
305
+ * live at `name.*` and can never collide with a host key. Used for a weaver's own strings
306
+ * (`demo`) and for distribution branding (`product`).
307
+ */
308
+ declare const TRANSLATION_NAMESPACES: InjectionToken<readonly string[]>;
309
+ /**
310
+ * A distribution declares which namespaced translation bundles to load — the weavers it bundles
311
+ * (e.g. `'testbed'`) and its own branding (`'product'`). The bare platform registers none;
312
+ * its host keys are the whole story.
313
+ */
314
+ declare function provideTranslationNamespaces(...namespaces: string[]): Provider;
315
+ /** Directory the distribution serves its overlay bundles from, without a trailing slash. */
316
+ declare const TRANSLATION_OVERRIDES: InjectionToken<string>;
317
+ /**
318
+ * Load `<basePath>/<lang>.json` and merge it over everything else **key by key**, so a product
319
+ * can reword the shell in its own house language ("Save as" rather than "Save as new") without
320
+ * forking our bundle: name only the keys you change and inherit the rest, including every key a
321
+ * later release adds. It is applied last, so it also reaches a bundled weaver's strings.
322
+ *
323
+ * Namespaces (`provideTranslationNamespaces`) remain the way to *add* your own strings, and they can
324
+ * still never collide with a host key. This is the opposite job — replacing one — which is why it is
325
+ * a separate, deliberate opt-in rather than a namespace with a magic name.
326
+ *
327
+ * `basePath` lets one build carry several wordings and pick one while composing — a white-label
328
+ * distribution that serves three brands from the same bundle, or a demo that switches product.
329
+ * A product with a backend does not need it: the default path is same-origin, so its server can
330
+ * already vary the bytes per tenant. Omit it and nothing changes.
331
+ *
332
+ * A language with no overlay file keeps the shipped strings (dev-warned). A key the overlay names but
333
+ * nothing ships is dev-warned too, since a typo there would otherwise be a string that never appears.
334
+ */
335
+ declare function provideTranslationOverrides(basePath?: string): Provider;
336
+
337
+ /**
338
+ * The neutral host button primitive. A directive on a real `<button>`/`<a>`, so it
339
+ * keeps native semantics (type, disabled, focus, aria) and its own content — consumers just add
340
+ * `lwButton` and pick a variant.
341
+ *
342
+ * The button LOOK is a CSS-class contract (`.lw-btn` in theme.css): this directive is
343
+ * only a thin wrapper that emits those class names from typed inputs. So the very same look is
344
+ * reachable by SDK-only plugins that cannot import this directive across the Nx boundary — they
345
+ * write `<button class="lw-btn lw-btn--primary">`. One source of truth, all on semantic tokens,
346
+ * so a theme / tenant override reaches host and plugin buttons alike.
347
+ *
348
+ * <button lwButton variant="primary" (click)="save()">Speichern</button>
349
+ * <button lwButton variant="ghost" size="sm" iconOnly aria-label="…"><lw-icon … /></button>
350
+ */
351
+ declare class LwButton {
352
+ /** Visual weight; defaults to `default`. */
353
+ readonly variant: i0.InputSignal<LwButtonVariant>;
354
+ /** Size; defaults to `md`. */
355
+ readonly size: i0.InputSignal<LwButtonSize>;
356
+ /** Square, equal-padding button for a single icon (needs its own `aria-label`). */
357
+ readonly iconOnly: i0.InputSignalWithTransform<boolean, unknown>;
358
+ protected readonly classes: i0.Signal<string>;
359
+ static ɵfac: i0.ɵɵFactoryDeclaration<LwButton, never>;
360
+ static ɵdir: i0.ɵɵDirectiveDeclaration<LwButton, "button[lwButton], a[lwButton]", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "iconOnly": { "alias": "iconOnly"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
361
+ }
362
+
363
+ /** The custom-element tag. */
364
+ declare const LW_BUTTON_TAG = "lw-button";
365
+ /**
366
+ * `<lw-button variant="primary">Save</lw-button>` — the host button primitive as a framework-agnostic
367
+ * custom element (like `<lw-tooltip>`/`<lw-select>`/`<lw-menu>`): light DOM (its content stays
368
+ * in place), a `role="button"` element that emits the same **`.lw-btn` CSS-class contract**
369
+ * (`theme.css`) the {@link LwButton} directive does — one source of truth for the look, on semantic tokens.
370
+ *
371
+ * The Angular {@link LwButton} directive stays the idiomatic choice for host chrome (it sits on a **native**
372
+ * `<button>`/`<a>`, keeping native semantics). This element is the framework-agnostic path for a weaver body
373
+ * or a sandboxed iframe that cannot import the directive across the Nx boundary. It adds keyboard activation
374
+ * (Enter/Space) and `disabled` so a `role="button"` behaves like a button.
375
+ *
376
+ * <lw-button variant="primary" (click)="save()">Speichern</lw-button>
377
+ * <lw-button variant="ghost" size="sm" icon-only aria-label="…"><!-- icon --></lw-button>
378
+ */
379
+ declare class LwButtonElement extends HTMLElement {
380
+ static readonly observedAttributes: string[];
381
+ get variant(): LwButtonVariant;
382
+ set variant(value: LwButtonVariant | null);
383
+ get size(): LwButtonSize;
384
+ set size(value: LwButtonSize | null);
385
+ get iconOnly(): boolean;
386
+ set iconOnly(value: boolean);
387
+ get disabled(): boolean;
388
+ set disabled(value: boolean);
389
+ connectedCallback(): void;
390
+ disconnectedCallback(): void;
391
+ attributeChangedCallback(): void;
392
+ private readonly onKeydown;
393
+ private render;
394
+ }
395
+ /** Registers `<lw-button>` once (idempotent) — called from {@link provideShell} at bootstrap. */
396
+ declare function defineLwButton(): void;
397
+
398
+ /** The custom-element tag. */
399
+ declare const LW_MARKDOWN_TAG = "lw-markdown";
400
+ /**
401
+ * `<lw-markdown source="…">` — the host rich-text primitive as a framework-agnostic custom element
402
+ * (like `<lw-tooltip>`/`<lw-select>`/`<lw-menu>`): a plain `HTMLElement`, **light DOM**, so the
403
+ * `prose-lw` Tailwind-Typography classes + `--lw-*` tokens cascade in (auto dark-mode). It does
404
+ * no formatting logic of its own — `marked` parses, **DOMPurify** sanitizes (scripts / `on*` / `javascript:`
405
+ * stripped; the element renders raw `innerHTML`, so this is the security seam, like the icon registry).
406
+ * Reusable anywhere rich text is needed (dialog bodies, About, weaver content), and — unlike the old
407
+ * Angular component — usable in a weaver body by tag, no `@loomweaver/shell` import.
408
+ *
409
+ * <lw-markdown [source]="'Delete **' + name + '**? This cannot be undone.'"></lw-markdown>
410
+ */
411
+ declare class LwMarkdownElement extends HTMLElement {
412
+ static readonly observedAttributes: string[];
413
+ private prose?;
414
+ get source(): string;
415
+ set source(value: string | null);
416
+ connectedCallback(): void;
417
+ attributeChangedCallback(): void;
418
+ private render;
419
+ }
420
+ /** Registers `<lw-markdown>` once (idempotent) — called from {@link provideShell} at bootstrap. */
421
+ declare function defineLwMarkdown(): void;
422
+
423
+ /**
424
+ * A small indeterminate spinner. Pure CSS ring on `currentColor`
425
+ * (brand by default), sized via the `size` input. Used by the progress dialog and anywhere a
426
+ * busy indicator is needed.
427
+ *
428
+ * <lw-spinner />
429
+ * <lw-spinner size="1rem" [label]="'…' | transloco" />
430
+ */
431
+ declare class LwSpinner {
432
+ /** CSS size of the spinner (width = height). */
433
+ readonly size: i0.InputSignal<string>;
434
+ /** Accessible label announced to screen readers — pass a translated string; empty = none. */
435
+ readonly label: i0.InputSignal<string>;
436
+ static ɵfac: i0.ɵɵFactoryDeclaration<LwSpinner, never>;
437
+ static ɵcmp: i0.ɵɵComponentDeclaration<LwSpinner, "lw-spinner", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
438
+ }
439
+
440
+ /** Tooltip placement relative to the trigger — the element positions the bubble in JS accordingly. */
441
+ type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
442
+ /** The custom-element tag. Authored as the last child of a `position: relative` trigger. */
443
+ declare const LW_TOOLTIP_TAG = "lw-tooltip";
444
+ /**
445
+ * `<lw-tooltip>` — the host tooltip primitive as a **framework-agnostic custom element**.
446
+ * It is a plain `HTMLElement` (no framework runtime), so it crosses the Nx boundary as a *tag* (an
447
+ * SDK-only weaver uses `<lw-tooltip text="…" position="bottom">` without importing `@loomweaver/shell`) and,
448
+ * later, an iframe/sandbox boundary once the element script is served alongside `theme.css`.
449
+ *
450
+ * The element renders the bubble and drives it entirely in JS — reveal, hide, and **positioning**. The
451
+ * bubble is a **Popover** in the browser top layer, so it is never clipped by a `transform`/`overflow`
452
+ * ancestor (a virtual-scroll row) nor lost under a region's z-order (#13). Its `left`/`top` are computed
453
+ * in JS (no CSS anchor positioning — shipping Safari does not support it): a **pointer** reveal anchors to
454
+ * the **cursor** (native-`title`-like, so a wide trigger's tooltip appears where the mouse is, not at the
455
+ * element's far edge); a **focus** reveal anchors to the trigger element per `position`. Reveal is off the
456
+ * trigger's hover/focus (mouse only — touch never reveals). Its **look** lives in `theme.css`
457
+ * (`.lw-tooltip-bubble`, tokened) like `.lw-btn`. Place it as the last child of a positioned trigger:
458
+ *
459
+ * <button class="relative …" [attr.aria-label]="'key' | transloco">
460
+ * <lw-icon name="…" />
461
+ * <lw-tooltip text="…" position="bottom"></lw-tooltip>
462
+ * </button>
463
+ */
464
+ declare class LwTooltipElement extends HTMLElement {
465
+ static readonly observedAttributes: string[];
466
+ private bubble?;
467
+ private trigger?;
468
+ private showTimer?;
469
+ private pointer?;
470
+ get text(): string | null;
471
+ set text(value: string | null);
472
+ get position(): TooltipPosition;
473
+ set position(value: TooltipPosition | null);
474
+ connectedCallback(): void;
475
+ disconnectedCallback(): void;
476
+ attributeChangedCallback(): void;
477
+ private readonly onEnter;
478
+ private readonly onMove;
479
+ private readonly onLeave;
480
+ private readonly onFocusIn;
481
+ private readonly onFocusOut;
482
+ private render;
483
+ private createBubble;
484
+ private scheduleShow;
485
+ private delayMs;
486
+ private show;
487
+ private placeBubble;
488
+ private hide;
489
+ private clearTimer;
490
+ }
491
+ /** Registers `<lw-tooltip>` once (idempotent) — called from {@link provideShell} at bootstrap. */
492
+ declare function defineLwTooltip(): void;
493
+
494
+ /**
495
+ * Displays the running build's version, e.g. `v0.1.0`. A neutral host-offered widget
496
+ * any distribution or plugin can embed (status bar, about dialog, …); the number comes
497
+ * from {@link VersionService}, never hardcoded.
498
+ *
499
+ * <lw-version /> → v0.1.0
500
+ * <lw-version prefix="" /> → 0.1.0
501
+ */
502
+ declare class LwVersion {
503
+ private readonly versions;
504
+ /** Text shown before the number; set to `''` to hide. */
505
+ readonly prefix: i0.InputSignal<string>;
506
+ protected readonly label: i0.Signal<string>;
507
+ static ɵfac: i0.ɵɵFactoryDeclaration<LwVersion, never>;
508
+ static ɵcmp: i0.ɵɵComponentDeclaration<LwVersion, "lw-version", never, { "prefix": { "alias": "prefix"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
509
+ }
510
+
511
+ declare const LOOM_ICONS: {
512
+ readonly themeLight: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z\"></path></svg>";
513
+ readonly themeDark: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z\"></path></svg>";
514
+ readonly themeSystem: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25\"></path></svg>";
515
+ readonly chevronsLeft: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5\"></path></svg>";
516
+ readonly chevronsRight: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5\"></path></svg>";
517
+ readonly chevronDown: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m19.5 8.25-7.5 7.5-7.5-7.5\"></path></svg>";
518
+ readonly check: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m4.5 12.75 6 6 9-13.5\"></path></svg>";
519
+ readonly close: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\"></path></svg>";
520
+ readonly pin: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 4h8M10.5 4v7M13.5 4v7M7 11h10M12 11v9\"/></svg>";
521
+ readonly save: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M17 3.75H5.25a1.5 1.5 0 0 0-1.5 1.5v13.5a1.5 1.5 0 0 0 1.5 1.5h13.5a1.5 1.5 0 0 0 1.5-1.5V7l-3.25-3.25Z\"/><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 3.75v4.5h6.5v-4.5M7.75 20.25v-7h8.5v7\"/></svg>";
522
+ readonly splitPanes: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4 5.25h16a.75.75 0 0 1 .75.75v12a.75.75 0 0 1-.75.75H4a.75.75 0 0 1-.75-.75V6a.75.75 0 0 1 .75-.75ZM12 5.25v13.5\"/></svg>";
523
+ readonly splitPanesDown: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4 5.25h16a.75.75 0 0 1 .75.75v12a.75.75 0 0 1-.75.75H4a.75.75 0 0 1-.75-.75V6a.75.75 0 0 1 .75-.75ZM3.25 12h17.5\"/></svg>";
524
+ readonly workspaces: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 0 1-1.125-1.125v-3.75ZM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 0 1-1.125-1.125v-8.25ZM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 0 1-1.125-1.125v-2.25Z\"></path></svg>";
525
+ readonly menu: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\"></path></svg>";
526
+ readonly document: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z\"></path></svg>";
527
+ readonly navigator: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\"></path></svg>";
528
+ readonly outline: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0ZM3.75 12h.007v.008H3.75V12Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm-.375 5.25h.007v.008H3.75v-.008Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z\"></path></svg>";
529
+ readonly add: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\"></path></svg>";
530
+ readonly edit: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10\"></path></svg>";
531
+ readonly trash: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0\"></path></svg>";
532
+ readonly preview: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z\"></path><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z\"></path></svg>";
533
+ readonly sort: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 7.5 7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5\"></path></svg>";
534
+ readonly reset: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99\"></path></svg>";
535
+ readonly undo: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3\"></path></svg>";
536
+ readonly info: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z\"></path></svg>";
537
+ readonly lock: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z\"></path></svg>";
538
+ readonly success: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m4.5 12.75 6 6 9-13.5\"></path></svg>";
539
+ readonly warning: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z\"></path></svg>";
540
+ readonly error: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z\"></path></svg>";
541
+ readonly settings: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z\"></path><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z\"></path></svg>";
542
+ readonly help: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z\"></path></svg>";
543
+ readonly search: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\"></path></svg>";
544
+ readonly plugin: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M14.25 6.087c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.036-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959v0a.64.64 0 0 1-.657.643 48.39 48.39 0 0 1-4.163-.3c.186 1.613.293 3.25.315 4.907a.656.656 0 0 1-.658.663v0c-.355 0-.676-.186-.959-.401a1.647 1.647 0 0 0-1.003-.349c-1.036 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401v0c.31 0 .555.26.532.57a48.039 48.039 0 0 1-.642 5.056c1.518.19 3.058.309 4.616.354a.64.64 0 0 0 .657-.643v0c0-.355-.186-.676-.401-.959a1.647 1.647 0 0 1-.349-1.003c0-1.035 1.008-1.875 2.25-1.875 1.243 0 2.25.84 2.25 1.875 0 .369-.128.713-.349 1.003-.215.283-.4.604-.4.959v0c0 .333.277.599.61.58a48.1 48.1 0 0 0 5.427-.63 48.05 48.05 0 0 0 .582-4.717.532.532 0 0 0-.533-.57v0c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.035 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.37 0 .713.128 1.003.349.283.215.604.401.96.401v0a.656.656 0 0 0 .658-.663 48.422 48.422 0 0 0-.37-5.36c-1.886.342-3.81.574-5.766.689a.578.578 0 0 1-.61-.58v0Z\"></path></svg>";
545
+ readonly download: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3\"></path></svg>";
546
+ readonly maximize: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15\"></path></svg>";
547
+ readonly popout: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\"></path></svg>";
548
+ readonly restore: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 9V4.5M9 9H4.5M9 9 3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5 5.25 5.25\"></path></svg>";
549
+ readonly minimize: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 12h14\"></path></svg>";
550
+ };
551
+ /** The names the shell ships. Naming one in `provideIcons` replaces that glyph app-wide. */
552
+ type LoomIconName = keyof typeof LOOM_ICONS;
553
+
554
+ /** A live notification held by the service and rendered by the toast outlet. */
555
+ interface Notification {
556
+ readonly id: string;
557
+ readonly kind: NotificationKind;
558
+ readonly message: string;
559
+ readonly action?: NotificationAction;
560
+ }
561
+ /**
562
+ * Neutral host service for transient notifications ("toasts"). The shell
563
+ * renders them once via the toast outlet; distributions and plugins raise them through
564
+ * `ctx`. Sticky toasts (no `timeoutMs`) stay until dismissed — the update notice uses
565
+ * this so dismissing it never loses the "update available" state.
566
+ */
567
+ declare class NotificationService {
568
+ private readonly items;
569
+ private readonly timers;
570
+ private nextId;
571
+ /** The currently visible notifications, oldest first. */
572
+ readonly notifications: i0.Signal<readonly Notification[]>;
573
+ /** Shows a notification (replacing any with the same id) and returns its id. */
574
+ show(input: NotificationInput): string;
575
+ /** Removes the notification with the given id (no-op if already gone). */
576
+ dismiss(id: string): void;
577
+ private clearTimer;
578
+ static ɵfac: i0.ɵɵFactoryDeclaration<NotificationService, never>;
579
+ static ɵprov: i0.ɵɵInjectableDeclaration<NotificationService>;
580
+ }
581
+
582
+ /**
583
+ * Renders the active notifications as toasts in a fixed corner. Mounted once
584
+ * by the shell root, so every distribution gets it for free. Each toast shows its kind
585
+ * icon, message, an optional action button and a dismiss control. The kind selects the
586
+ * icon; a feedback-colour token ladder can be added later if toasts grow richer.
587
+ */
588
+ declare class ToastOutlet {
589
+ private readonly service;
590
+ protected readonly notifications: i0.Signal<readonly Notification[]>;
591
+ private readonly icons;
592
+ private readonly iconColors;
593
+ protected iconFor(kind: NotificationKind): LoomIconName;
594
+ protected iconColorFor(kind: NotificationKind): string;
595
+ protected roleFor(kind: NotificationKind): 'alert' | 'status';
596
+ protected runAction(toast: Notification): void;
597
+ protected dismiss(id: string): void;
598
+ static ɵfac: i0.ɵɵFactoryDeclaration<ToastOutlet, never>;
599
+ static ɵcmp: i0.ɵɵComponentDeclaration<ToastOutlet, "lw-toasts", never, {}, {}, never, never, true, never>;
600
+ }
601
+
602
+ /**
603
+ * Persistent update affordance for a bar. While no update is pending it is a
604
+ * quiet "check for updates" button; once the service worker has a new version ready it
605
+ * gains a badge dot and becomes "reload to update". When installing an update failed it gains a
606
+ * caution dot and offers a reload instead of pretending to be current; when the worker's cache is
607
+ * broken outright it says so and offers the repair. Reads {@link UpdateService}, so the badge
608
+ * survives dismissing the toast. Renders nothing when updates are unavailable
609
+ * (no service worker — dev/unsupported).
610
+ */
611
+ declare class UpdateBadge {
612
+ private readonly updates;
613
+ protected readonly enabled: boolean;
614
+ protected readonly available: i0.Signal<boolean>;
615
+ protected readonly failed: i0.Signal<boolean>;
616
+ protected readonly label: i0.Signal<"update.available" | "update.broken" | "update.failed" | "update.check">;
617
+ protected onClick(): void;
618
+ static ɵfac: i0.ɵɵFactoryDeclaration<UpdateBadge, never>;
619
+ static ɵcmp: i0.ɵɵComponentDeclaration<UpdateBadge, "lw-update-badge", never, {}, {}, never, never, true, never>;
620
+ }
621
+
622
+ type DialogKind = 'confirm' | 'alert' | 'prompt' | 'custom' | 'progress';
623
+ type ButtonRole = 'confirm' | 'cancel' | 'custom';
624
+ /** A footer button as the dialog outlet renders it. */
625
+ interface DialogButtonView {
626
+ readonly label: string;
627
+ readonly variant: LwButtonVariant;
628
+ readonly role: ButtonRole;
629
+ readonly value?: unknown;
630
+ readonly autofocus: boolean;
631
+ }
632
+ /** One open dialog, held by the service and rendered by the dialog outlet. */
633
+ interface DialogInstance {
634
+ readonly id: string;
635
+ readonly ref: DialogRef;
636
+ readonly kind: DialogKind;
637
+ readonly tone: DialogTone;
638
+ /** Host icon name (from options or the tone default); rendered by `<lw-icon>`. */
639
+ readonly icon?: string;
640
+ readonly title?: string;
641
+ readonly message?: string;
642
+ readonly component?: Type<unknown>;
643
+ readonly injector?: Injector;
644
+ readonly placeholder?: string;
645
+ readonly promptValue?: WritableSignal<string>;
646
+ /** Live status text for a `progress` dialog. */
647
+ readonly progressMessage?: WritableSignal<string>;
648
+ /** Type-to-confirm label (Markdown) shown above the input, if any. */
649
+ readonly requireLabel?: string;
650
+ /** Validates the guard input; `null` = valid (enables confirm), else an inline error. */
651
+ readonly requireValidate?: (value: string) => string | null;
652
+ readonly buttons: readonly DialogButtonView[];
653
+ readonly dismissable: boolean;
654
+ readonly size?: DialogSize;
655
+ /** Render only the component (no host chrome) — the component owns the frame contents. */
656
+ readonly bare?: boolean;
657
+ /** The frame shows a maximize/restore control (framed dialogs; bare ones draw their own). */
658
+ readonly maximizable?: boolean;
659
+ /** Vertical anchor: `'top'` pins the panel's top edge on every width. Defaults to `'center'`. */
660
+ readonly align?: 'center' | 'top';
661
+ }
662
+ /**
663
+ * Neutral host service for modal dialogs:
664
+ * convenience dialogs (`confirm`/`alert`/`prompt`, the host paints everything) and rich
665
+ * `open(Component)` dialogs where a plugin owns the body and the host paints the frame +
666
+ * declarative footer buttons. The shell renders them once via `<lw-dialog-outlet>`. Uses
667
+ * only `<lw-*>` chrome + semantic tokens, so plugin-authored bodies stay harmonious.
668
+ */
669
+ declare class DialogService {
670
+ private readonly injector;
671
+ private readonly document;
672
+ private readonly items;
673
+ private counter;
674
+ /** The currently open dialogs, oldest first (the last one is topmost). */
675
+ readonly dialogs: i0.Signal<readonly DialogInstance[]>;
676
+ /** Asks a yes/no question; resolves `true` only if confirmed. */
677
+ confirm(options: ConfirmOptions): Promise<boolean>;
678
+ /** Shows a message with a single acknowledge button. */
679
+ alert(options: AlertOptions): Promise<void>;
680
+ /** Asks for a line of text; resolves the value, or `null` if cancelled/dismissed. */
681
+ prompt(options: PromptOptions): Promise<string | null>;
682
+ /**
683
+ * Opens a custom body component. The host paints the frame + any declarative footer
684
+ * buttons; the component owns the body and injects {@link DialogRef} to close itself.
685
+ */
686
+ open<R = unknown>(component: Type<unknown>, options?: OpenOptions): DialogRef<R>;
687
+ /**
688
+ * Shows a non-dismissable busy indicator (spinner + status) so a running operation is not
689
+ * interrupted. The caller closes it when done — or use {@link withProgress} to auto-close.
690
+ */
691
+ progress(options: ProgressOptions): ProgressHandle;
692
+ /** Runs `work` behind a progress dialog that closes automatically when it settles. */
693
+ withProgress<T>(options: ProgressOptions, work: Promise<T>): Promise<T>;
694
+ private push;
695
+ private mount;
696
+ static ɵfac: i0.ɵɵFactoryDeclaration<DialogService, never>;
697
+ static ɵprov: i0.ɵɵInjectableDeclaration<DialogService>;
698
+ }
699
+
700
+ /**
701
+ * Renders the open dialogs once, mounted by the shell root. Draws the frame
702
+ * (backdrop, panel, title, close-X, footer buttons) and either the convenience body
703
+ * (message + optional prompt input) or a plugin's custom body via NgComponentOutlet.
704
+ * Owns the modal mechanics: scroll-lock, Escape + backdrop dismiss, focus into the newest
705
+ * dialog + restore on close, and a lightweight focus trap.
706
+ */
707
+ declare class DialogOutlet {
708
+ private readonly service;
709
+ private readonly document;
710
+ protected readonly dialogs: i0.Signal<readonly DialogInstance[]>;
711
+ private readonly panels;
712
+ constructor();
713
+ protected onEscape(): void;
714
+ protected onFocusIn(event: FocusEvent): void;
715
+ protected onTab(event: Event, backward: boolean): void;
716
+ protected dismiss(dialog: DialogInstance): void;
717
+ protected onButton(dialog: DialogInstance, button: DialogButtonView): void;
718
+ protected onPromptInput(dialog: DialogInstance, event: Event): void;
719
+ protected onEnter(dialog: DialogInstance): void;
720
+ protected confirmBlocked(dialog: DialogInstance, button: DialogButtonView): boolean;
721
+ protected guardError(dialog: DialogInstance): string | null;
722
+ protected wrapperClasses(dialog: DialogInstance): string;
723
+ protected toneCircle(tone: DialogTone): string;
724
+ protected panelWidth(dialog: DialogInstance): string;
725
+ private topPanel;
726
+ private focusable;
727
+ private top;
728
+ static ɵfac: i0.ɵɵFactoryDeclaration<DialogOutlet, never>;
729
+ static ɵcmp: i0.ɵɵComponentDeclaration<DialogOutlet, "lw-dialog-outlet", never, {}, {}, never, never, true, never>;
730
+ }
731
+
732
+ /**
733
+ * One settings row: label (+ optional description) on the left, the control projected on
734
+ * the right. A pure layout primitive — the host slots the matching control
735
+ * (`<lw-select>`, later toggle/button) via content projection.
736
+ */
737
+ declare class LwSettingRow {
738
+ readonly label: i0.InputSignal<string>;
739
+ readonly description: i0.InputSignal<string>;
740
+ static ɵfac: i0.ɵɵFactoryDeclaration<LwSettingRow, never>;
741
+ static ɵcmp: i0.ɵɵComponentDeclaration<LwSettingRow, "lw-setting-row", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
742
+ }
743
+
744
+ /** Multi-provider token: each contribution adds one {@link BarItem}. */
745
+ declare const BAR_ITEM: InjectionToken<readonly BarItem[]>;
746
+ /** Hooks bar items into the shell — used by the host and by distributions. */
747
+ declare function provideBarItems(...items: BarItem[]): Provider[];
748
+
749
+ /** Multi-provider token: each contribution adds one {@link RailItem}. */
750
+ declare const RAIL_ITEM: InjectionToken<readonly RailItem[]>;
751
+ /** Hooks rail commands into the shell — used by the host and by distributions. */
752
+ declare function provideRailItems(...items: RailItem[]): Provider[];
753
+
754
+ /** Multi-provider token: each contribution adds one {@link View}. */
755
+ declare const VIEW: InjectionToken<readonly View[]>;
756
+ /** Hooks views into regions — used by the host and by distributions. */
757
+ declare function provideViews(...views: View[]): Provider[];
758
+
759
+ /**
760
+ * A {@link ContentRoute} as the registry holds it: the plugin's own declaration plus the **host-stamped**
761
+ * id of the plugin that registered it. The id is not part of the authoring contract — a plugin must not be
762
+ * able to claim someone else's identity — so it lives here rather than in `@loomweaver/plugin-sdk`.
763
+ */
764
+ type RegisteredContentRoute = ContentRoute & {
765
+ readonly pluginId?: string;
766
+ };
767
+ /**
768
+ * A {@link View} as the registry holds it: the plugin's own declaration plus the **host-stamped** id
769
+ * of the plugin that registered it, mirroring {@link RegisteredContentRoute}. The stamp is what lets
770
+ * the retention guards find a plugin's dirty instances before the plugin is disabled or uninstalled.
771
+ */
772
+ type RegisteredView = View & {
773
+ readonly pluginId?: string;
774
+ };
775
+
776
+ /**
777
+ * A command as the registry holds it: the declaration a plugin handed in, plus the owner the host
778
+ * stamped on it. The owner is what lets a command be attributed — to let its own plugin reach it
779
+ * without a grant, and to tell a plugin's commands from the shell's own.
780
+ */
781
+ interface RegisteredCommand {
782
+ readonly command: Command;
783
+ readonly ownerId?: string;
784
+ }
785
+ /**
786
+ * Holds the live UI contributions the regions render. Seeded at startup from the
787
+ * static `provide*` API and (P1.2 loader) added to at runtime by plugins. Signal-
788
+ * based so the regions react to runtime registration/removal.
789
+ *
790
+ * Contributions are keyed by `id`: registering an existing id **overrides** the
791
+ * previous entry in place (last contribution wins — a distribution/plugin can
792
+ * replace a host default by reusing its id), and `remove*ById` drops one (a
793
+ * distribution hides a default it does not want, e.g. via `provideShell({ omit })`).
794
+ *
795
+ * Override is **destructive**, not stacked: the superseded entry is replaced, so disposing
796
+ * the *winning* handle removes the id entirely (it does not restore the entry it replaced),
797
+ * and disposing a *superseded* handle is a no-op. That is fine for today's usage (defaults are
798
+ * overridden at composition time and never disposed); a per-id stack that reveals the previous
799
+ * entry on dispose is the follow-up if a plugin ever overrides a default and is then unloaded.
800
+ */
801
+ declare class ContributionRegistry {
802
+ private readonly commandsSignal;
803
+ private readonly surfacesSignal;
804
+ private readonly barItemsSignal;
805
+ private readonly railItemsSignal;
806
+ private readonly menuItemsSignal;
807
+ private readonly dockedSurfaces;
808
+ private readonly routableSurfaces;
809
+ private readonly omittedSignal;
810
+ /** Registered commands with their owner — the source for anything that has to attribute one. */
811
+ readonly registeredCommands: Signal<readonly RegisteredCommand[]>;
812
+ /** Registered commands — the invocable actions triggers (items/keybindings/palette) point at. */
813
+ readonly commands: Signal<readonly Command[]>;
814
+ readonly views: Signal<readonly RegisteredView[]>;
815
+ readonly barItems: Signal<readonly BarItem[]>;
816
+ readonly railItems: Signal<readonly RailItem[]>;
817
+ /**
818
+ * URL-addressed content-area routes; the ContentRouter mirrors these into the Router.
819
+ * Omitted routes are filtered out, so every consumer — tab strip, auto-open, pane target
820
+ * picker, `matchRoute` — drops them without knowing about omission.
821
+ */
822
+ readonly contentRoutes: Signal<readonly RegisteredContentRoute[]>;
823
+ /**
824
+ * The routes {@link omit} dropped. The ContentRouter still maps these to a neutral
825
+ * "not available here" placeholder at their path, so a shared deep-link explains itself instead of
826
+ * silently falling back to home.
827
+ */
828
+ readonly omittedContentRoutes: Signal<readonly RegisteredContentRoute[]>;
829
+ /** Menu-slot contributions; the MenuService filters these by slot + `when` when a menu opens. */
830
+ readonly menuItems: Signal<readonly MenuItem[]>;
831
+ /** The ids {@link omit} hides, exactly as the distribution wrote them, prefixes and all. */
832
+ readonly omitted: Signal<ReadonlySet<string>>;
833
+ /**
834
+ * Every id registered so far, of whatever kind, **including** those {@link omit} hides. An
835
+ * omitted contribution is by construction absent from every other signal here, so this is the
836
+ * only way to tell an `omit` that hid something from one that hit nothing at all.
837
+ */
838
+ readonly registeredIds: Signal<ReadonlySet<string>>;
839
+ /**
840
+ * Hides contributions by id for good — a distribution drops a default it does not want
841
+ * (`provideShell({ omit })`). Unlike {@link removeCommandById} and friends, which delete what is
842
+ * registered *now*, this is a lasting filter: an id a plugin registers later stays hidden too.
843
+ */
844
+ omit(ids: readonly string[]): void;
845
+ /**
846
+ * Adds a command. Like {@link addContentRoute}, `pluginId` is stamped by the host and never
847
+ * claimed by the plugin: it is what decides whether a caller is reaching its own command or
848
+ * another's. The shell's own seeded commands carry none.
849
+ */
850
+ addCommand(command: Command, pluginId?: string): Disposable;
851
+ /**
852
+ * Adds a panel view. Like {@link addContentRoute}, `pluginId` is stamped by the host, never by
853
+ * the plugin, and overwrites anything the view carried.
854
+ */
855
+ addView(view: View, pluginId?: string): Disposable;
856
+ addBarItem(item: BarItem): Disposable;
857
+ addRailItem(item: RailItem): Disposable;
858
+ removeCommandById(id: string): void;
859
+ removeViewById(id: string): void;
860
+ removeBarItemById(id: string): void;
861
+ removeRailItemById(id: string): void;
862
+ /**
863
+ * Adds a content route (keyed by `path`; a re-registered path overrides in place). `pluginId` is
864
+ * stamped by the host, never by the plugin: it is what lets a route's surface be checked against its
865
+ * owner's capability grants, so it is applied last and overwrites anything the route carried.
866
+ */
867
+ addContentRoute(route: ContentRoute, pluginId?: string): Disposable;
868
+ /** Adds a menu-slot item. With an `id` a re-registration replaces in place (last-in wins); without one the item is additive and dispose removes this exact contribution. */
869
+ addMenuItem(item: MenuItem): Disposable;
870
+ removeMenuItemById(id: string): void;
871
+ private addSurface;
872
+ private visible;
873
+ private add;
874
+ private removeById;
875
+ static ɵfac: i0.ɵɵFactoryDeclaration<ContributionRegistry, never>;
876
+ static ɵprov: i0.ɵɵInjectableDeclaration<ContributionRegistry>;
877
+ }
878
+
879
+ /**
880
+ * Host registry for settings sections (schema-driven). The shell and
881
+ * plugins contribute sections; the host renders them all in one {@link SettingsDialog}.
882
+ * Storage is deliberately NOT centralised — each section carries its own value accessors,
883
+ * so the owner keeps responsibility for persistence (shell settings in the shell, plugin
884
+ * settings in the plugin, optionally against its owner's backend). This keeps the host domain-pure and
885
+ * fits the per-tenant tenancy model without the host needing to persist foreign data.
886
+ */
887
+ declare class SettingsService {
888
+ private current;
889
+ private readonly dialogs;
890
+ private readonly registry;
891
+ /** The section a caller asked to show; the settings dialog consumes it. */
892
+ readonly requestedSection: i0.Signal<string | undefined>;
893
+ /**
894
+ * Registered sections, ordered by `order` (default 0), then registration order — with any
895
+ * omitted section or row dropped, and a section left with no rows hidden entirely.
896
+ */
897
+ readonly all: i0.Signal<SettingsSection[]>;
898
+ /**
899
+ * Every registered section, **including** those {@link omit} hides and with their omitted rows
900
+ * still in place. {@link all} is what the dialog draws; this is what was contributed, which is
901
+ * what a dev-mode report needs to tell an `omit` that hid a row from one that hit nothing.
902
+ */
903
+ readonly registered: i0.Signal<readonly SettingsSection[]>;
904
+ /**
905
+ * Contributes a section; dispose to remove it (plugin deactivation). Registering an existing
906
+ * id **overrides** the previous section in place (last contribution wins), like every other
907
+ * contribution.
908
+ */
909
+ register(section: SettingsSection): Disposable;
910
+ /**
911
+ * Hides settings by id — a section id drops the whole section, a row id drops that row. A
912
+ * lasting filter (like {@link ContributionRegistry.omit}), so a section a plugin registers
913
+ * later is covered too.
914
+ */
915
+ omit(ids: readonly string[]): void;
916
+ /**
917
+ * Opens the settings dialog — a bare, wide two-column surface owning its own chrome. Pass a
918
+ * `sectionId` to land on (or switch an already-open dialog to) that section — e.g. the plugin
919
+ * store's gear action jumping to an installed plugin's own settings.
920
+ */
921
+ open(sectionId?: string): DialogRef;
922
+ consumeRequestedSection(): void;
923
+ static ɵfac: i0.ɵɵFactoryDeclaration<SettingsService, never>;
924
+ static ɵprov: i0.ɵɵInjectableDeclaration<SettingsService>;
925
+ }
926
+
927
+ /**
928
+ * The shared shape of the shell's two persistence ports: `SETTINGS_STORE` for genuine
929
+ * settings and `WORKING_STATE_STORE` for working state. A store implemented once fits either port —
930
+ * only what the shell routes through it differs.
931
+ *
932
+ * It is a plain **string** key-value store: callers serialise/validate their own payloads (they already
933
+ * do so defensively), which keeps the wire format identical to what the shell wrote before — no data
934
+ * migration. `get`/`set`/`delete` are async so a network-backed store fits; the optional {@link peek} is
935
+ * a synchronous fast-path for bootstrap-critical reads (theme, panel sizes) that must apply before the
936
+ * first paint. A store that cannot answer synchronously (network-backed) omits `peek`; callers then start
937
+ * from a default and reconcile via `get`.
938
+ */
939
+ interface KeyValueStore {
940
+ /**
941
+ * Reads the stored string for `key`, or `undefined` if absent. Untrusted — callers validate its shape.
942
+ * Best-effort like {@link set}: prefer resolving with `undefined` over rejecting when the read fails
943
+ * (a network store that is offline or unauthorized). The shell treats a rejection as "no value" rather
944
+ * than failing startup, but a store that rejects makes the user's data silently unavailable.
945
+ */
946
+ get(key: string): Promise<string | undefined>;
947
+ /** Writes `value` for `key`. Best-effort: implementations resolve even when persistence fails. */
948
+ set(key: string, value: string): Promise<void>;
949
+ /** Removes the value for `key`. */
950
+ delete(key: string): Promise<void>;
951
+ /**
952
+ * Optional synchronous snapshot of `key` for bootstrap-critical reads that must apply before first
953
+ * paint. Omitted by stores that can only answer asynchronously — callers fall back to {@link get}.
954
+ */
955
+ peek?(key: string): string | undefined;
956
+ }
957
+ /**
958
+ * Default {@link KeyValueStore}: user-local `localStorage`. Reads and writes are synchronous
959
+ * under the hood — `peek` exposes that for zero-flash bootstrap — and wrapped in the same best-effort
960
+ * try/catch the shell used before (private browsing, quota, corrupt payloads). Both persistence ports
961
+ * default to an instance of this store.
962
+ */
963
+ declare class LocalStorageStore implements KeyValueStore {
964
+ peek(key: string): string | undefined;
965
+ get(key: string): Promise<string | undefined>;
966
+ set(key: string, value: string): Promise<void>;
967
+ delete(key: string): Promise<void>;
968
+ }
969
+
970
+ /**
971
+ * The **settings** persistence port: deliberate user decisions only — theme,
972
+ * language, text size, plugin settings, installed/disabled plugins, capability revocations and the
973
+ * saved-workspaces list. Working state (view state, layout, usage traces) never flows through this
974
+ * port; it lives behind `WORKING_STATE_STORE`. That guarantee is structural, which is what makes
975
+ * this port the right seam for a product backend: writes are rare, small and roaming-worthy, so a
976
+ * REST-backed implementation (the `ISettingsRepository` pattern) receives exactly
977
+ * what it expects. Swap the backing store with {@link provideSettingsStore}; the built-in default
978
+ * is {@link LocalStorageStore}.
979
+ */
980
+ declare const SETTINGS_STORE: InjectionToken<KeyValueStore>;
981
+ /**
982
+ * Provides a custom settings {@link KeyValueStore}, replacing the default `localStorage` one —
983
+ * place after `provideShell()` (last provider wins). Pass a class (DI-constructed) or a ready
984
+ * instance. The cross-tab sync wrapper is applied on top either way.
985
+ */
986
+ declare function provideSettingsStore(store: KeyValueStore | Type<KeyValueStore>): Provider;
987
+
988
+ /**
989
+ * The **working-state** persistence port: state that accrues from using the app rather
990
+ * than from deciding something — view state and view instances, the palette's recently-used list,
991
+ * and the window-local layout keys (pane trees, panel sizes, collapse state, item order, view
992
+ * placement). Writes are frequent and debounced, which is why this port defaults to the device
993
+ * ({@link LocalStorageStore}) instead of a product backend.
994
+ *
995
+ * A distribution that wants working state to travel across devices provides a backend-backed store
996
+ * with {@link provideWorkingStateStore}: a fresh tab then hydrates the last persisted state at boot
997
+ * with no further wiring. For *live* cross-device updates, pair the store with a push transport
998
+ * (SSE, WebSocket) that calls `StateSyncService.notifyRemoteChange` when the backend reports a
999
+ * changed key.
1000
+ */
1001
+ declare const WORKING_STATE_STORE: InjectionToken<KeyValueStore>;
1002
+ /**
1003
+ * Provides a custom working-state {@link KeyValueStore}, replacing the default `localStorage` one —
1004
+ * place after `provideShell()` (last provider wins). Pass a class (DI-constructed) or a ready
1005
+ * instance. The cross-tab sync wrapper is applied on top either way.
1006
+ */
1007
+ declare function provideWorkingStateStore(store: KeyValueStore | Type<KeyValueStore>): Provider;
1008
+
1009
+ /**
1010
+ * The storage keys that stay **device-level** by default when the stores are identity-scoped
1011
+ *: preferences that reasonably belong to the browser, not the signed-in user. Extend the
1012
+ * default via `provideIdentityScopedStores({ deviceKeys: [...DEVICE_LEVEL_KEYS, 'my.key'] })`.
1013
+ */
1014
+ declare const DEVICE_LEVEL_KEYS: readonly string[];
1015
+ /** Configuration for {@link provideIdentityScopedStores}. */
1016
+ interface IdentityScopedStoreOptions {
1017
+ /**
1018
+ * The identity discriminator, read synchronously. Return the current principal's stable id
1019
+ * (typically `AuthSnapshot.subject`; encode the tenant into it if tenant switches should
1020
+ * separate state). `null`/`undefined`/`''` means anonymous — keys pass through **unscoped**, so
1021
+ * a signed-out app behaves exactly like the unwrapped stores. Both stores **latch the first
1022
+ * non-empty value per boot** through one shared latch and never follow a live switch afterwards
1023
+ *: a change to a *different* subject only takes effect across the reload boundary
1024
+ * (`provideAuthSource(..., { onIdentityChange: 'reload' })`), so in-flight writes of the
1025
+ * departing user — a pending debounce, a commit during the login transition — can never land in
1026
+ * the next user's namespace. The shell peeks bootstrap-critical keys before first paint, so the
1027
+ * discriminator must be answerable synchronously at boot (persist the last-known subject
1028
+ * yourself).
1029
+ */
1030
+ identity: () => string | null | undefined;
1031
+ /**
1032
+ * Exact keys that stay unscoped regardless of identity. **Replaces** the default
1033
+ * ({@link DEVICE_LEVEL_KEYS}) — spread it to extend. Applied to both stores; the built-in
1034
+ * defaults are all settings keys, but a distribution may declare device-level keys of its own.
1035
+ */
1036
+ deviceKeys?: readonly string[];
1037
+ /** The wrapped settings store. Defaults to the built-in `localStorage` store. */
1038
+ settingsStore?: KeyValueStore;
1039
+ /** The wrapped working-state store. Defaults to the built-in `localStorage` store. */
1040
+ workingStateStore?: KeyValueStore;
1041
+ }
1042
+ /**
1043
+ * Scopes **both** persistence ports (`SETTINGS_STORE` and `WORKING_STATE_STORE`) per
1044
+ * signed-in user: every key outside `deviceKeys` is prefixed `lw.id.<identity>:` while
1045
+ * someone is signed in, so on a shared browser one user's pane trees, tab titles, workspaces,
1046
+ * view state and plugin settings are never re-hydrated for the next. Device-level keys and the
1047
+ * anonymous session write the original, unprefixed keys — a distribution without auth is
1048
+ * byte-identical to the unwrapped stores.
1049
+ *
1050
+ * Place after `provideShell()` (last provider wins). The standard answer to per-user state on a
1051
+ * shared browser; pair with `provideAuthSource(..., { onIdentityChange: 'reload' })` so a user
1052
+ * switch re-hydrates cleanly from the new namespace. The cross-tab sync wrapper is applied on top
1053
+ * of both stores; `peek` is offered only when the wrapped store offers it, because the
1054
+ * shell branches on the *presence* of `peek` (sync bootstrap vs. async hydration).
1055
+ */
1056
+ declare function provideIdentityScopedStores(options: IdentityScopedStoreOptions): Provider;
1057
+
1058
+ /**
1059
+ * Where a synced key's fresh value is read back from after another window announced a change
1060
+ *: the `SETTINGS_STORE`, the `WORKING_STATE_STORE`, or — for state persisted outside
1061
+ * both ports, such as a product session store — `'external'`, in which case no read-back happens
1062
+ * and the applier receives `undefined` (it re-reads its own storage).
1063
+ */
1064
+ type SyncSource = 'settings' | 'working-state' | 'external';
1065
+ /**
1066
+ * Re-applies a key that another browser window just wrote. `raw` is the value read back from the
1067
+ * registered {@link SyncSource}'s store (`undefined` for `'external'` registrations) — the store
1068
+ * stays the single source of truth, the message only names the key. An applier must **set its
1069
+ * state without persisting again**, or two windows would write back and forth forever.
1070
+ */
1071
+ type ApplySyncedState = (raw: string | undefined, key: string) => void;
1072
+ /**
1073
+ * Cross-tab live sync at the persistence seams. Every write through either port
1074
+ * broadcasts its **key** to the other windows of the same origin over a `BroadcastChannel`; a
1075
+ * window that has registered a reaction for that key reads the fresh value back through the
1076
+ * registered source's store and applies it — so it works with any store behind the ports,
1077
+ * including a product's HTTP-backed one.
1078
+ *
1079
+ * The shell registers its own keys (settings such as theme, language and text size on the
1080
+ * `'settings'` source; view state, view instances and the palette's recently-used list on
1081
+ * `'working-state'`), so plugins inherit the sync for free: their state lives in host-managed
1082
+ * stores. **Layout keys are deliberately not registered** — two windows are meant to be able to
1083
+ * show different layouts.
1084
+ *
1085
+ * A distribution registers its own keys the same way, most usefully its product session key
1086
+ * (source `'external'`): when it changes, the other window's `AuthSnapshot` flips and the existing
1087
+ * `onIdentityChange` policy takes over. Use {@link announce} when that state is
1088
+ * persisted outside the ports, and {@link notifyRemoteChange} when a backend push transport
1089
+ * reports a change made on another device.
1090
+ */
1091
+ declare class StateSyncService {
1092
+ private readonly settings;
1093
+ private readonly workingState;
1094
+ private readonly channel;
1095
+ private readonly exact;
1096
+ private readonly prefixes;
1097
+ constructor();
1098
+ /**
1099
+ * Reacts to remote writes of exactly `key`; `source` names the store the fresh value is read
1100
+ * back from. One applier per key — registering again replaces it. Returns a disposer that
1101
+ * unregisters this applier (a later re-registration always wins over a stale disposer).
1102
+ */
1103
+ register(source: SyncSource, key: string, apply: ApplySyncedState): () => void;
1104
+ /**
1105
+ * Reacts to remote writes of every key starting with `prefix` — for key families such as
1106
+ * `lw.shell.view-state:`. Exact registrations win over prefixes. Returns a disposer that
1107
+ * unregisters this applier (a later re-registration always wins over a stale disposer).
1108
+ */
1109
+ registerPrefix(source: SyncSource, prefix: string, apply: ApplySyncedState): () => void;
1110
+ /**
1111
+ * Announces a change this window made to state that is persisted **outside** the persistence
1112
+ * ports — a product session store, for example. Writes through the ports broadcast on their own;
1113
+ * this is the escape hatch for state the ports never see. Notifies only the **other** windows.
1114
+ */
1115
+ announce(key: string): void;
1116
+ /**
1117
+ * Runs the registered applier for `key` in **this** window, reading the fresh value back from
1118
+ * the registered source's store. The entry point for cross-device live sync: a
1119
+ * backend-backed store paired with a push transport (SSE, WebSocket) calls this when the backend
1120
+ * reports a key changed on another device. No-op for keys without a registration.
1121
+ */
1122
+ notifyRemoteChange(key: string): void;
1123
+ private applyRemote;
1124
+ private readBack;
1125
+ private registrationFor;
1126
+ static ɵfac: i0.ɵɵFactoryDeclaration<StateSyncService, never>;
1127
+ static ɵprov: i0.ɵɵInjectableDeclaration<StateSyncService>;
1128
+ }
1129
+
1130
+ /**
1131
+ * A developer-defined workspace a distribution ships with {@link provideWorkspaces}: the same thing a
1132
+ * user can build and save, only its baseline lives in code — it is never written to storage, the user
1133
+ * cannot overwrite, rename or delete it, and it moves with product updates. Switching, the automatic
1134
+ * per-workspace working state and **Reset** (back to this declaration) work exactly as for a
1135
+ * user-saved workspace.
1136
+ */
1137
+ interface WorkspaceDefinition {
1138
+ /**
1139
+ * Stable identity — keys the workspace's working state. `default` is reserved for the built-in
1140
+ * empty workspace; a duplicate id keeps the first declaration.
1141
+ */
1142
+ readonly id: string;
1143
+ /** Display name — a translation key; a literal string falls back to itself. */
1144
+ readonly title: string;
1145
+ /** Optional icon (registry name) shown next to the name in the workspace management UI. */
1146
+ readonly icon?: string;
1147
+ /**
1148
+ * Makes this the workspace a fresh install opens in, instead of the empty `default` one. It applies
1149
+ * **once**, on a first boot with nothing stored yet: from then on the user's own last choice wins,
1150
+ * so switching away is not undone by the next reload. A deep link still wins over the declaration —
1151
+ * the baseline is laid out, but an incoming address is navigated to, so a shared link opens what it
1152
+ * names. If two declarations set this, the first one wins, as with a duplicate id.
1153
+ */
1154
+ readonly initial?: boolean;
1155
+ /**
1156
+ * Which views each sidebar shows, keyed by the **panel region id** of your layout. A listed region
1157
+ * shows exactly the named views; everything else declared for it is hidden and can be brought back
1158
+ * through the strip's context menu. List a region with an **empty array** to show none of its views
1159
+ * — the sidebar itself stays, empty. A region you leave out, or omitting the whole field, keeps
1160
+ * whatever the user has there. **Whether a sidebar exists, is open and how wide it is belongs to the
1161
+ * window, not to the workspace**: your layout decides which sidebars the app has, the user decides
1162
+ * whether they are open, and switching workspaces never collapses, resizes or removes one.
1163
+ */
1164
+ readonly sidebars?: Readonly<Record<string, readonly string[]>>;
1165
+ /**
1166
+ * The content addresses that belong to this workspace, as route paths in the same vocabulary as
1167
+ * {@link content} — `quotes/:id` claims every quote document and everything below one. Reaching a
1168
+ * claimed address activates this workspace and shows the content inside it, **however the address
1169
+ * was reached**: a link followed into the application, a restart, a command, a programmatic
1170
+ * navigation or a tab a plugin opened. Without a claim an address is shown wherever the user
1171
+ * already is, which is the behaviour of every workspace that declares none.
1172
+ *
1173
+ * Where two workspaces claim addresses of the same shape, neither is narrower and the claim is
1174
+ * dropped from both with a message naming them; a narrower claim (more segments, or fewer
1175
+ * parameters at the same length) simply wins. A workspace the **user** saved is never a
1176
+ * destination, however it came by its claim — it exists on one machine only, and an address that
1177
+ * led somewhere different for every user would not be an address.
1178
+ */
1179
+ readonly claims?: readonly string[];
1180
+ /**
1181
+ * The content area as a recursive arrangement: an area either holds `tabs`, splits into `rows`
1182
+ * (top to bottom) or splits into `columns` (left to right). The **first** tabs area in reading
1183
+ * order becomes the URL pane; switching to the workspace navigates to its active tab. Omit to
1184
+ * start on the empty layout.
1185
+ */
1186
+ readonly content?: WorkspaceArea;
1187
+ }
1188
+ /**
1189
+ * One node of a {@link WorkspaceDefinition} content declaration — the shared {@link PaneArea} grammar
1190
+ * over route paths. A container declares its own arrangement in the same grammar over
1191
+ * child surface ids.
1192
+ */
1193
+ type WorkspaceArea = PaneArea<WorkspaceTabEntry>;
1194
+ type WorkspaceAreaBase = PaneAreaBase;
1195
+ /** An area that holds tabs. */
1196
+ type WorkspaceTabArea = PaneTabArea<WorkspaceTabEntry>;
1197
+ /** An area that splits into rows, top to bottom. */
1198
+ type WorkspaceRowArea = PaneRowArea<WorkspaceTabEntry>;
1199
+ /** An area that splits into columns, left to right. */
1200
+ type WorkspaceColumnArea = PaneColumnArea<WorkspaceTabEntry>;
1201
+ /** A tab in a workspace declaration — a route path, or the object form for the extra flags. */
1202
+ type WorkspaceTabEntry = string | WorkspaceTab;
1203
+ interface WorkspaceTab {
1204
+ /** The surface route path the tab opens (sidebar views belong under `sidebars`, not here). */
1205
+ readonly path: string;
1206
+ /**
1207
+ * `false` fixes the tab in this workspace: it shows no close affordance, "close others" and
1208
+ * "close all" spare it, and it cannot be dragged away. Reset restores it either way.
1209
+ */
1210
+ readonly closable?: boolean;
1211
+ /** Marks the area's initially active tab; without it the first tab is active. */
1212
+ readonly active?: boolean;
1213
+ }
1214
+
1215
+ /**
1216
+ * The developer-defined workspaces of the composition — each {@link provideWorkspaces} call
1217
+ * contributes one batch. Read by the workspace service; a distribution never injects this itself.
1218
+ */
1219
+ declare const WORKSPACE_DEFINITIONS: InjectionToken<readonly (readonly WorkspaceDefinition[])[]>;
1220
+ /**
1221
+ * Ships developer-defined workspaces with the distribution (composition root). They are switchable,
1222
+ * self-remembering (each keeps its live working state per workspace) and resettable to the
1223
+ * declaration — but their baseline lives in code, so the user cannot overwrite, rename or delete
1224
+ * them. The workspace dialog therefore lists them separately from the user's own, opening on
1225
+ * whichever list holds the active workspace. Invalid declarations are reported to
1226
+ * the console in dev mode, naming what is ignored; nothing fails silently at runtime.
1227
+ */
1228
+ declare function provideWorkspaces(...definitions: WorkspaceDefinition[]): EnvironmentProviders;
1229
+
1230
+ /**
1231
+ * The running build's version, sourced from `<Version>` in Directory.Build.props
1232
+ * at build time (see tools/stamp-version.mjs). Exposed as a signal so a
1233
+ * later source (e.g. a product backend) can update it live without touching consumers.
1234
+ *
1235
+ * Neutral core chrome: the version is host-offered data; distributions and plugins
1236
+ * render it via `<lw-version>` or `ctx`, never hardcoded.
1237
+ */
1238
+ declare class VersionService {
1239
+ /** SemVer of the running build, e.g. `0.1.0`. */
1240
+ readonly version: i0.WritableSignal<string>;
1241
+ static ɵfac: i0.ɵɵFactoryDeclaration<VersionService, never>;
1242
+ static ɵprov: i0.ɵɵInjectableDeclaration<VersionService>;
1243
+ }
1244
+
1245
+ /**
1246
+ * Detects when a new app version has been fetched by the service worker and offers
1247
+ * to activate it. Neutral core chrome: it raises a sticky toast and drives
1248
+ * the persistent update badge off {@link updateAvailable}. Dismissing the toast never
1249
+ * clears that signal, so the badge stays until the user actually reloads.
1250
+ *
1251
+ * `SwUpdate` is injected optionally: with no registered service worker (dev, tests,
1252
+ * unsupported browsers) the service is simply inert — {@link enabled} is `false` and
1253
+ * no update is ever offered.
1254
+ */
1255
+ declare class UpdateService {
1256
+ private readonly swUpdate;
1257
+ private readonly notifications;
1258
+ private readonly document;
1259
+ private readonly destroyRef;
1260
+ private readonly available;
1261
+ private readonly failed;
1262
+ private readonly broken;
1263
+ private lastSilentCheck;
1264
+ /** True once a new version is downloaded and ready to activate. */
1265
+ readonly updateAvailable: i0.Signal<boolean>;
1266
+ /**
1267
+ * True once an update could not be installed, whichever of the two ways it went wrong.
1268
+ * The client keeps running its current version until a reload; the badge and the failure
1269
+ * toast both offer that reload. Read {@link updateBroken} to tell the two apart.
1270
+ */
1271
+ readonly updateFailed: i0.Signal<boolean>;
1272
+ /**
1273
+ * True once the service worker reports an unrecoverable state, which is the harsher half of
1274
+ * {@link updateFailed}: its cached asset table no longer matches what the server serves, so it
1275
+ * cannot repair itself and a plain reload lands in the same state again. {@link activateUpdate}
1276
+ * handles it by dropping the worker rather than reloading into the same wall; read this only if
1277
+ * you want to say something different about it in your own UI.
1278
+ */
1279
+ readonly updateBroken: i0.Signal<boolean>;
1280
+ /** Whether update checks are possible (a service worker is registered and enabled). */
1281
+ readonly enabled: boolean;
1282
+ constructor();
1283
+ /** Manually checks for a new version, noting when already up to date. */
1284
+ checkForUpdate(): Promise<void>;
1285
+ /**
1286
+ * Gets the client onto a working version and reloads into it — it reloads even if that fails,
1287
+ * so the affordance is never a silent no-op.
1288
+ *
1289
+ * When the worker is {@link updateBroken} activating is pointless: the broken registration would
1290
+ * still control the next load and report the same failure, which is a loop the user cannot leave
1291
+ * from inside the app. There the shell unregisters its own worker and drops its caches first, so
1292
+ * the reload lands uncontrolled and registers afresh. Only the shell's own `ngsw-worker.js` and
1293
+ * the `ngsw:` caches are touched; anything else the product registered is left alone.
1294
+ */
1295
+ activateUpdate(): Promise<void>;
1296
+ private ensureControlled;
1297
+ private showReloadNeeded;
1298
+ private tryActivate;
1299
+ private dropWorker;
1300
+ private onVersionEvent;
1301
+ private onUpdateReady;
1302
+ private showUpdateAvailable;
1303
+ private onUpdateFailed;
1304
+ private onWorkerBroken;
1305
+ private showUpdateFailed;
1306
+ private startBackgroundChecks;
1307
+ private silentCheck;
1308
+ static ɵfac: i0.ɵɵFactoryDeclaration<UpdateService, never>;
1309
+ static ɵprov: i0.ɵɵInjectableDeclaration<UpdateService>;
1310
+ }
1311
+
1312
+ /**
1313
+ * The distribution-supplied session snapshot signal. Defaults to **anonymous** so the
1314
+ * bare platform runs with nobody signed in; a distribution maps its product's auth (OIDC/custom/…)
1315
+ * into an {@link AuthSnapshot} signal via {@link provideAuthSource}. LoomWeaver never owns auth — it
1316
+ * only reads this signal.
1317
+ */
1318
+ declare const AUTH_SOURCE: InjectionToken<Signal<AuthSnapshot>>;
1319
+ /**
1320
+ * What the shell does when one established identity is replaced by another.
1321
+ * The comparison anchor is {@link AuthSnapshot.subject}.
1322
+ */
1323
+ interface AuthSourceOptions {
1324
+ /**
1325
+ * `'reload'`: when a snapshot carries a **different** non-empty `subject` than the one already
1326
+ * established this app lifetime, the shell performs a full `location.reload()` — the
1327
+ * guaranteed-clean user switch (no pane trees, tab titles or plugin in-memory state of the
1328
+ * previous user survive; paired with an identity-scoped settings store, the new session
1329
+ * re-hydrates entirely from its own namespace). First sign-in (anonymous → subject) and
1330
+ * sign-out (subject → anonymous) never fire, so an async session restore at boot causes no
1331
+ * reload flicker. Snapshots without a `subject` never fire either. Default: `'ignore'`.
1332
+ */
1333
+ onIdentityChange?: 'reload' | 'ignore';
1334
+ }
1335
+ /**
1336
+ * A distribution declares its auth source. The factory runs in the injection context, so it can
1337
+ * `inject()` the product's own session/BFF service and return a signal derived from it. Pass
1338
+ * `options` to opt into the identity-change policy: with
1339
+ * `{ onIdentityChange: 'reload' }` a user switch on a shared browser reloads the app instead of
1340
+ * leaving the previous user's in-memory state alive.
1341
+ */
1342
+ declare function provideAuthSource(factory: () => Signal<AuthSnapshot>, options?: AuthSourceOptions): EnvironmentProviders;
1343
+ /**
1344
+ * Reactive read-only view of the current session, plus the gating predicates the host chrome uses to
1345
+ * hide/disable contributions. Roles/claims are opaque — this service matches, never
1346
+ * interprets. Client-side gating is presentation, not a security boundary.
1347
+ */
1348
+ declare class AuthContext {
1349
+ private readonly source;
1350
+ /** The current session snapshot (reactive). */
1351
+ readonly state: Signal<AuthSnapshot>;
1352
+ /** Whether someone is signed in. */
1353
+ readonly authenticated: Signal<boolean>;
1354
+ /** The current principal's roles. */
1355
+ readonly roles: Signal<readonly string[]>;
1356
+ /** True if the current principal holds `role`. */
1357
+ hasRole(role: string): boolean;
1358
+ /** True if the current session satisfies `access` (undefined = always). */
1359
+ meets(access: AccessRequirement | undefined): boolean;
1360
+ /** Whether a chrome item with this requirement should render (unmet + hide → false). */
1361
+ visible(access: AccessRequirement | undefined): boolean;
1362
+ /** Whether a rendered chrome item with this requirement should be disabled (unmet + disable). */
1363
+ disabled(access: AccessRequirement | undefined): boolean;
1364
+ static ɵfac: i0.ɵɵFactoryDeclaration<AuthContext, never>;
1365
+ static ɵprov: i0.ɵɵInjectableDeclaration<AuthContext>;
1366
+ }
1367
+
1368
+ /** What the user picked. `system` follows the OS `prefers-color-scheme`. */
1369
+ type ThemeMode = 'light' | 'dark' | 'system';
1370
+ /** What is actually being rendered, once `system` has been resolved against the OS. */
1371
+ type ResolvedTheme = 'light' | 'dark';
1372
+ /**
1373
+ * Owns light/dark for the whole application: it persists the user's choice through the
1374
+ * the settings store, mirrors it across tabs, and toggles the `dark` class on `<html>` — which is what
1375
+ * flips the `--lw-*` token ladder every surface reads.
1376
+ *
1377
+ * Inject it when your own UI has to follow the same mode, most commonly to mirror it onto another
1378
+ * framework's switch (Bootstrap's `data-bs-theme`, for example) so the two never disagree:
1379
+ *
1380
+ * ```ts
1381
+ * const theme = inject(ThemeService);
1382
+ * effect(() => {
1383
+ * document.documentElement.setAttribute('data-bs-theme', theme.resolvedTheme());
1384
+ * });
1385
+ * ```
1386
+ */
1387
+ declare class ThemeService {
1388
+ private readonly document;
1389
+ private readonly store;
1390
+ private readonly sync;
1391
+ private readonly systemDark;
1392
+ private readonly modeState;
1393
+ /** The user's choice, including `system`. Use {@link resolvedTheme} to know what is rendered. */
1394
+ readonly mode: Signal<ThemeMode>;
1395
+ /** The mode in effect, with `system` already resolved against the OS preference. */
1396
+ readonly resolvedTheme: Signal<ResolvedTheme>;
1397
+ constructor();
1398
+ /** Switch the mode and persist it. Other tabs follow through the settings sync. */
1399
+ setMode(mode: ThemeMode): void;
1400
+ private watchSystemPreference;
1401
+ static ɵfac: i0.ɵɵFactoryDeclaration<ThemeService, never>;
1402
+ static ɵprov: i0.ɵɵInjectableDeclaration<ThemeService>;
1403
+ }
1404
+
1405
+ /**
1406
+ * The distribution's router, set up for the content area — call this **instead of**
1407
+ * `provideRouter([])`. It bundles the three pieces that make plugin-contributed content routes work
1408
+ * as one foolproof unit, so a distribution author cannot forget the initial-navigation flag:
1409
+ *
1410
+ * - `withDisabledInitialNavigation()` — the router does not navigate until the plugin routes exist.
1411
+ * - `ContentReuseStrategy` — keeps a surface's instance across tab/perspective switches when its
1412
+ * retention policy says so (`retain: 'always'` or a `retention: 'retain'` default).
1413
+ * - an app-initializer that runs {@link ContentRouter.start} after plugins activate: it mirrors the
1414
+ * registered content routes into the router and then performs the deferred initial navigation.
1415
+ *
1416
+ * Pass `extraRoutes` for any non-content routes the distribution owns.
1417
+ */
1418
+ declare function provideShellRouter(extraRoutes?: Routes): (Provider | EnvironmentProviders)[];
1419
+
1420
+ /**
1421
+ * A distribution's redirect for a gated content route the session cannot reach. Given the
1422
+ * attempted path, it returns an in-app URL to redirect to (e.g. the product's login route), or `null`
1423
+ * to fall back to the host's neutral "sign-in required" placeholder. LoomWeaver owns no login UI, so
1424
+ * this is how a product plugs in its own auth flow. Defaults to `null` (placeholder).
1425
+ */
1426
+ type UnauthorizedHandler = (attemptedPath: string) => string | null;
1427
+ /** A distribution registers a login redirect for gated content routes. */
1428
+ declare function provideUnauthorizedRedirect(handler: UnauthorizedHandler): Provider;
1429
+
1430
+ /** One rendered tab in the content strip (static or dynamic), as the content-area template consumes it. */
1431
+ interface ContentTabView {
1432
+ /** The tab **root** — stable identity (tracking, active compare, close). */
1433
+ readonly path: string;
1434
+ /** The full path to navigate to when the tab is selected (root + remembered sub-route). */
1435
+ readonly navPath: string;
1436
+ readonly title: string;
1437
+ /** When true the host shows `title` verbatim; otherwise it is a Transloco key (finding #8). */
1438
+ readonly literalTitle: boolean;
1439
+ readonly icon?: string;
1440
+ readonly order: number;
1441
+ /** Dynamic tabs show a close affordance; static (registered) tabs do not. */
1442
+ readonly closable: boolean;
1443
+ /** A preview tab renders its title in italics until promoted. */
1444
+ readonly preview: boolean;
1445
+ /** A pinned tab is sorted to the group's front and shows an unpin control instead of close. */
1446
+ readonly pinned: boolean;
1447
+ readonly actions?: readonly ViewAction[];
1448
+ }
1449
+
1450
+ /**
1451
+ * A navigable content target the command palette's Quick-Open mode lists:
1452
+ * a registered surface reachable by plain navigation (a parameterless, non-chromeless route), or a
1453
+ * currently **open** tab — across every workspace pane. `lastActive` is a session-only epoch stamp of
1454
+ * when the target was last the active tab — it is not persisted (a fresh window starts with no
1455
+ * times), and it floats recently visited targets up.
1456
+ */
1457
+ interface QuickOpenTarget {
1458
+ /** The tab root — stable identity and the `tabId` for the tab context menu. */
1459
+ readonly path: string;
1460
+ /** The full path to navigate to (open: root + remembered sub-route; else the route path). */
1461
+ readonly navPath: string;
1462
+ readonly title: string;
1463
+ readonly literalTitle: boolean;
1464
+ readonly icon?: string;
1465
+ readonly pinned: boolean;
1466
+ /** A target that is not an open tab is not closable — the context menu hides Close/Split/Pin. */
1467
+ readonly closable: boolean;
1468
+ readonly lastActive?: number;
1469
+ }
1470
+
1471
+ /**
1472
+ * The **URL pane's** tab state: the tabs to show (facet tabs of `follows`
1473
+ * surfaces + opened ones), and the active tab — all derived from the URL plus the open set. The open
1474
+ * set lives in the **pane tree's primary leaf**: the URL pane's tab group works like any
1475
+ * pane's, so its tabs persist and reload with the tree (R10); only the non-serialisable `onClose`
1476
+ * hooks stay session-local. A tab is identified by its **tab root**; sub-routes (`subRoutes`) live
1477
+ * under it, so switching sub-routes stays in one tab and is reflected in the URL. A **chromeless**
1478
+ * surface shows no strip.
1479
+ */
1480
+ declare class ContentTabsService {
1481
+ private readonly state;
1482
+ private readonly closing;
1483
+ private readonly registry;
1484
+ private readonly reuse;
1485
+ private readonly features;
1486
+ private readonly paneTree;
1487
+ private readonly closeHooks;
1488
+ /**
1489
+ * The Quick-Open source for the command palette: every currently **open** tab across
1490
+ * **all content panes** (a split's secondary panes contribute theirs too), plus every registered
1491
+ * route the user could open — one that takes no route parameter and is not `chromeless`. A route
1492
+ * the user has visited carries its session `lastActive`; an open tab wins any path collision (it
1493
+ * keeps its pinned identity). View tabs are excluded — those are reached through their rail item.
1494
+ *
1495
+ * **Access:** an unopened route is offered only when the session meets its `access` requirement. An
1496
+ * already-open tab is listed as it is — matching the tab strip, which does not re-gate content tabs
1497
+ * either, which is a deliberate open point. Client-side gating is
1498
+ * presentation, not enforcement: opening the target still passes the router's `canMatch` twin, and an
1499
+ * off-router mount re-checks the session, so a target the session no longer qualifies for renders the
1500
+ * neutral placeholder rather than its content.
1501
+ */
1502
+ readonly quickOpenTargets: Signal<readonly QuickOpenTarget[]>;
1503
+ /** The active `view:` tab of the URL group, or `null` when the router drives the area (R9). */
1504
+ readonly activeViewPath: Signal<string | null>;
1505
+ readonly activeViewInstance: Signal<string | undefined>;
1506
+ /** Active content path (no leading slash, no query), including the sub-route, e.g. `doc/abc/preview`. */
1507
+ readonly activePath: Signal<string>;
1508
+ /** The active tab root — the leading segments the active route matched (e.g. `doc/abc`). */
1509
+ readonly activeTabRoot: Signal<string>;
1510
+ /** The active content read a plugin reaches through `ctx.activeContent` (finding #19). */
1511
+ readonly activeContent: Signal<ActiveContent | null>;
1512
+ /**
1513
+ * Whether the tab strip renders: whenever the pane holds tabs — and never while a **chromeless**
1514
+ * surface (login, onboarding) is active. That is the whole rule since groups retired:
1515
+ * a pane shows a strip when it holds tabs; a chromeless surface shows none.
1516
+ */
1517
+ readonly showStrip: Signal<boolean>;
1518
+ /**
1519
+ * Everything the strip holds: the permanent facet tabs (`follows` surfaces with a computed
1520
+ * address), then the open tabs, then the view tabs. Groups retired — every open tab
1521
+ * renders, in every workspace.
1522
+ */
1523
+ readonly tabs: Signal<readonly ContentTabView[]>;
1524
+ /**
1525
+ * Activates a `view:` tab of the URL group (O5/E7 — a view living as a tab beside the content tabs):
1526
+ * the content area host-mounts the view; the URL stays at its last route. No-op unless the group
1527
+ * actually holds that tab.
1528
+ */
1529
+ activateViewTab(path: string): void;
1530
+ /**
1531
+ * Applies a user drag/keyboard reorder of the strip's dynamic tabs. `ids` is the new full
1532
+ * order of dynamic tab roots (pinned then unpinned — the directive keeps moves within their band).
1533
+ *
1534
+ * The order is written to the pane that holds the tabs, exactly as a reorder in any other pane is.
1535
+ * It used to be kept beside the tree under one key for the whole content dock, which meant the URL
1536
+ * pane sorted its strip by that key while every other pane rendered the tabs in the order the tree
1537
+ * held them — so the same tabs changed order as the URL role moved between panes (TreeWeaver #41).
1538
+ */
1539
+ reorder(ids: string[]): void;
1540
+ /**
1541
+ * Moves the tab rooted at `path` to the front of the strip's unpinned band (MRU). The strip does not
1542
+ * scroll: tabs that no longer fit are clipped away, reachable via the overflow
1543
+ * dropdown or the Library. Reopening such a tab front-inserts it here so it becomes visible while the
1544
+ * least-recent tabs drop off the end. No-op for a pinned (anchored) or unknown tab. It moves the tab
1545
+ * in the pane's own tab list, exactly as a drag-reorder does, so every pane shows the one
1546
+ * order; the host calls it only when the active tab is actually clipped, so tabs that already fit
1547
+ * keep their order.
1548
+ */
1549
+ bringToFront(path: string): void;
1550
+ /**
1551
+ * Navigates the content area to a path (a full-area screen, another tab, a specific sub-route).
1552
+ *
1553
+ * If another pane already holds that tab, **that pane becomes the URL pane** and the tab is activated
1554
+ * there instead of a second copy opening beside the current one: exactly one pane carries the
1555
+ * address, and the address follows the focused pane. Without this, a workspace that parks a surface
1556
+ * in its own pane would gain a duplicate of it the moment a rail item or command navigated there, because
1557
+ * every visit opens a tab.
1558
+ *
1559
+ * Ends any view-tab selection here and marks the navigation as ours, so the URL effect — which exists
1560
+ * to end the selection on **external** moves like back/forward or a deep link — leaves alone whatever
1561
+ * the caller selects once this settles.
1562
+ *
1563
+ * **A no-op in a pop-out window**, with a dev-mode warning. A pop-out shows exactly one surface and
1564
+ * has no tab strip; navigating it would take its address out of `/popout/…`, and the window would
1565
+ * quietly stop being a pop-out — chrome-less until the next reload, and the full app after it. Same
1566
+ * reasoning as a docked surface, whose `navigate` is a no-op for want of a content area.
1567
+ */
1568
+ navigate(path: string): Promise<boolean>;
1569
+ /**
1570
+ * Fire-and-forget navigation: like {@link navigate}, but owns the "navigation may fail"
1571
+ * semantics — a rejected router navigation is logged instead of surfacing as an unhandled
1572
+ * rejection. Call sites that do not await the outcome use this.
1573
+ */
1574
+ navigateTo(path: string): void;
1575
+ /**
1576
+ * Reveals a content tab **where it already lives** (Quick-Open): a tab held by another pane is
1577
+ * activated there and that pane takes the address; anything else simply navigates. Either way the tab is
1578
+ * never duplicated into the URL pane. Since {@link navigate} carries that rule, this is the name the
1579
+ * Quick-Open call site reads by — the behaviour is the same for every way of reaching a tab.
1580
+ */
1581
+ revealContentTab(navPath: string): void;
1582
+ /**
1583
+ * Opens a titled dynamic tab (idempotent per tab root; re-opening restores its sub-route) and activates
1584
+ * it. With `preview` (and preview enabled) a **new** tab uses the strip's **single reused preview slot**
1585
+ *: a preview open for a *different* path replaces that slot in place (the old instance is
1586
+ * evicted + its `onClose` runs). Re-opening an **existing** tab preserves its current preview state (a
1587
+ * mere title/sub-route refinement never promotes); promotion is explicit via {@link keep}.
1588
+ */
1589
+ open(input: OpenTabInput): void;
1590
+ /**
1591
+ * Promotes the preview tab rooted at `path` to a permanent tab — the programmatic
1592
+ * "Keep Open" (`ctx.keepContentTab`, a double-click, or an edit). No-op if it is already permanent.
1593
+ */
1594
+ keep(path: string): void;
1595
+ /**
1596
+ * Pins the tab rooted at `path`: it moves to the front of the strip — after the tabs
1597
+ * already pinned — and its close control becomes an unpin control (guarding against accidental
1598
+ * close). Pinning also promotes a preview tab (a pinned tab is never transient). No-op if the tab is
1599
+ * not open.
1600
+ */
1601
+ pin(path: string): void;
1602
+ /**
1603
+ * Unpins the tab rooted at `path` — it returns to a normal, closable tab and takes the
1604
+ * first seat after the tabs still pinned. No-op otherwise.
1605
+ */
1606
+ unpin(path: string): void;
1607
+ /** Closes every dynamic tab in the strip except the target — pinned and unclosable tabs are kept. */
1608
+ closeOthers(path: string): void;
1609
+ /** Closes every dynamic tab in the strip — pinned and unclosable tabs are kept. */
1610
+ closeAll(): void;
1611
+ /** Closes the dynamic tabs that render after the target in the strip — pinned tabs are kept. */
1612
+ closeToRight(path: string): void;
1613
+ /**
1614
+ * Closes the **primary (URL) pane** of a split — the pane-toolbar "Close pane" on the URL pane: the
1615
+ * primary leaf collapses, a neighbour is promoted to URL pane and navigated to. Guarded like every
1616
+ * other user-initiated close: unsaved changes in any of the primary group's tabs run
1617
+ * the host's Save · Discard · Cancel dialog first.
1618
+ */
1619
+ closePrimaryPane(): void;
1620
+ /**
1621
+ * Closes a dynamic tab (by any path under its root). If it was active, we navigate to a neighbour
1622
+ * **first**, then evict the stored instance (keyed by the tab root — see {@link ContentReuseStrategy});
1623
+ * a background tab is evicted immediately.
1624
+ */
1625
+ close(path: string): void;
1626
+ /**
1627
+ * Runs (and clears) the close hook of the tab rooted at `path` — for a close that happens **outside**
1628
+ * the URL group (a moved tab closed in another pane). No-op while the tab is still open
1629
+ * in the URL group (its own {@link close} will run the hook).
1630
+ */
1631
+ runCloseHook(path: string): void;
1632
+ /**
1633
+ * The URL pane's neighbour of the tab rooted at `path` — where the URL goes when that tab **leaves**
1634
+ * the pane (moved away) or closes: the last remaining sibling, else home (`''` — the
1635
+ * "no file is open" default).
1636
+ */
1637
+ neighbourOf(path: string): string;
1638
+ private repin;
1639
+ private replacePreviewSlot;
1640
+ private refineElsewhere;
1641
+ static ɵfac: i0.ɵɵFactoryDeclaration<ContentTabsService, never>;
1642
+ static ɵprov: i0.ɵɵInjectableDeclaration<ContentTabsService>;
1643
+ }
1644
+
1645
+ /** What the host knows when it works out where a following tab should point. */
1646
+ interface TabAddressInput {
1647
+ /** The following surface's id, when it declared one. */
1648
+ readonly surfaceId?: string;
1649
+ /** That surface's route pattern, e.g. `cedents/:cedentId/programs/:programId/treaties`. */
1650
+ readonly pattern: string;
1651
+ /** Parameter values of the address the user is on, by name. */
1652
+ readonly params: Readonly<Record<string, string>>;
1653
+ /** The full active content path, for a resolver that needs more than the names. */
1654
+ readonly activePath: string;
1655
+ }
1656
+ /**
1657
+ * A distribution's own answer to "where should this following tab point?" — part of the mapping is
1658
+ * domain knowledge the platform cannot have (a query parameter on one tab deciding a path segment on
1659
+ * another). Return `null` to fall back to the host's substitution.
1660
+ */
1661
+ type TabAddressResolver = (input: TabAddressInput) => string | null;
1662
+ /**
1663
+ * Overrides or extends how the host computes the address of a **following** tab
1664
+ * (`routable: { follows: true }`). The platform supplies the default — the parameter values of the
1665
+ * current address substituted by name into the tab's own pattern — and the distribution supplies the
1666
+ * domain knowledge on top; returning `null` keeps the default for that tab.
1667
+ */
1668
+ declare function provideTabAddressResolver(resolve: TabAddressResolver): EnvironmentProviders;
1669
+
1670
+ interface Triggerable {
1671
+ readonly command?: string;
1672
+ run?(): void | Promise<void>;
1673
+ }
1674
+ /**
1675
+ * Executes commands by id — the single seam every trigger flows through:
1676
+ * rail/bar/view-action items, and (next) keybindings and the command palette. Keeping execution
1677
+ * here (not on the registry, which only stores) means one place resolves an id, one place fires the
1678
+ * behaviour, and one place reports a failure.
1679
+ */
1680
+ declare class CommandService {
1681
+ private readonly registry;
1682
+ private readonly auth;
1683
+ private readonly errors;
1684
+ private readonly shortcuts;
1685
+ private readonly inPopout;
1686
+ /** The registered commands — the source list for a command palette. */
1687
+ readonly commands: Signal<readonly Command[]>;
1688
+ /**
1689
+ * Whether this command can run **here and now**: the session meets its `access`, and
1690
+ * this window is one it belongs in — a pop-out offers only commands that declare `popout`, since it
1691
+ * has no content area, rail or sidebar for the rest to reach. A palette uses this to filter.
1692
+ */
1693
+ available(command: Command): boolean;
1694
+ /**
1695
+ * The command's keyboard chord, formatted for display and OS-correct (`'⌘K'` / `'Ctrl+K'`), or
1696
+ * `undefined` where it has none — **or where this distribution has switched shortcuts off**, so a
1697
+ * hint never promises a key that does nothing. Every place the shell prints a chord (menu entry,
1698
+ * palette row, bar button) asks here.
1699
+ */
1700
+ shortcutOf(command: Command | undefined): string | undefined;
1701
+ /**
1702
+ * Runs the command with this id; a no-op (with a warning) if none is registered. Blocks — the one
1703
+ * choke point every trigger flows through (keybinding, palette, item) — when the session does not
1704
+ * meet the command's `access`. An optional {@link MenuContext} is forwarded to
1705
+ * `command.run(context)` — how a menu tells `shell.tab.closeOthers` which tab.
1706
+ */
1707
+ execute(id: string, context?: MenuContext): void;
1708
+ /**
1709
+ * Fires a resolved command's behaviour — the one place that happens, whatever triggered it — and
1710
+ * reports a failure the way every trigger already does. Answers what the command returned, and
1711
+ * rejects with what it threw, so a caller that has to tell a failure from an answer can.
1712
+ */
1713
+ run(command: Command, context?: MenuContext, args?: CommandArguments): Promise<unknown>;
1714
+ /**
1715
+ * Whether this item's trigger leads anywhere: an inline callback, or a command that is actually
1716
+ * registered. A rail or bar item naming a command nobody registered would render as a dead button
1717
+ * — the same orphan a menu entry can be, and the host drops both rather than draw them.
1718
+ */
1719
+ triggerable(item: Triggerable): boolean;
1720
+ /** Fires a UI item's trigger: its bound command if it names one, else its inline callback. */
1721
+ trigger(item: Triggerable): void;
1722
+ private invoke;
1723
+ private onError;
1724
+ static ɵfac: i0.ɵɵFactoryDeclaration<CommandService, never>;
1725
+ static ɵprov: i0.ɵɵInjectableDeclaration<CommandService>;
1726
+ }
1727
+
1728
+ /**
1729
+ * Binds command shortcuts to the keyboard. Bindings are *derived* from the registered
1730
+ * commands' `shortcut` (no separate registration path) and rebuild reactively as commands come and
1731
+ * go. One global `keydown` listener resolves the pressed chord to a command id and fires it through
1732
+ * the {@link CommandService} — the same seam a click uses. User rebinding is deferred.
1733
+ */
1734
+ declare class KeybindingService {
1735
+ private readonly registry;
1736
+ private readonly commands;
1737
+ private readonly document;
1738
+ private readonly destroyRef;
1739
+ private readonly injector;
1740
+ private readonly enabled;
1741
+ private readonly isMac;
1742
+ private started;
1743
+ private readonly build;
1744
+ private readonly bindings;
1745
+ /** Attaches the global listener once; auto-detached on destroy. No-op where shortcuts are off. */
1746
+ start(): void;
1747
+ private onKeydown;
1748
+ static ɵfac: i0.ɵɵFactoryDeclaration<KeybindingService, never>;
1749
+ static ɵprov: i0.ɵɵInjectableDeclaration<KeybindingService>;
1750
+ }
1751
+
1752
+ /**
1753
+ * Formats a keyboard chord for display, OS-correct: `formatChord('mod+k')` → `'⌘K'` on macOS,
1754
+ * `'Ctrl+K'` elsewhere. `mod` maps to ⌘/Ctrl, `alt` to ⌥/Alt, `shift` to ⇧/Shift, and so on — the
1755
+ * same token vocabulary a {@link Command.shortcut} uses. Lets a distribution or weaver show a
1756
+ * shortcut anywhere without duplicating the platform detection the shell already does internally.
1757
+ */
1758
+ declare function formatChord(chord: string): string;
1759
+
1760
+ /** Where the built command-palette entry goes (LWF-05). Defaults: top bar, end slot, order 5. */
1761
+ interface CommandPaletteEntryOptions {
1762
+ /**
1763
+ * Target Bar region id. Default `'top-bar'`. The badge adapts to the bar it lands in: a top bar is
1764
+ * a fixed band, so there it pins the shared bar-control height and lines up with the theme and
1765
+ * language controls; a bottom bar takes the height of its tallest item, so there it renders like a
1766
+ * plain bar item rather than growing the bar and costing the content area.
1767
+ */
1768
+ readonly bar?: string;
1769
+ /** Which Bar slot the entry renders in. Default `'end'`. */
1770
+ readonly slot?: BarSlot;
1771
+ /** Lower renders first within the slot. Default `5` (left of the built update/language/theme items). */
1772
+ readonly order?: number;
1773
+ }
1774
+ /**
1775
+ * Places a built command-palette entry in a Bar (LWF-05) — a badge-styled affordance (search icon +
1776
+ * the palette's OS-correct shortcut) that opens `shell.commandPalette`, correct-by-construction and
1777
+ * without a distribution component. Opt-in: omit it for a palette that opens only by shortcut. Uses the
1778
+ * `shell.commandPaletteEntry` bar-item id, so `provideShell({ omit: ['shell.commandPaletteEntry'] })`
1779
+ * removes it.
1780
+ */
1781
+ declare function provideCommandPaletteEntry(options?: CommandPaletteEntryOptions): EnvironmentProviders;
1782
+
1783
+ /** Multi-provider token: each contribution adds one plugin to load. */
1784
+ declare const PLUGIN: InjectionToken<readonly Plugin[]>;
1785
+ /**
1786
+ * Activates the registered plugins. Trusted in-process runtime (the lowest isolation
1787
+ * rung); this abstraction lets a sandboxed runtime slot in later
1788
+ * without touching plugins or the host.
1789
+ */
1790
+ declare class PluginRuntime {
1791
+ private readonly grants;
1792
+ private readonly enablement;
1793
+ private readonly factory;
1794
+ private readonly injector;
1795
+ private readonly plugins;
1796
+ private readonly active;
1797
+ private started;
1798
+ activateAll(): void;
1799
+ /**
1800
+ * Unloads a plugin: undoes every contribution it made (via its context's tracked
1801
+ * disposables) and runs its optional `deactivate` hook. Idempotent — unknown/inactive
1802
+ * ids are a no-op. This is the counterpart to {@link activateAll} that keeps the `active`
1803
+ * map from being write-only (a real unload path, needed before an untrusted loader lands).
1804
+ */
1805
+ deactivate(id: string): void;
1806
+ /** Unloads every active plugin (e.g. on teardown). */
1807
+ deactivateAll(): void;
1808
+ private reconcile;
1809
+ private activate;
1810
+ private onActivationError;
1811
+ static ɵfac: i0.ɵɵFactoryDeclaration<PluginRuntime, never>;
1812
+ static ɵprov: i0.ɵɵInjectableDeclaration<PluginRuntime>;
1813
+ }
1814
+ /** A distribution registers its plugins; they are activated eagerly at startup. */
1815
+ declare function providePlugins(...plugins: Plugin[]): (Provider | EnvironmentProviders)[];
1816
+
1817
+ /**
1818
+ * How much the browser holds a frame plugin back. **Isolated** strips the frame of an origin, which
1819
+ * is what denies it the hosting document, any storage and any session the browser would carry for
1820
+ * it. **Embedded** lets it keep an origin, and with it whatever the browser grants that origin — a
1821
+ * separation of deployments rather than of privileges.
1822
+ *
1823
+ * Isolated is the default: a plugin whose level was never stated runs isolated.
1824
+ */
1825
+ type PluginIsolationLevel = 'isolated' | 'embedded';
1826
+
1827
+ /**
1828
+ * A **sandboxed** plugin a distribution registers: its code is not an in-process
1829
+ * {@link Plugin} object but a URL to an isolated document, loaded into an `<iframe sandbox>` and given
1830
+ * `ctx` over RPC. Contrast {@link providePlugins} (trusted, in-process).
1831
+ */
1832
+ interface FramePlugin {
1833
+ /** Stable plugin id — the same id the distribution grants capabilities to (default-deny). */
1834
+ readonly id: string;
1835
+ /**
1836
+ * What the workbench calls this plugin where it names it to the user — the permissions surface,
1837
+ * the plugin list. Omit it and the id is shown, which is a poor name but a correct one: nothing is
1838
+ * derived from it. Grants, collisions and the user's stored decisions all follow {@link id}, never
1839
+ * this, so naming a plugin changes what is read and nothing else.
1840
+ */
1841
+ readonly name?: string;
1842
+ /** URL of the plugin's entry document (served by the distribution, e.g. `/my-plugin/plugin.html`). */
1843
+ readonly entryUrl: string;
1844
+ /** Capabilities the plugin declares it needs; the distribution still has to grant them. */
1845
+ readonly capabilities?: readonly Capability[];
1846
+ /**
1847
+ * Origins this plugin's own surfaces may be served from, beyond the application's own — the seam
1848
+ * refuses anything else, and refuses an address that would execute or carry its content inline at
1849
+ * any level. Omit it and the application's own origin is the only one, which is the right answer
1850
+ * for a plugin whose files the distribution serves itself.
1851
+ *
1852
+ * A sibling subdomain belongs here: it is what gives an embedded application its own storage and
1853
+ * keeps it out of the hosting document, while a session cookie scoped to the shared domain still
1854
+ * reaches it.
1855
+ */
1856
+ readonly origins?: readonly string[];
1857
+ /**
1858
+ * How much the browser holds this plugin back. Omitted means
1859
+ * {@link PluginIsolationLevel `'isolated'`} — the frame is stripped of an origin and reaches
1860
+ * neither the hosting document nor any storage. `'embedded'` lets it keep an origin, which is what
1861
+ * a first-party application composed for its own deployment needs and what a plugin you did not
1862
+ * write must never be given.
1863
+ */
1864
+ readonly level?: PluginIsolationLevel;
1865
+ }
1866
+ /** Multi-provider token: each contribution adds one sandboxed plugin to load. */
1867
+ declare const FRAME_PLUGIN: InjectionToken<readonly FramePlugin[]>;
1868
+ /**
1869
+ * Second {@link PluginRuntime} implementation:
1870
+ * runs each plugin in an isolated `<iframe sandbox="allow-scripts">` and hands it `ctx` over **Penpal**
1871
+ * RPC. The RPC endpoints are backed by the **same** {@link HostPluginContext} the trusted runtime uses,
1872
+ * so the default-deny capability broker enforces grants identically — the isolation and
1873
+ * transport change, the broker does not. Data-oriented `ctx` calls (`registerRoute({iframe})`, `toast`)
1874
+ * serialise across the boundary; the reserved Angular-only surface (`component`) never crosses it.
1875
+ *
1876
+ * Activation reconciles against the union of three sets: the composed {@link FramePlugin} list,
1877
+ * what the operator deployed through the catalog, and what the user installed. Installing spawns a
1878
+ * plugin live, uninstalling unloads it, and a catalog that stops carrying a deployed entry unloads
1879
+ * that one — all without a reload. Authority decides an id collision: composed wins over deployed,
1880
+ * and deployed wins over installed, because a deployed entry holds exactly what it names and a
1881
+ * user's consent cannot narrow what the operator issued.
1882
+ */
1883
+ declare class FramePluginRuntime {
1884
+ private readonly grants;
1885
+ private readonly enablement;
1886
+ private readonly install;
1887
+ private readonly deployment;
1888
+ private readonly isolation;
1889
+ private readonly catalogCap;
1890
+ private readonly refusals;
1891
+ private readonly store;
1892
+ private readonly sync;
1893
+ private readonly factory;
1894
+ private readonly injector;
1895
+ private readonly plugins;
1896
+ private readonly instances;
1897
+ private started;
1898
+ activateAll(): void;
1899
+ /**
1900
+ * Unloads one sandboxed plugin: closes the RPC connection, removes the frame, disposes `ctx`
1901
+ * (undoing its contributions). Idempotent — unknown/inactive ids are a no-op; the counterpart
1902
+ * to the trusted runtime's `deactivate(id)`.
1903
+ */
1904
+ deactivate(id: string): void;
1905
+ /** Tears down every sandboxed plugin (e.g. on teardown). */
1906
+ deactivateAll(): void;
1907
+ private reconcile;
1908
+ private runnablePlugins;
1909
+ private dropUninstalled;
1910
+ private spawn;
1911
+ private createFrame;
1912
+ private reportingRefusals;
1913
+ private rpcMethods;
1914
+ private watchState;
1915
+ private notifyState;
1916
+ private notifyTabClosed;
1917
+ private notifySettings;
1918
+ static ɵfac: i0.ɵɵFactoryDeclaration<FramePluginRuntime, never>;
1919
+ static ɵprov: i0.ɵɵInjectableDeclaration<FramePluginRuntime>;
1920
+ }
1921
+ /** A distribution registers its sandboxed plugins; they are activated eagerly at startup. */
1922
+ declare function provideFramePlugins(...plugins: FramePlugin[]): (Provider | EnvironmentProviders)[];
1923
+
1924
+ interface CommandInvoker {
1925
+ invocable(callerId: string, granted: boolean): readonly InvocableCommand[];
1926
+ invoke(callerId: string, granted: boolean, id: string, args?: CommandArguments): Promise<CommandOutcome>;
1927
+ }
1928
+ declare const COMMAND_INVOKER: InjectionToken<CommandInvoker>;
1929
+
1930
+ declare class CommandInvocationService implements CommandInvoker {
1931
+ private readonly commands;
1932
+ private readonly registry;
1933
+ private readonly errors;
1934
+ private readonly transloco;
1935
+ private depth;
1936
+ invocable(callerId: string, granted: boolean): readonly InvocableCommand[];
1937
+ invoke(callerId: string, granted: boolean, id: string, args?: CommandArguments): Promise<CommandOutcome>;
1938
+ private run;
1939
+ private answerOf;
1940
+ private reachable;
1941
+ private reportRefusal;
1942
+ private describe;
1943
+ private text;
1944
+ static ɵfac: i0.ɵɵFactoryDeclaration<CommandInvocationService, never>;
1945
+ static ɵprov: i0.ɵɵInjectableDeclaration<CommandInvocationService>;
1946
+ }
1947
+
1948
+ /** Capabilities granted per plugin id. A missing id means "no grant" → default-deny. */
1949
+ type CapabilityGrants = Readonly<Record<string, readonly Capability[]>>;
1950
+ /**
1951
+ * The active capability grants. Defaults to **empty** (default-deny — no plugin can do anything
1952
+ * until granted). A distribution overrides it via {@link provideCapabilityGrants}; a product can
1953
+ * later feed per-tenant grants from its own backend behind the same seam.
1954
+ */
1955
+ declare const CAPABILITY_GRANTS: InjectionToken<Readonly<Record<string, readonly ("contributions" | "ui" | "host" | "navigation" | "session" | "theme" | "automation")[]>>>;
1956
+ /**
1957
+ * A distribution declares which plugin gets which capabilities. The composition root is the
1958
+ * authoritative grant source — the honest default-deny model, dogfooded by the first-party plugin
1959
+ * like a third-party one would be. A product backend can replace it behind the same seam.
1960
+ */
1961
+ declare function provideCapabilityGrants(grants: CapabilityGrants): Provider;
1962
+ /**
1963
+ * The effective capability set for one plugin: what the distribution granted, **intersected** with
1964
+ * what the plugin declares ("plugin declares, distribution grants"). A grant for an
1965
+ * undeclared capability is inert, so least privilege holds in both directions; dev mode flags the
1966
+ * mismatch so the composition stays honest. A plugin without a declaration keeps its grants as-is
1967
+ * (declaring is optional today; the manifest schema hardens this later).
1968
+ */
1969
+ declare function effectiveCapabilities(pluginId: string, granted: readonly Capability[] | undefined, declared: readonly Capability[] | undefined): ReadonlySet<Capability>;
1970
+
1971
+ /** One capability as shown in the permissions surface: whether the user currently keeps it on. */
1972
+ interface PluginCapabilityState {
1973
+ readonly capability: Capability;
1974
+ readonly effective: boolean;
1975
+ }
1976
+ /** A plugin and the base-granted capabilities the user can manage for it. */
1977
+ interface PluginPermissions {
1978
+ readonly pluginId: string;
1979
+ readonly capabilities: readonly PluginCapabilityState[];
1980
+ }
1981
+ /**
1982
+ * The live capability broker. The distribution's static grants (the platform ships no server, so
1983
+ * the composition root — later a per-tenant backend behind the same seam —
1984
+ * is the grant source) form each plugin's **base** set (grant ∩ declaration). On top of that the user can
1985
+ * **revoke** a granted capability at runtime; the decision is user-local and persisted through the
1986
+ * {@link SETTINGS_STORE}, exactly like the rest of the chrome state. Enforcement in
1987
+ * {@link HostPluginContext} consults {@link isGranted} on every `ctx` call, so a revocation takes effect
1988
+ * on the plugin's next call without a reload — the honest default-deny model made transparent and
1989
+ * user-controllable (a "Permissions" settings surface), never wider than the distribution allowed.
1990
+ */
1991
+ declare class CapabilityGrantService {
1992
+ private readonly store;
1993
+ private readonly sync;
1994
+ private readonly grants;
1995
+ private readonly revoked;
1996
+ private readonly bases;
1997
+ /** The plugins and their base-granted capabilities, for the permissions settings surface. */
1998
+ readonly permissions: i0.Signal<readonly PluginPermissions[]>;
1999
+ constructor();
2000
+ /**
2001
+ * Records a plugin's base grant (grant ∩ declaration) so enforcement and the permissions surface know
2002
+ * about it. A runtime calls this once when it activates a plugin. `granted` overrides the
2003
+ * distribution's static grant source — used for user-installed plugins, whose grant is the user's
2004
+ * install consent instead of the composition root.
2005
+ */
2006
+ register(pluginId: string, declared: readonly Capability[] | undefined, granted?: readonly Capability[]): void;
2007
+ /** Drops a plugin from the permissions surface (its runtime deactivated it). Idempotent. */
2008
+ unregister(pluginId: string): void;
2009
+ /** Whether `pluginId` may use `capability` right now: base-granted and not user-revoked. */
2010
+ isGranted(pluginId: string, capability: Capability): boolean;
2011
+ /**
2012
+ * Whether the distribution granted `capability` to `pluginId` (grant ∩ declaration), **ignoring** any
2013
+ * user revocation. Used only while a plugin activates — a revocation must not stop it from loading and
2014
+ * registering its contributions; it applies to the plugin's runtime `ctx` calls afterwards.
2015
+ */
2016
+ isBaseGranted(pluginId: string, capability: Capability): boolean;
2017
+ /** Turns a base-granted capability on or off for a plugin (user revoke / restore). Persisted. */
2018
+ setGranted(pluginId: string, capability: Capability, granted: boolean): void;
2019
+ static ɵfac: i0.ɵɵFactoryDeclaration<CapabilityGrantService, never>;
2020
+ static ɵprov: i0.ɵɵInjectableDeclaration<CapabilityGrantService>;
2021
+ }
2022
+
2023
+ /** A plugin as shown in the permissions surface: its id, display name and whether it is enabled. */
2024
+ interface PluginInfo {
2025
+ readonly id: string;
2026
+ readonly name: string;
2027
+ readonly enabled: boolean;
2028
+ }
2029
+
2030
+ /**
2031
+ * Whether each plugin is turned on — plugin enable/disable, distinct from capability revocation. Disabling a plugin is coarse: it does not restrict a power, it unloads the
2032
+ * whole plugin so **none** of its contributions appear; enabling loads it again. The decision is
2033
+ * user-local and persisted through the {@link SETTINGS_STORE}, exactly like the rest of the chrome state.
2034
+ *
2035
+ * This service holds only the state; the runtimes ({@link PluginRuntime}, {@link FramePluginRuntime})
2036
+ * inject it and reconcile activation against {@link disabled} reactively, so a toggle takes effect at
2037
+ * once without a reload. It never depends on the runtimes, which keeps the dependency one-way.
2038
+ */
2039
+ declare class PluginEnablementService {
2040
+ private readonly store;
2041
+ private readonly sync;
2042
+ private readonly disabledSet;
2043
+ private readonly names;
2044
+ /** The disabled plugin ids (reactive) — a runtime reconciles activation against this. */
2045
+ readonly disabled: i0.Signal<ReadonlySet<string>>;
2046
+ /** Every known plugin with its enabled state, for the permissions settings surface. */
2047
+ readonly plugins: i0.Signal<readonly PluginInfo[]>;
2048
+ constructor();
2049
+ /** Records a plugin so the permissions surface can list it (enabled or not). Idempotent. A runtime calls it for every plugin it knows. */
2050
+ register(id: string, name: string): void;
2051
+ /** Drops a plugin from the list — it was uninstalled, not merely disabled. Idempotent. */
2052
+ unregister(id: string): void;
2053
+ /** Whether `id` is currently enabled (default: yes — a plugin is on until the user turns it off). */
2054
+ isEnabled(id: string): boolean;
2055
+ /** Turns a whole plugin on or off (persisted). The runtimes react by loading/unloading it. */
2056
+ setEnabled(id: string, enabled: boolean): void;
2057
+ static ɵfac: i0.ɵɵFactoryDeclaration<PluginEnablementService, never>;
2058
+ static ɵprov: i0.ɵɵInjectableDeclaration<PluginEnablementService>;
2059
+ }
2060
+
2061
+ /**
2062
+ * A community plugin the user installed from the distribution's catalog. Plain data — the
2063
+ * same shape the sandbox runtime needs to spawn it, plus display metadata for the store and
2064
+ * permissions surfaces.
2065
+ */
2066
+ interface InstalledPlugin {
2067
+ /** Stable plugin id — also the id capabilities are granted to (default-deny). */
2068
+ readonly id: string;
2069
+ /** Display name shown in the store and permissions surfaces. */
2070
+ readonly name: string;
2071
+ /** Same-origin URL of the plugin's entry document. */
2072
+ readonly entryUrl: string;
2073
+ /**
2074
+ * Capabilities the plugin declares. Accepting the install dialog grants exactly these — the user's
2075
+ * consent replaces the composition root's grant for installed plugins.
2076
+ */
2077
+ readonly capabilities?: readonly Capability[];
2078
+ /**
2079
+ * Catalog version at install/update time. Drives update detection (a strictly newer catalog
2080
+ * version offers an update) and is part of the runtime's respawn signature — a version-only
2081
+ * change respawns the running plugin.
2082
+ */
2083
+ readonly version?: string;
2084
+ /** Same-origin URL of the plugin's list icon (an image the operator ships with the plugin). */
2085
+ readonly iconUrl?: string;
2086
+ /**
2087
+ * The level this entry asks to run at. It is a request, not a decision: the composition sets the
2088
+ * highest level a catalog may confer, an entry at or below it runs at what it asked for, and one
2089
+ * above it is refused rather than quietly run lower. Omitted means isolated.
2090
+ */
2091
+ readonly level?: PluginIsolationLevel;
2092
+ }
2093
+ /**
2094
+ * One entry of the distribution's plugin catalog: an {@link InstalledPlugin} plus the
2095
+ * display metadata the store dialog shows (the Obsidian community-plugins model — list of name,
2096
+ * author, downloads, last update and description; a detail pane rendering the plugin's README
2097
+ * **in-app**, never an embedded external page).
2098
+ */
2099
+ interface PluginCatalogEntry extends InstalledPlugin {
2100
+ /** Short description shown in the store list. */
2101
+ readonly description?: string;
2102
+ /** Icon name for the install-consent dialog — resolved by the host icon registry. */
2103
+ readonly icon?: string;
2104
+ /** Operator-curated category, shown as a badge and matched by the store search. */
2105
+ readonly category?: string;
2106
+ /** Plugin author, shown in the list and the detail pane. */
2107
+ readonly author?: string;
2108
+ /** Install/download count, display-only (the operator's stats). */
2109
+ readonly downloads?: number;
2110
+ /** ISO date of the last update, display-only. */
2111
+ readonly updated?: string;
2112
+ /** External link to the plugin's repository/homepage — rendered as a plain link, never framed. */
2113
+ readonly repository?: string;
2114
+ /**
2115
+ * Same-origin URL of the plugin's README (Markdown) — the operator copies it into the store next
2116
+ * to the plugin files; the detail pane fetches and renders it sanitized in-app.
2117
+ */
2118
+ readonly readmeUrl?: string;
2119
+ /**
2120
+ * `true` for a plugin the operator **deploys**: active for every user without a consent dialog,
2121
+ * holding exactly the capabilities this entry names, and gone again once the catalog stops
2122
+ * carrying it. Omitted or `false` means the entry is merely **offered** — the user browses it,
2123
+ * consents and installs it, which is the only path that grants anything on their say-so.
2124
+ *
2125
+ * The authority behind a deployed entry is the operator's, so the user is shown it but is not
2126
+ * asked about it and cannot remove it.
2127
+ */
2128
+ readonly deployed?: boolean;
2129
+ }
2130
+
2131
+ /**
2132
+ * The user's installed community plugins. Holds only the state: which catalog entries the
2133
+ * user installed, persisted user-locally through the {@link SETTINGS_STORE} — a product that wants
2134
+ * tenant-wide or server-held installs implements that in its store backend, the seam does not change.
2135
+ * The {@link FramePluginRuntime} reconciles activation against {@link installed} reactively, so an
2136
+ * install spawns the plugin at once and an uninstall unloads it, both without a reload. It never
2137
+ * depends on the runtime, which keeps the dependency one-way (the {@link PluginEnablementService}
2138
+ * precedent).
2139
+ */
2140
+ declare class PluginInstallService {
2141
+ private readonly store;
2142
+ private readonly sync;
2143
+ private readonly pluginState;
2144
+ private readonly entries;
2145
+ private composedIds;
2146
+ /** The installed plugins (reactive) — the sandbox runtime reconciles against this. */
2147
+ readonly installed: i0.Signal<readonly InstalledPlugin[]>;
2148
+ constructor();
2149
+ /** Records the composition-time plugin ids so an install can never shadow a composed plugin. */
2150
+ markComposed(ids: readonly string[]): void;
2151
+ isInstalled(id: string): boolean;
2152
+ /** The installed entry for an id, or `undefined` — the baseline an update is compared against. */
2153
+ find(id: string): InstalledPlugin | undefined;
2154
+ /**
2155
+ * Installs a catalog entry after the user consented to its declared capabilities. Fail-fast: the
2156
+ * `entryUrl` must be same-origin (the store is the distribution's own origin) and the id
2157
+ * must be neither composed nor already installed. Persisted; the runtime spawns the plugin live.
2158
+ */
2159
+ install(plugin: InstalledPlugin): void;
2160
+ /**
2161
+ * Replaces an installed entry with a newer catalog entry (the update flow).
2162
+ * Same fail-fast validation as {@link install}, except the id must already be installed. The
2163
+ * persisted entry carries the plugin's capabilities, so a declaration that grew must be consented
2164
+ * to before this is called — see `confirmUpdate`. The runtime respawns the plugin live.
2165
+ */
2166
+ update(plugin: InstalledPlugin): void;
2167
+ /** Removes an installed plugin (idempotent); the runtime unloads it live. */
2168
+ /**
2169
+ * Removes an installed plugin and **deletes its own store** — deliberately the
2170
+ * opposite of its settings section, which survives so a reinstall finds its configuration. An
2171
+ * abandoned draft of a plugin the user just removed is litter; a preference is not.
2172
+ */
2173
+ uninstall(id: string): void;
2174
+ private validEntry;
2175
+ private persist;
2176
+ static ɵfac: i0.ɵɵFactoryDeclaration<PluginInstallService, never>;
2177
+ static ɵprov: i0.ɵɵInjectableDeclaration<PluginInstallService>;
2178
+ }
2179
+
2180
+ /**
2181
+ * The distribution's plugin catalog port: the operator-curated list of community plugins a
2182
+ * user may install. The platform only defines the seam — what is not in the catalog does not exist
2183
+ * for the shell; per-tenant curation is the product backend answering the catalog request
2184
+ * tenant-dependently.
2185
+ */
2186
+ interface PluginCatalog {
2187
+ load(): Promise<readonly PluginCatalogEntry[]>;
2188
+ }
2189
+ /**
2190
+ * Injection token for the {@link PluginCatalog}. No default — a distribution without a catalog has no
2191
+ * plugin store (and no store settings section). Provide one with {@link providePluginCatalog}.
2192
+ */
2193
+ declare const PLUGIN_CATALOG: InjectionToken<PluginCatalog>;
2194
+ /**
2195
+ * A {@link PluginCatalog} that fetches a same-origin JSON document (an array of
2196
+ * {@link PluginCatalogEntry}). Entries are parsed defensively: junk shapes, foreign-origin
2197
+ * `entryUrl`s and unknown capability names are dropped. The catalog URL itself must be same-origin —
2198
+ * the store is the distribution's own origin; a product that wants another source
2199
+ * implements the {@link PluginCatalog} port directly.
2200
+ */
2201
+ declare function urlPluginCatalog(url: string): PluginCatalog;
2202
+
2203
+ /** Options for {@link providePluginCatalog}. */
2204
+ interface PluginCatalogOptions {
2205
+ /**
2206
+ * Transloco key (or literal) for the store's settings-section title — brand the store per product
2207
+ * (e.g. `'product.marketplace'`). Defaults to the built-in `settings.pluginStore` ("Plugin store").
2208
+ */
2209
+ readonly title?: string;
2210
+ /**
2211
+ * The highest level this catalog may confer on what it carries. Defaults to the strict one, so a
2212
+ * catalog can never hand out an embedded application unless the composition said it may. An entry
2213
+ * asking for more is refused rather than started lower — a plugin running below what it needs
2214
+ * fails in ways nobody can trace back to a line of configuration.
2215
+ */
2216
+ readonly maxLevel?: PluginIsolationLevel;
2217
+ }
2218
+ /**
2219
+ * Wires the plugin store into a distribution: provides the catalog (a same-origin JSON URL
2220
+ * or a custom {@link PluginCatalog} implementation), makes sure the sandbox runtime is active even
2221
+ * when no plugin is composed statically, and registers the store's entry points — a settings section
2222
+ * (`setting:shell.pluginStore`, omit-able) whose Browse button opens the **store dialog** (the
2223
+ * Obsidian browse model: searchable list + in-app detail pane with README), plus the palette command
2224
+ * `shell.openPluginStore`. The title is brandable via {@link PluginCatalogOptions.title}. Installing
2225
+ * and uninstalling happens in the store; an installed plugin that declares its own settings gets its
2226
+ * own entry under the **Community plugins** nav group. Place after `provideShell()`.
2227
+ */
2228
+ declare function providePluginCatalog(source: PluginCatalog | string, options?: PluginCatalogOptions): (Provider | EnvironmentProviders)[];
2229
+
2230
+ /**
2231
+ * Distribution-level icons: seed `name → SVG` into the module-global registry at bootstrap,
2232
+ * resolved by `<lw-icon>`. **The distribution wins:** naming one of the first-party icons replaces it
2233
+ * everywhere the chrome draws it, which is how a product re-skins the workbench; naming a new one adds it.
2234
+ * A *weaver* instead contributes at runtime via `ctx.contributeIcons` and can never shadow a name that is
2235
+ * already taken, so an installed plugin cannot repaint the chrome. Distribution icons are build-time and
2236
+ * trusted like the first-party set, so they are not re-sanitized.
2237
+ *
2238
+ * The key type suggests the shipped names while still accepting your own, so a typo in an intended
2239
+ * replacement shows up while writing it instead of silently adding a glyph nothing draws.
2240
+ *
2241
+ * These icons also travel into sandboxed surfaces, so a plugin drawing `<lw-icon name="trash">` shows
2242
+ * your glyph rather than ours.
2243
+ */
2244
+ declare function provideIcons(icons: Readonly<Partial<Record<LoomIconName | (string & {}), string>>>): EnvironmentProviders;
2245
+
2246
+ /** The custom-element tag. */
2247
+ declare const LW_ICON_TAG = "lw-icon";
2248
+ /**
2249
+ * `<lw-icon name="add" size="1rem">` — the host icon primitive as a framework-agnostic custom element
2250
+ *: a plain `HTMLElement` that resolves a **name** to its SVG via the module-global icon
2251
+ * registry — no Angular DI, no `@ng-icons` runtime — so it works in a weaver body by tag, not
2252
+ * only in shell chrome. Light DOM: the SVG uses `currentColor`, so `text-*` tokens tint it for free.
2253
+ *
2254
+ * The SVG is already safe (first-party markup, or sanitized at registration), so it is set as raw
2255
+ * `innerHTML`. Decorative by default (`aria-hidden`); pass `aria-label` to expose it as an image.
2256
+ *
2257
+ * <lw-icon name="close" size="0.85rem"></lw-icon>
2258
+ * <lw-icon [name]="dynamicName()" aria-label="…"></lw-icon>
2259
+ */
2260
+ declare class LwIconElement extends HTMLElement {
2261
+ static readonly observedAttributes: string[];
2262
+ get name(): string;
2263
+ set name(value: string | null);
2264
+ get size(): string;
2265
+ set size(value: string | null);
2266
+ connectedCallback(): void;
2267
+ attributeChangedCallback(): void;
2268
+ /**
2269
+ * Re-draws from the registry without changing the name. A sandboxed surface receives the product's
2270
+ * replacement icons over its channel, which can arrive after it has already painted.
2271
+ */
2272
+ refresh(): void;
2273
+ private render;
2274
+ }
2275
+ /** Registers `<lw-icon>` once (idempotent) — called from {@link provideShell} at bootstrap. */
2276
+ declare function defineLwIcon(): void;
2277
+
2278
+ export { AUTH_SOURCE, AuthContext, BAR_ITEM, CAPABILITY_GRANTS, COMMAND_INVOKER, CapabilityGrantService, CommandInvocationService, CommandService, ContentTabsService, ContributionRegistry, DEFAULT_LAYOUT, DEFAULT_SHELL_FEATURES, DEVICE_LEVEL_KEYS, DialogOutlet, DialogService, FRAME_PLUGIN, FramePluginRuntime, KeybindingService, LW_BUTTON_TAG, LW_ICON_TAG, LW_MARKDOWN_TAG, LW_TOOLTIP_TAG, LocalStorageStore, LwButton, LwButtonElement, LwIconElement, LwMarkdownElement, LwSettingRow, LwSpinner, LwTooltipElement, LwVersion, NotificationService, PLUGIN, PLUGIN_CATALOG, PluginEnablementService, PluginInstallService, PluginRuntime, PopoutService, RAIL_ITEM, SETTINGS_STORE, SHELL_FEATURES, SHELL_LAYOUT, SettingsService, Shell, StateSyncService, TRANSLATION_NAMESPACES, TRANSLATION_OVERRIDES, ThemeService, ToastOutlet, UpdateBadge, UpdateService, VIEW, VersionService, WORKING_STATE_STORE, WORKSPACE_DEFINITIONS, defineLwButton, defineLwIcon, defineLwMarkdown, defineLwTooltip, effectiveCapabilities, formatChord, provideAuthSource, provideBarItems, provideCapabilityGrants, provideCommandPaletteEntry, provideFramePlugins, provideIcons, provideIdentityScopedStores, provideLayout, providePluginCatalog, providePlugins, provideRailItems, provideSettingsStore, provideShell, provideShellFeatures, provideShellRouter, provideTabAddressResolver, provideTranslationNamespaces, provideTranslationOverrides, provideUnauthorizedRedirect, provideViews, provideWorkingStateStore, provideWorkspaces, urlPluginCatalog };
2279
+ export type { ApplySyncedState, AuthSourceOptions, CapabilityGrants, CommandFeatures, CommandInvoker, CommandPaletteEntryOptions, ContentFeatures, ContentTabView, DialogButtonView, DialogInstance, DockPosition, FramePlugin, IdentityScopedStoreOptions, InstalledPlugin, KeyValueStore, LayoutRegion, LoomIconName, Notification, PluginCapabilityState, PluginCatalog, PluginCatalogEntry, PluginCatalogOptions, PluginInfo, PluginPermissions, QuickOpenTarget, RailFeatures, RegionType, RegisteredCommand, RegisteredContentRoute, RegisteredView, ResolvedTheme, RetentionDefault, ShellFeatures, ShellFeaturesInput, ShellLayout, ShellOptions, SidebarFeatures, SyncSource, TabAddressInput, TabAddressResolver, ThemeMode, TooltipPosition, UnauthorizedHandler, WindowFeatures, WorkspaceArea, WorkspaceAreaBase, WorkspaceColumnArea, WorkspaceDefinition, WorkspaceFeatures, WorkspaceRowArea, WorkspaceTab, WorkspaceTabArea, WorkspaceTabEntry };
2280
+ //# sourceMappingURL=loomweaver-shell.d.ts.map