@mohamedatia/fly-design-system 2.9.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, signal, computed, Injectable, inject, ErrorHandler, PLATFORM_ID, DestroyRef, ChangeDetectionStrategy, Component, Pipe, DOCUMENT, ElementRef, input, output, HostListener, ViewChild, EventEmitter, Output, Input, forwardRef, Injector, viewChild, effect, afterNextRender, ViewEncapsulation, model, Directive } from '@angular/core';
2
+ import { InjectionToken, signal, computed, Injectable, inject, ErrorHandler, PLATFORM_ID, DestroyRef, ChangeDetectionStrategy, Component, Pipe, DOCUMENT, ElementRef, input, output, HostListener, ViewChild, EventEmitter, Output, Input, forwardRef, Injector, viewChild, effect, afterNextRender, ViewEncapsulation, model, Directive, Renderer2 } from '@angular/core';
3
3
  import * as i1$1 from '@angular/common';
4
4
  import { isPlatformBrowser, NgComponentOutlet, CommonModule, DOCUMENT as DOCUMENT$1 } from '@angular/common';
5
5
  import { Router, NavigationEnd } from '@angular/router';
@@ -41,6 +41,29 @@ const FLYOS_LAUNCH_EVENT = 'flyos:launch';
41
41
  * Subsequent re-launches into the already-mounted remote arrive via the event.
42
42
  */
43
43
  const FlyosPendingLaunchesGlobalKey = '__FLYOS_PENDING_LAUNCHES__';
44
+ /**
45
+ * Per-window injection token carrying the active <see cref="WindowHelpHint"/>
46
+ * as a writable signal. The shell creates one writable signal per window
47
+ * (alongside <see cref="LAUNCH_CONTEXT"/>); apps inject it and `.set(...)` to
48
+ * publish or update their hint. Setting `null` clears the hint — the chrome
49
+ * falls back to the implicit `win.appId` deeplink.
50
+ *
51
+ * **Federation note:** same caveat as <see cref="LAUNCH_CONTEXT"/> — Native
52
+ * Federation can split the InjectionToken across host/remote bundles, so
53
+ * federated remotes cannot rely on DI to publish hints. Use
54
+ * <see cref="FLY_WINDOW_HELP_HINT_EVENT"/> instead.
55
+ */
56
+ const WINDOW_HELP_HINT = new InjectionToken('WINDOW_HELP_HINT');
57
+ /**
58
+ * Federation-safe window CustomEvent name for publishing a help hint from a
59
+ * federated remote that cannot see <see cref="WINDOW_HELP_HINT"/> via DI.
60
+ *
61
+ * Detail: <see cref="FlyWindowHelpHintEventDetail"/>. The shell listens at
62
+ * `window` scope and mirrors the payload into the matching per-window signal
63
+ * (keyed by `windowId` from <see cref="WINDOW_DATA"/>). Pairs with the
64
+ * <see cref="FLYOS_LAUNCH_EVENT"/> pattern.
65
+ */
66
+ const FLY_WINDOW_HELP_HINT_EVENT = 'flyos:window-help-hint';
44
67
 
45
68
  /**
46
69
  * Generic share / ACL panel models — hosts map domain DTOs to these shapes.
@@ -799,6 +822,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
799
822
  args: [{ providedIn: 'root' }]
800
823
  }] });
801
824
 
825
+ /**
826
+ * Publishes a {@link WindowHelpHint} for the current window so the shell's
827
+ * titlebar Help button deeplinks the help-center reader to the article most
828
+ * relevant to where the user is.
829
+ *
830
+ * This is the **publisher twin** of the shell's `WindowHelpHintService`
831
+ * (the listener). It exists in the design system so that **any app — and
832
+ * especially a federated remote — publishes hints the same way**, without
833
+ * hand-rolling the cross-bundle CustomEvent contract.
834
+ *
835
+ * **Why a `window` CustomEvent and not the `WINDOW_HELP_HINT` DI token?**
836
+ * Native Federation can split an `InjectionToken` instance across the host and
837
+ * remote bundles, so a remote that injects `WINDOW_HELP_HINT` may get a
838
+ * different token than the one the shell provides. The string-keyed
839
+ * {@link FLY_WINDOW_HELP_HINT_EVENT} crosses bundles reliably; the shell
840
+ * mirrors it into the matching per-window hint signal. In-shell (OS-Core) apps
841
+ * may still use the DI token directly, but this service works for them too.
842
+ *
843
+ * **Imperative by design.** The service holds no signals and runs no effect —
844
+ * the *app* owns its reactivity (route + state → resolved hint) and pushes the
845
+ * result via {@link setHint}. The design system owns only the wire format. A
846
+ * hint set before {@link bindWindow} is buffered and emitted once the window id
847
+ * arrives, so call order doesn't matter.
848
+ *
849
+ * Usage (typically from the remote root + its feature components):
850
+ * ```ts
851
+ * private help = inject(FlyWindowHelpService);
852
+ * constructor() {
853
+ * this.help.bindWindow(inject(WINDOW_DATA, { optional: true })?.id);
854
+ * effect(() => this.help.setHint(this.resolvedHint())); // app-specific
855
+ * }
856
+ * ```
857
+ */
858
+ class FlyWindowHelpService {
859
+ windowId = null;
860
+ hint = null;
861
+ /**
862
+ * Bind (or clear) the host window id — typically once, from the app root,
863
+ * with `WINDOW_DATA.id`. Re-emits the current hint for the (new) window.
864
+ * Passing null/undefined (standalone, no shell) makes publishing a no-op.
865
+ */
866
+ bindWindow(windowId) {
867
+ this.windowId = windowId ?? null;
868
+ this.publish();
869
+ }
870
+ /**
871
+ * Set the active hint (or null to clear — the shell then reverts to the
872
+ * window's own `appId`). Emits immediately when a window is bound; otherwise
873
+ * the value is buffered and emitted on the next {@link bindWindow}.
874
+ */
875
+ setHint(hint) {
876
+ this.hint = hint;
877
+ this.publish();
878
+ }
879
+ publish() {
880
+ if (typeof window === 'undefined' || !this.windowId)
881
+ return;
882
+ const detail = { windowId: this.windowId, hint: this.hint };
883
+ window.dispatchEvent(new CustomEvent(FLY_WINDOW_HELP_HINT_EVENT, { detail }));
884
+ }
885
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyWindowHelpService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
886
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyWindowHelpService, providedIn: 'root' });
887
+ }
888
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyWindowHelpService, decorators: [{
889
+ type: Injectable,
890
+ args: [{ providedIn: 'root' }]
891
+ }] });
892
+
802
893
  /**
803
894
  * fly-remote-styles — Shell-layer CSS loader for Native Federation remotes.
804
895
  *
@@ -3359,7 +3450,7 @@ class AgentCommandRegistry {
3359
3450
  * for reactive filtering.
3360
3451
  */
3361
3452
  visible(liveAppIds) {
3362
- const liveSignal = isSignal(liveAppIds) ? liveAppIds : signal(liveAppIds).asReadonly();
3453
+ const liveSignal = isSignal$1(liveAppIds) ? liveAppIds : signal(liveAppIds).asReadonly();
3363
3454
  return computed(() => {
3364
3455
  const live = liveSignal();
3365
3456
  return this._commands().filter((cmd) => {
@@ -3432,6 +3523,111 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
3432
3523
  args: [{ providedIn: 'root' }]
3433
3524
  }] });
3434
3525
  /** `isSignal` shim — narrows to either `Signal<T>` or a plain value. */
3526
+ function isSignal$1(v) {
3527
+ return typeof v === 'function';
3528
+ }
3529
+
3530
+ /**
3531
+ * Singleton registry of entity lookups offered by the `/lookup` typeahead.
3532
+ *
3533
+ * Mirrors {@link AgentCommandRegistry} exactly — same federation-singleton story
3534
+ * (`sharedMappings: ['@mohamedatia/fly-design-system']`), same id-collision
3535
+ * "latest wins" contract, same disposable-handle ergonomics. Federated remotes
3536
+ * register their lookupable entities at boot (Circles: scenario / trend / signal)
3537
+ * and dispose on window close, so the picker only ever offers entities whose app
3538
+ * is currently live.
3539
+ *
3540
+ * Storage is a signal store keyed on {@link LookupRegistration.entity}. Because
3541
+ * `entity` is the collision key, an app re-registering the same entity replaces
3542
+ * the prior descriptor; a stale handle's `dispose()` then no-ops.
3543
+ */
3544
+ class AgentLookupRegistry {
3545
+ _lookups = signal([], ...(ngDevMode ? [{ debugName: "_lookups" }] : /* istanbul ignore next */ []));
3546
+ /** All currently-registered lookups, in insertion order. */
3547
+ all = this._lookups.asReadonly();
3548
+ /**
3549
+ * Lookups whose scope is `'global'` OR whose `scope.appId` is in the live app
3550
+ * set. Recomputes when either the registry or `liveAppIds` changes — pass a
3551
+ * `Signal<ReadonlySet<string>>` from the host's app-registry for reactive
3552
+ * filtering, exactly like {@link AgentCommandRegistry.visible}.
3553
+ */
3554
+ visible(liveAppIds) {
3555
+ const liveSignal = isSignal(liveAppIds)
3556
+ ? liveAppIds
3557
+ : signal(liveAppIds).asReadonly();
3558
+ return computed(() => {
3559
+ const live = liveSignal();
3560
+ return this._lookups().filter((l) => {
3561
+ if (l.scope === 'global')
3562
+ return true;
3563
+ return live.has(l.scope.appId);
3564
+ });
3565
+ });
3566
+ }
3567
+ /**
3568
+ * Register one lookup. Returns a handle whose `dispose()` removes the row by
3569
+ * `entity`. A later re-registration of the same entity makes the original
3570
+ * handle's `dispose()` a no-op (the newer registration owns the row).
3571
+ */
3572
+ register(lookup) {
3573
+ const generation = ++this._generation;
3574
+ this._lookups.update((rows) => [
3575
+ ...rows.filter((r) => r.entity !== lookup.entity),
3576
+ lookup,
3577
+ ]);
3578
+ this._owners.set(lookup.entity, generation);
3579
+ return {
3580
+ dispose: () => {
3581
+ if (this._owners.get(lookup.entity) === generation) {
3582
+ this._owners.delete(lookup.entity);
3583
+ this._lookups.update((rows) => rows.filter((r) => r.entity !== lookup.entity));
3584
+ }
3585
+ },
3586
+ };
3587
+ }
3588
+ /**
3589
+ * Bulk register. Rolls back on a duplicate entity WITHIN the input batch
3590
+ * (throws before any row lands). Cross-batch duplicates against existing rows
3591
+ * follow the standard "latest wins" rule and do NOT trigger rollback.
3592
+ */
3593
+ registerAll(lookups) {
3594
+ const seen = new Set();
3595
+ for (const l of lookups) {
3596
+ if (seen.has(l.entity)) {
3597
+ throw new Error(`AgentLookupRegistry.registerAll: duplicate entity "${l.entity}" in batch`);
3598
+ }
3599
+ seen.add(l.entity);
3600
+ }
3601
+ const handles = lookups.map((l) => this.register(l));
3602
+ let disposed = false;
3603
+ return {
3604
+ dispose: () => {
3605
+ if (disposed)
3606
+ return;
3607
+ disposed = true;
3608
+ for (const h of handles)
3609
+ h.dispose();
3610
+ },
3611
+ };
3612
+ }
3613
+ /** Tear down by entity. Idempotent. */
3614
+ unregister(entity) {
3615
+ if (this._owners.delete(entity)) {
3616
+ this._lookups.update((rows) => rows.filter((r) => r.entity !== entity));
3617
+ }
3618
+ }
3619
+ /** Monotonic counter; identifies which registration call currently owns each entity. */
3620
+ _generation = 0;
3621
+ /** entity → generation. Lets a stale handle's `dispose()` no-op after replacement. */
3622
+ _owners = new Map();
3623
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentLookupRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3624
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentLookupRegistry, providedIn: 'root' });
3625
+ }
3626
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentLookupRegistry, decorators: [{
3627
+ type: Injectable,
3628
+ args: [{ providedIn: 'root' }]
3629
+ }] });
3630
+ /** `isSignal` shim — narrows to either `Signal<T>` or a plain value. */
3435
3631
  function isSignal(v) {
3436
3632
  return typeof v === 'function';
3437
3633
  }
@@ -3539,6 +3735,21 @@ function compositeKey(kind, appId) {
3539
3735
  return `${kind}::${appId}`;
3540
3736
  }
3541
3737
 
3738
+ /**
3739
+ * Thrown synchronously by {@link AgentActionBus.dispatch} when a caller
3740
+ * supplies a dispatch mode this DS version doesn't implement yet. Catching
3741
+ * by class name lets a forward-compatible caller fall back to `'stage'`
3742
+ * without depending on instanceof across federation boundaries.
3743
+ */
3744
+ class AgentActionUnsupportedDispatchError extends Error {
3745
+ dispatch;
3746
+ constructor(dispatch) {
3747
+ super(`AgentActionBus: dispatch="${dispatch}" not supported in this DS version`);
3748
+ this.dispatch = dispatch;
3749
+ this.name = 'AgentActionUnsupportedDispatchError';
3750
+ }
3751
+ }
3752
+
3542
3753
  /**
3543
3754
  * Agent input contracts.
3544
3755
  *
@@ -3558,12 +3769,40 @@ function compositeKey(kind, appId) {
3558
3769
  */
3559
3770
  /** Frozen MIME used by `flyAgentDraggable` and the drop-zone reader. Never change without a DS major. */
3560
3771
  const AGENT_DRAG_MIME = 'application/x-fly-agent-payload+json';
3561
- /** Frozen payload envelope version. Bump only with a published DS major. */
3562
- const AGENT_PAYLOAD_VERSION = 1;
3772
+ /**
3773
+ * Frozen payload envelope version.
3774
+ *
3775
+ * v1 — minimal drag payload: kind / appId / version / payload / plainTextFallback /
3776
+ * suggestedCommandIds. Still used by the drag/drop surface.
3777
+ *
3778
+ * v2 — bus envelope ({@link AgentMessageEnvelope}). Adds optional `userMessage`,
3779
+ * `systemContext`, `attachments`, `mcpScope` so a dispatcher can give the agent
3780
+ * rich context without polluting the user-visible message bubble. v2 is a
3781
+ * superset of v1 — every v1 payload is a valid v2 payload — so the version
3782
+ * number ratchets forward without breaking existing callers.
3783
+ */
3784
+ const AGENT_PAYLOAD_VERSION = 2;
3785
+ /**
3786
+ * Versions accepted by the validator. v1 payloads (the drag/drop surface) remain valid;
3787
+ * v2 adds the optional bus-envelope fields. Renderers narrow on `version` when they need
3788
+ * to.
3789
+ */
3790
+ const SUPPORTED_AGENT_PAYLOAD_VERSIONS = Object.freeze([1, 2]);
3563
3791
  const DEFAULT_AGENT_PAYLOAD_LIMITS = Object.freeze({
3564
3792
  maxJsonBytes: 32 * 1024,
3565
3793
  maxPlainTextFallbackBytes: 8 * 1024,
3566
3794
  maxStringFieldBytes: 4 * 1024,
3795
+ maxUserMessageBytes: 280 * 4, // 280 chars × 4 bytes/char worst-case UTF-8
3796
+ maxSystemContextEntries: 32,
3797
+ maxSystemContextValueBytes: 4 * 1024,
3798
+ maxSystemContextJsonBytes: 8 * 1024,
3799
+ maxAttachments: 4,
3800
+ maxAttachmentJsonBytes: 16 * 1024,
3801
+ maxAttachmentTextBytes: 8 * 1024,
3802
+ maxAttachmentDataUrlBytes: 32 * 1024,
3803
+ maxMcpScopeApis: 5,
3804
+ maxMcpScopeApiBytes: 128,
3805
+ maxDeepLinkRouteBytes: 1024,
3567
3806
  });
3568
3807
 
3569
3808
  /**
@@ -3669,18 +3908,21 @@ function trimAgentString(input, maxBytes, ellipsis = '…') {
3669
3908
  * structured failure with the offending field path and byte counts.
3670
3909
  *
3671
3910
  * Order of checks (cheap → expensive):
3672
- * 1. `version` matches the frozen {@link AGENT_PAYLOAD_VERSION}.
3911
+ * 1. `version` is in {@link SUPPORTED_AGENT_PAYLOAD_VERSIONS}.
3673
3912
  * 2. `kind` is a non-empty string.
3674
3913
  * 3. `plainTextFallback` fits its cap.
3675
3914
  * 4. Every string in `payload` (recursively) fits the per-field cap.
3676
- * 5. The full `JSON.stringify(envelope)` fits the JSON cap.
3915
+ * 5. v2 sections `userMessage`, `systemContext`, `attachments`, `mcpScope`
3916
+ * each fits their independent caps. Skipped when absent so v1 payloads pass
3917
+ * unchanged.
3918
+ * 6. The full `JSON.stringify(envelope)` fits the JSON cap.
3677
3919
  *
3678
3920
  * The walker stops on the first oversize string field — the intent is to surface
3679
3921
  * actionable feedback, not enumerate every offender.
3680
3922
  */
3681
3923
  function validateAgentPayload(payload, limits) {
3682
3924
  const eff = { ...DEFAULT_AGENT_PAYLOAD_LIMITS, ...(limits ?? {}) };
3683
- if (payload.version !== AGENT_PAYLOAD_VERSION) {
3925
+ if (!SUPPORTED_AGENT_PAYLOAD_VERSIONS.includes(payload.version)) {
3684
3926
  return { ok: false, reason: 'invalid_version' };
3685
3927
  }
3686
3928
  if (typeof payload.kind !== 'string' || payload.kind.length === 0) {
@@ -3699,6 +3941,16 @@ function validateAgentPayload(payload, limits) {
3699
3941
  const fieldFailure = walkStringsForOverage(payload.payload, 'payload', eff.maxStringFieldBytes);
3700
3942
  if (fieldFailure)
3701
3943
  return fieldFailure;
3944
+ // v2 — per-section caps. Each helper is a no-op when its section is absent
3945
+ // so a v1 payload (no userMessage / systemContext / attachments / mcpScope /
3946
+ // deepLinkRoute) sails through unchanged.
3947
+ const v2Failure = checkUserMessage(payload, eff) ??
3948
+ checkSystemContext(payload, eff) ??
3949
+ checkAttachments(payload, eff) ??
3950
+ checkMcpScope(payload, eff) ??
3951
+ checkDeepLinkRoute(payload, eff);
3952
+ if (v2Failure)
3953
+ return v2Failure;
3702
3954
  const json = safeStringify(payload);
3703
3955
  const jsonBytes = utf8ByteLength(json);
3704
3956
  if (jsonBytes > eff.maxJsonBytes) {
@@ -3711,6 +3963,200 @@ function validateAgentPayload(payload, limits) {
3711
3963
  }
3712
3964
  return { ok: true };
3713
3965
  }
3966
+ // ─── v2 section validators ──────────────────────────────────────────────────
3967
+ function checkUserMessage(payload, eff) {
3968
+ if (payload.userMessage === undefined)
3969
+ return null;
3970
+ if (typeof payload.userMessage !== 'string')
3971
+ return null; // structural; ts catches at the call site
3972
+ const bytes = utf8ByteLength(payload.userMessage);
3973
+ if (bytes > eff.maxUserMessageBytes) {
3974
+ return {
3975
+ ok: false,
3976
+ reason: 'user_message_too_large',
3977
+ fieldPath: 'userMessage',
3978
+ actualBytes: bytes,
3979
+ limitBytes: eff.maxUserMessageBytes,
3980
+ };
3981
+ }
3982
+ return null;
3983
+ }
3984
+ function checkSystemContext(payload, eff) {
3985
+ const ctx = payload.systemContext;
3986
+ if (!ctx)
3987
+ return null;
3988
+ const keys = Object.keys(ctx);
3989
+ if (keys.length > eff.maxSystemContextEntries) {
3990
+ return {
3991
+ ok: false,
3992
+ reason: 'system_context_too_many_entries',
3993
+ fieldPath: 'systemContext',
3994
+ actualBytes: keys.length,
3995
+ limitBytes: eff.maxSystemContextEntries,
3996
+ };
3997
+ }
3998
+ for (const k of keys) {
3999
+ const v = ctx[k];
4000
+ if (typeof v !== 'string')
4001
+ continue;
4002
+ const bytes = utf8ByteLength(v);
4003
+ if (bytes > eff.maxSystemContextValueBytes) {
4004
+ return {
4005
+ ok: false,
4006
+ reason: 'system_context_value_too_large',
4007
+ fieldPath: `systemContext.${k}`,
4008
+ actualBytes: bytes,
4009
+ limitBytes: eff.maxSystemContextValueBytes,
4010
+ };
4011
+ }
4012
+ }
4013
+ const ctxJsonBytes = utf8ByteLength(safeStringify(ctx));
4014
+ if (ctxJsonBytes > eff.maxSystemContextJsonBytes) {
4015
+ return {
4016
+ ok: false,
4017
+ reason: 'system_context_json_too_large',
4018
+ fieldPath: 'systemContext',
4019
+ actualBytes: ctxJsonBytes,
4020
+ limitBytes: eff.maxSystemContextJsonBytes,
4021
+ };
4022
+ }
4023
+ return null;
4024
+ }
4025
+ function checkAttachments(payload, eff) {
4026
+ const atts = payload.attachments;
4027
+ if (!atts)
4028
+ return null;
4029
+ if (atts.length > eff.maxAttachments) {
4030
+ return {
4031
+ ok: false,
4032
+ reason: 'too_many_attachments',
4033
+ fieldPath: 'attachments',
4034
+ actualBytes: atts.length,
4035
+ limitBytes: eff.maxAttachments,
4036
+ };
4037
+ }
4038
+ for (let i = 0; i < atts.length; i++) {
4039
+ const a = atts[i];
4040
+ const path = `attachments[${i}]`;
4041
+ switch (a.kind) {
4042
+ case 'json': {
4043
+ const bytes = utf8ByteLength(safeStringify(a.json));
4044
+ if (bytes > eff.maxAttachmentJsonBytes) {
4045
+ return {
4046
+ ok: false,
4047
+ reason: 'attachment_json_too_large',
4048
+ fieldPath: `${path}.json`,
4049
+ actualBytes: bytes,
4050
+ limitBytes: eff.maxAttachmentJsonBytes,
4051
+ };
4052
+ }
4053
+ break;
4054
+ }
4055
+ case 'text': {
4056
+ const bytes = utf8ByteLength(a.text ?? '');
4057
+ if (bytes > eff.maxAttachmentTextBytes) {
4058
+ return {
4059
+ ok: false,
4060
+ reason: 'attachment_text_too_large',
4061
+ fieldPath: `${path}.text`,
4062
+ actualBytes: bytes,
4063
+ limitBytes: eff.maxAttachmentTextBytes,
4064
+ };
4065
+ }
4066
+ break;
4067
+ }
4068
+ case 'image':
4069
+ case 'file': {
4070
+ const bytes = utf8ByteLength(a.dataUrl ?? '');
4071
+ if (bytes > eff.maxAttachmentDataUrlBytes) {
4072
+ return {
4073
+ ok: false,
4074
+ reason: 'attachment_data_url_too_large',
4075
+ fieldPath: `${path}.dataUrl`,
4076
+ actualBytes: bytes,
4077
+ limitBytes: eff.maxAttachmentDataUrlBytes,
4078
+ };
4079
+ }
4080
+ break;
4081
+ }
4082
+ default:
4083
+ return {
4084
+ ok: false,
4085
+ reason: 'attachment_invalid_kind',
4086
+ fieldPath: `${path}.kind`,
4087
+ };
4088
+ }
4089
+ }
4090
+ return null;
4091
+ }
4092
+ /**
4093
+ * Validate `deepLinkRoute` — byte cap + minimal shape sanity check.
4094
+ *
4095
+ * The DS package is shell-agnostic, so full route grammar validation
4096
+ * (scheme prefixes, `..` segments, `//` runs) lives in the shell's
4097
+ * `DeepLinkService.isValidRoute` — the launcher is the authoritative
4098
+ * gate. Here we just enforce:
4099
+ * - byte cap so a malicious payload can't bloat the JSON
4100
+ * - must start with `/` so the value matches the launcher's contract
4101
+ * and a renderer can safely concatenate / display it.
4102
+ *
4103
+ * Anything more would force the DS to grow a second copy of the route
4104
+ * grammar; the launcher rejects malformed routes at click time so the
4105
+ * worst-case impact of a bad route landing here is a no-op click.
4106
+ */
4107
+ function checkDeepLinkRoute(payload, eff) {
4108
+ const route = payload.deepLinkRoute;
4109
+ if (route === undefined)
4110
+ return null;
4111
+ if (typeof route !== 'string')
4112
+ return null; // structural; ts catches at the call site
4113
+ if (!route.startsWith('/')) {
4114
+ return {
4115
+ ok: false,
4116
+ reason: 'deep_link_route_invalid_shape',
4117
+ fieldPath: 'deepLinkRoute',
4118
+ };
4119
+ }
4120
+ const bytes = utf8ByteLength(route);
4121
+ if (bytes > eff.maxDeepLinkRouteBytes) {
4122
+ return {
4123
+ ok: false,
4124
+ reason: 'deep_link_route_too_large',
4125
+ fieldPath: 'deepLinkRoute',
4126
+ actualBytes: bytes,
4127
+ limitBytes: eff.maxDeepLinkRouteBytes,
4128
+ };
4129
+ }
4130
+ return null;
4131
+ }
4132
+ function checkMcpScope(payload, eff) {
4133
+ const scope = payload.mcpScope;
4134
+ if (!scope)
4135
+ return null;
4136
+ const apis = scope.apis ?? [];
4137
+ if (apis.length > eff.maxMcpScopeApis) {
4138
+ return {
4139
+ ok: false,
4140
+ reason: 'mcp_scope_too_many_apis',
4141
+ fieldPath: 'mcpScope.apis',
4142
+ actualBytes: apis.length,
4143
+ limitBytes: eff.maxMcpScopeApis,
4144
+ };
4145
+ }
4146
+ for (let i = 0; i < apis.length; i++) {
4147
+ const bytes = utf8ByteLength(apis[i]);
4148
+ if (bytes > eff.maxMcpScopeApiBytes) {
4149
+ return {
4150
+ ok: false,
4151
+ reason: 'mcp_scope_api_too_large',
4152
+ fieldPath: `mcpScope.apis[${i}]`,
4153
+ actualBytes: bytes,
4154
+ limitBytes: eff.maxMcpScopeApiBytes,
4155
+ };
4156
+ }
4157
+ }
4158
+ return null;
4159
+ }
3714
4160
  /**
3715
4161
  * Returns a NEW payload with all string fields trimmed to fit the per-field cap, the
3716
4162
  * `plainTextFallback` trimmed to its cap, and the full envelope re-checked against the
@@ -3816,6 +4262,234 @@ function safeStringify(value) {
3816
4262
  });
3817
4263
  }
3818
4264
 
4265
+ /**
4266
+ * Imperative sibling to {@link AgentCommandRegistry} / {@link AgentDropRegistry}.
4267
+ *
4268
+ * Apps call {@link dispatch} to push a typed {@link AgentAction} onto the bus;
4269
+ * the agent panel subscribes once at construct and routes by verb. The bus
4270
+ * itself is a thin pass-through — it does NOT decide UI behaviour. The
4271
+ * subscriber (agent-panel) owns: showing the panel, staging the chip,
4272
+ * triggering the flight animation, and binding the command. This keeps the
4273
+ * DS free of host policy.
4274
+ *
4275
+ * Federation-safe: `providedIn: 'root'` + `sharedMappings: ['@mohamedatia/fly-design-system']`
4276
+ * give every federated remote the same singleton, so a remote's "Explain"
4277
+ * button reaches the host's panel without any cross-bundle wiring.
4278
+ *
4279
+ * Validation runs synchronously inside `dispatch` so a caller that sends an
4280
+ * oversize payload sees the throw at their site, not on the subscriber. The
4281
+ * subscriber therefore never has to defend against malformed envelopes.
4282
+ */
4283
+ class AgentActionBus {
4284
+ _actions$ = new Subject();
4285
+ /** Hot stream of actions in dispatch order. Subscribers receive only
4286
+ * actions dispatched AFTER they subscribe — late subscribers see nothing
4287
+ * retroactively. Use {@link lastAction} for the latest snapshot. */
4288
+ actions$ = this._actions$.asObservable();
4289
+ /** Most recent action — for DevTools, smoke tests, and late-subscriber
4290
+ * catch-up. Null until the first successful dispatch. */
4291
+ lastAction = signal(null, ...(ngDevMode ? [{ debugName: "lastAction" }] : /* istanbul ignore next */ []));
4292
+ /**
4293
+ * The action currently being processed by the subscriber, or null when
4294
+ * none. Set by {@link dispatch} immediately before emitting on
4295
+ * {@link actions$}; cleared by the subscriber via {@link settle} once
4296
+ * it finishes its handler (success or fail). Lets the dispatcher render
4297
+ * a busy state on the originating control — e.g. a card swapping its
4298
+ * sparkle icon for a spinner while the agent panel mints the optimistic
4299
+ * thread and starts the request. Identity check (`bus.inFlight() === act`)
4300
+ * is the panel-side contract; dispatchers usually project to a stable id
4301
+ * inside the payload (e.g. <c>reportId</c>) to scope busy-state visually.
4302
+ *
4303
+ * If multiple dispatches race, the latest wins — the prior in-flight
4304
+ * action is dropped on the floor here (the panel may still handle it,
4305
+ * but the dispatcher's busy indicator follows the newer action). Apps
4306
+ * that need stricter single-flight semantics should guard at the call
4307
+ * site (the agent-panel's <c>_pendingTempThreadId</c> already does so
4308
+ * for the explain verb).
4309
+ */
4310
+ inFlight = signal(null, ...(ngDevMode ? [{ debugName: "inFlight" }] : /* istanbul ignore next */ []));
4311
+ /**
4312
+ * Push an action onto the bus.
4313
+ *
4314
+ * Throws synchronously when:
4315
+ * - `dispatch === 'auto'` (not implemented in this DS version) — see
4316
+ * {@link AgentActionUnsupportedDispatchError}.
4317
+ * - the payload fails {@link validateAgentPayload} (oversize, invalid
4318
+ * version, invalid kind). The error message carries the field path
4319
+ * so the caller can fix the offending field.
4320
+ *
4321
+ * Subscribers see the action via {@link actions$} on the next tick of
4322
+ * the Subject; the {@link lastAction} signal updates synchronously
4323
+ * before the Subject emits so an effect reading both stays consistent.
4324
+ */
4325
+ dispatch(action) {
4326
+ if (action.dispatch === 'auto') {
4327
+ throw new AgentActionUnsupportedDispatchError(action.dispatch);
4328
+ }
4329
+ const v = validateAgentPayload(action.payload);
4330
+ if (!v.ok) {
4331
+ const where = v.fieldPath ? ` at ${v.fieldPath}` : '';
4332
+ throw new Error(`AgentActionBus.dispatch: invalid payload — ${v.reason}${where}`);
4333
+ }
4334
+ const a = action;
4335
+ this.lastAction.set(a);
4336
+ this.inFlight.set(a);
4337
+ this._actions$.next(a);
4338
+ }
4339
+ /**
4340
+ * Subscriber contract: call after the handler for {@link inFlight}
4341
+ * completes (success or fail). Only clears {@link inFlight} if it still
4342
+ * points at the passed action — a no-op when a later dispatch already
4343
+ * superseded it. Pass the same action reference the subscriber received
4344
+ * from {@link actions$}; identity is the gate.
4345
+ */
4346
+ settle(action) {
4347
+ if (this.inFlight() === action) {
4348
+ this.inFlight.set(null);
4349
+ }
4350
+ }
4351
+ /**
4352
+ * Semantic alias of {@link settle} for explicit user-driven cancellation —
4353
+ * e.g. a future "Stop" button in the agent input tray, or a dispatcher
4354
+ * teardown that wants to abandon its own in-flight action. Identical
4355
+ * runtime behaviour (identity check + clear), but the two-method surface
4356
+ * lets the UI distinguish "handler finished" from "user said no" in
4357
+ * telemetry / logs without sniffing a "reason" parameter.
4358
+ *
4359
+ * Pass the same action reference returned from {@link inFlight} or held
4360
+ * by the dispatcher; identity is the gate.
4361
+ */
4362
+ cancel(action) {
4363
+ if (this.inFlight() === action) {
4364
+ this.inFlight.set(null);
4365
+ }
4366
+ }
4367
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentActionBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4368
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentActionBus, providedIn: 'root' });
4369
+ }
4370
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentActionBus, decorators: [{
4371
+ type: Injectable,
4372
+ args: [{ providedIn: 'root' }]
4373
+ }] });
4374
+
4375
+ /**
4376
+ * FLIP-style entry animation for payloads landing in the agent panel.
4377
+ *
4378
+ * Pure DOM + Web Animations API — no Chart.js, no Angular animations module,
4379
+ * no CSS transitions racing layout. Honours `prefers-reduced-motion`: the
4380
+ * ghost is appended then removed without animating when the user asked for
4381
+ * less motion (so DOM side-effects stay consistent).
4382
+ *
4383
+ * Lifecycle:
4384
+ * 1. The agent panel calls {@link registerTarget} in `ngAfterViewInit`
4385
+ * with its header element.
4386
+ * 2. A source app dispatches an `AgentAction` carrying an `originRect`
4387
+ * from `getBoundingClientRect()` on the click target.
4388
+ * 3. The bus subscriber calls {@link flyInto} with that rect.
4389
+ * 4. The animator creates a fixed-position ghost at the origin, animates
4390
+ * transform + opacity toward the registered target's rect, then
4391
+ * removes itself on `onfinish` / `oncancel`.
4392
+ *
4393
+ * Uses `getBoundingClientRect()` (physical viewport coords) so the animation
4394
+ * is RTL-correct without inset-inline math — the rect already encodes the
4395
+ * physical position regardless of `dir`.
4396
+ *
4397
+ * The 900 ms duration and easing curve are deliberately hardcoded — making
4398
+ * them configurable surfaces an API the host can't usefully tune without
4399
+ * understanding motion design as a whole.
4400
+ */
4401
+ class AgentFlightAnimator {
4402
+ /** Hardcoded — see class doc. */
4403
+ static DURATION_MS = 900;
4404
+ static EASING = 'cubic-bezier(.2,.7,.2,1)';
4405
+ /** Floor the target/source scale ratio so a tiny target rect doesn't
4406
+ * collapse the ghost to invisibility before the animation finishes. */
4407
+ static MIN_SCALE = 0.05;
4408
+ targetEl = null;
4409
+ /** Called by the panel host to publish where flights should land. Pass
4410
+ * `null` on destroy so a re-mounted panel doesn't leave the animator
4411
+ * pointing at a detached node. */
4412
+ registerTarget(el) {
4413
+ this.targetEl = el;
4414
+ }
4415
+ /**
4416
+ * Animate a ghost element from {@link from} to the registered target's
4417
+ * rect. No-ops when:
4418
+ * - no target is registered (silent — panel may not be mounted yet)
4419
+ * - running outside a browser (SSR safety)
4420
+ * - the user has `prefers-reduced-motion: reduce` set (DOM is still
4421
+ * touched so callers see consistent side-effects, but no animation
4422
+ * runs)
4423
+ *
4424
+ * The ghost is appended to `document.body` (not the panel) so a parent
4425
+ * `overflow: hidden` on the panel can't clip the flight path.
4426
+ */
4427
+ flyInto(from, opts = {}) {
4428
+ if (!this.targetEl)
4429
+ return;
4430
+ if (typeof document === 'undefined')
4431
+ return;
4432
+ const reduced = typeof window !== 'undefined' && typeof window.matchMedia === 'function'
4433
+ ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
4434
+ : false;
4435
+ const to = this.targetEl.getBoundingClientRect();
4436
+ const ghost = document.createElement('div');
4437
+ ghost.className = 'fly-agent-flight-ghost';
4438
+ // The ghost is purely decorative — it duplicates the source content
4439
+ // (chart title + snapshot) that the user just clicked and screen-reader
4440
+ // users have already heard via the sparkle button's aria-label. Re-
4441
+ // announcing the same content during the flight would be noisy at best
4442
+ // and confusing at worst. aria-hidden hides the subtree from assistive
4443
+ // tech while leaving it visible for sighted users.
4444
+ ghost.setAttribute('aria-hidden', 'true');
4445
+ // role="presentation" is belt-and-braces — even if a future renderer
4446
+ // walks the tree looking for semantic landmarks, the ghost has none.
4447
+ ghost.setAttribute('role', 'presentation');
4448
+ if (opts.previewHtml) {
4449
+ // Caller is responsible for safe HTML — the bus subscriber escapes
4450
+ // plainTextFallback before passing it here.
4451
+ ghost.innerHTML = opts.previewHtml;
4452
+ }
4453
+ Object.assign(ghost.style, {
4454
+ position: 'fixed',
4455
+ left: `${from.left}px`,
4456
+ top: `${from.top}px`,
4457
+ width: `${from.width}px`,
4458
+ height: `${from.height}px`,
4459
+ pointerEvents: 'none',
4460
+ zIndex: '99999',
4461
+ transformOrigin: 'top left',
4462
+ willChange: 'transform, opacity',
4463
+ });
4464
+ document.body.appendChild(ghost);
4465
+ if (reduced) {
4466
+ ghost.remove();
4467
+ return;
4468
+ }
4469
+ const dx = to.left - from.left;
4470
+ const dy = to.top - from.top;
4471
+ const sx = Math.max(to.width / from.width, AgentFlightAnimator.MIN_SCALE);
4472
+ const sy = Math.max(to.height / from.height, AgentFlightAnimator.MIN_SCALE);
4473
+ const anim = ghost.animate([
4474
+ { transform: 'translate(0,0) scale(1,1)', opacity: 1 },
4475
+ { transform: `translate(${dx}px,${dy}px) scale(${sx},${sy})`, opacity: 0.15 },
4476
+ ], {
4477
+ duration: AgentFlightAnimator.DURATION_MS,
4478
+ easing: AgentFlightAnimator.EASING,
4479
+ fill: 'forwards',
4480
+ });
4481
+ const cleanup = () => ghost.remove();
4482
+ anim.onfinish = cleanup;
4483
+ anim.oncancel = cleanup;
4484
+ }
4485
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentFlightAnimator, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4486
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentFlightAnimator, providedIn: 'root' });
4487
+ }
4488
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentFlightAnimator, decorators: [{
4489
+ type: Injectable,
4490
+ args: [{ providedIn: 'root' }]
4491
+ }] });
4492
+
3819
4493
  /**
3820
4494
  * Strict whitelist for the ghost element id. Letter-led ASCII, allows word/hyphen/colon/dot,
3821
4495
  * capped at 128 chars. Anything else is rejected before reaching `getElementById` so a
@@ -3922,6 +4596,113 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
3922
4596
  args: ['dragstart', ['$event']]
3923
4597
  }] } });
3924
4598
 
4599
+ /**
4600
+ * Renders an authenticated image into a host `<img>` element by fetching the
4601
+ * resource as a blob through Angular's `HttpClient` and binding the resulting
4602
+ * object URL to the host's `src` attribute.
4603
+ *
4604
+ * Why this exists: every Business / Supporting app stores files in the Files
4605
+ * Manager service, which authenticates with a Bearer JWT. A raw `<img src>`
4606
+ * request bypasses the Angular HTTP interceptor — the browser issues the GET
4607
+ * without the Authorization header, so the response is 401 and the image is
4608
+ * broken. The standard workaround is roughly fifteen lines of `effect` +
4609
+ * `URL.createObjectURL` + `URL.revokeObjectURL` per call site (see prior
4610
+ * inline copies in `FlyImageUploadComponent` and the Circles trend-detail
4611
+ * cover). This directive centralizes that pattern so:
4612
+ *
4613
+ * * Memory hygiene is automatic — the previous object URL is revoked
4614
+ * whenever the input changes and on destroy. Easy to forget inline.
4615
+ * * Error handling is uniform — a fetch failure clears the host `src`
4616
+ * so the browser shows its broken-image affordance once, not the prior
4617
+ * image stuck on screen.
4618
+ * * A future protocol change (per-request signed URLs, CDN tokens, etc.)
4619
+ * lands in one place rather than across every app's image bindings.
4620
+ *
4621
+ * Usage:
4622
+ *
4623
+ * ```html
4624
+ * <img [flySecureSrc]="trend.coverImageId" alt="" class="cover-img" />
4625
+ * ```
4626
+ *
4627
+ * Pass a full URL when the resource lives somewhere other than the default
4628
+ * Files Manager download endpoint:
4629
+ *
4630
+ * ```html
4631
+ * <img [flySecureSrc]="'/api/avatars/' + user.id" alt="" />
4632
+ * ```
4633
+ *
4634
+ * A bare file id (no `/` prefix) is resolved against
4635
+ * `/api/files/{id}/download`, which matches the Files Manager convention
4636
+ * used everywhere in the platform. Anything that starts with `/` is treated
4637
+ * as a literal URL and passed through unchanged — apps with bespoke download
4638
+ * routes (signed Excel exports, attachment thumbnails, …) keep working
4639
+ * without a wrapper.
4640
+ */
4641
+ class FlySecureSrcDirective {
4642
+ /**
4643
+ * File id or absolute path. Passing `null` / `undefined` / `''` clears the
4644
+ * `src` attribute and revokes the previous blob. Useful when binding to a
4645
+ * signal whose value can become empty (e.g. after a file is detached).
4646
+ */
4647
+ flySecureSrc = input(...(ngDevMode ? [undefined, { debugName: "flySecureSrc" }] : /* istanbul ignore next */ []));
4648
+ http = inject(HttpClient);
4649
+ host = inject((ElementRef));
4650
+ renderer = inject(Renderer2);
4651
+ /**
4652
+ * The object URL currently bound to the host's `src` attribute. Tracked so
4653
+ * we can revoke it before binding a new one (avoids leaking blob memory
4654
+ * across rapid input changes — e.g. carousel scrubbing) and on destroy.
4655
+ */
4656
+ currentUrl = null;
4657
+ constructor() {
4658
+ effect((onCleanup) => {
4659
+ const raw = this.flySecureSrc();
4660
+ this.releaseCurrent();
4661
+ this.renderer.removeAttribute(this.host.nativeElement, 'src');
4662
+ if (!raw)
4663
+ return;
4664
+ const url = raw.startsWith('/') ? raw : `/api/files/${raw}/download`;
4665
+ const sub = this.http
4666
+ .get(url, { responseType: 'blob' })
4667
+ .subscribe({
4668
+ next: (blob) => {
4669
+ const objectUrl = URL.createObjectURL(blob);
4670
+ this.currentUrl = objectUrl;
4671
+ this.renderer.setAttribute(this.host.nativeElement, 'src', objectUrl);
4672
+ },
4673
+ // A 401 / 403 / 404 here means the image is unavailable for this user.
4674
+ // Don't loop or retry — clear the src and let the host page render its
4675
+ // own placeholder. Logging is intentionally silent: it's expected on
4676
+ // permission boundaries (e.g. shared-evidence on a trend you can read
4677
+ // but whose file you can't), and stack-traces would be noise.
4678
+ error: () => {
4679
+ this.releaseCurrent();
4680
+ this.renderer.removeAttribute(this.host.nativeElement, 'src');
4681
+ },
4682
+ });
4683
+ onCleanup(() => sub.unsubscribe());
4684
+ });
4685
+ }
4686
+ ngOnDestroy() {
4687
+ this.releaseCurrent();
4688
+ }
4689
+ releaseCurrent() {
4690
+ if (this.currentUrl) {
4691
+ URL.revokeObjectURL(this.currentUrl);
4692
+ this.currentUrl = null;
4693
+ }
4694
+ }
4695
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlySecureSrcDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4696
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: FlySecureSrcDirective, isStandalone: true, selector: "img[flySecureSrc]", inputs: { flySecureSrc: { classPropertyName: "flySecureSrc", publicName: "flySecureSrc", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
4697
+ }
4698
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlySecureSrcDirective, decorators: [{
4699
+ type: Directive,
4700
+ args: [{
4701
+ selector: 'img[flySecureSrc]',
4702
+ standalone: true,
4703
+ }]
4704
+ }], ctorParameters: () => [], propDecorators: { flySecureSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "flySecureSrc", required: false }] }] } });
4705
+
3925
4706
  /**
3926
4707
  * Verbatim TypeScript port of the C# `LanguageDTO[] _AllLanguages` array supplied by
3927
4708
  * product. **31 entries**, in the canonical order. Three RTL flagged: `he-IL`, `ur-IN`,
@@ -4533,6 +5314,31 @@ function isRtlLocaleEntry(entry) {
4533
5314
  * centralising the canonicalization rule that strips `-service` from
4534
5315
  * JWT client_ids (`circles-service` → `circles`) so chips, banners,
4535
5316
  * and launchers stay consistent across the shell.
5317
+ * v2.6.0: Agent action bus + flight animator. New imperative sibling to
5318
+ * `AgentCommandRegistry` / `AgentDropRegistry`: `AgentActionBus` lets
5319
+ * any app push a typed `AgentAction { verb, payload, dispatch, originRect }`
5320
+ * to the agent panel without going through drag-drop. `AgentFlightAnimator`
5321
+ * plays a FLIP-style transform animation from the source DOM rect to the
5322
+ * registered panel-header rect (hardcoded 420 ms / cubic-bezier; honours
5323
+ * prefers-reduced-motion). Phase 1 supports `dispatch: 'stage'` only —
5324
+ * `'auto'` throws `AgentActionUnsupportedDispatchError` until
5325
+ * `AgentInputComponent.programmaticSubmit` ships. Re-uses
5326
+ * `AgentDragPayload<T>` as the wire envelope so drag and programmatic
5327
+ * transports never fork. New verbs: `explain`, `why-empty`,
5328
+ * `compose-query`, `compare`, `forecast`, `summarize`.
5329
+ * v2.10.0: Per-window help-deeplink contract. The shell renders a help-icon
5330
+ * button in every non-chromeless `<fly-window>` titlebar. By default
5331
+ * the deeplink uses `win.appId`; apps override or augment via the
5332
+ * new `WINDOW_HELP_HINT` InjectionToken (a `WritableSignal<WindowHelpHint | null>`)
5333
+ * provided per-window. `WindowHelpHint` is `{ appId?, topic? }` —
5334
+ * `topic` is a free-form search-query seed updated dynamically as the
5335
+ * user navigates within the app. New federation-safe channel
5336
+ * `FLY_WINDOW_HELP_HINT_EVENT` mirrors the DI surface for federated
5337
+ * remotes. New exports: `WindowHelpHint`, `WINDOW_HELP_HINT`,
5338
+ * `FLY_WINDOW_HELP_HINT_EVENT`, `FlyWindowHelpHintEventDetail`.
5339
+ * Backed by the help-center reader's new `params.topic` launch payload
5340
+ * and "no help for this app yet" empty-state. See
5341
+ * `skills/help-center.md` and `skills/desktop-shell-angular.md`.
4536
5342
  * v2.5.1: New `MessageBoxService.showAcknowledged()` entry point returning
4537
5343
  * `DialogResultWithAcknowledgement` for "Don't ask again"-style flows.
4538
5344
  * The `dontAskAgain` config (`MessageBoxDontAskAgainConfig`) lives ONLY on
@@ -4560,5 +5366,5 @@ const AUDIENCE_ERROR_CODES = {
4560
5366
  * Generated bundle index. Do not edit.
4561
5367
  */
4562
5368
 
4563
- export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentCommandRegistry, AgentDropRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DialogResult, FLYOS_LAUNCH_EVENT, FLY_LOCALE_CATALOG, FLY_REMOTE_BASE_PATH, FLY_REMOTE_ROUTES, FLY_THEME_MODE_IDS, FlyAgentDraggableDirective, FlyBlockUiComponent, FlyFileUploadComponent, FlyImageUploadComponent, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlyThemeService, FlyosPendingLaunchesGlobalKey, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, TranslatePipe, WINDOW_DATA, WindowManagerService, findLocaleByDialect, findLocaleByPrefix, isRtlLocale, isRtlLocaleEntry, loadRemoteStyles, matchFlyRoutePattern, normalizeFlyTheme, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload };
5369
+ export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DialogResult, FLYOS_LAUNCH_EVENT, FLY_LOCALE_CATALOG, FLY_REMOTE_BASE_PATH, FLY_REMOTE_ROUTES, FLY_THEME_MODE_IDS, FLY_WINDOW_HELP_HINT_EVENT, FlyAgentDraggableDirective, FlyBlockUiComponent, FlyFileUploadComponent, FlyImageUploadComponent, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySecureSrcDirective, FlyThemeService, FlyWindowHelpService, FlyosPendingLaunchesGlobalKey, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, findLocaleByDialect, findLocaleByPrefix, isRtlLocale, isRtlLocaleEntry, loadRemoteStyles, matchFlyRoutePattern, normalizeFlyTheme, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload };
4564
5370
  //# sourceMappingURL=mohamedatia-fly-design-system.mjs.map