@mohamedatia/fly-design-system 2.10.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.
- package/fesm2022/mohamedatia-fly-design-system.mjs +706 -7
- package/fesm2022/mohamedatia-fly-design-system.mjs.map +1 -1
- package/package.json +1 -1
- package/scss/_theme-light.scss +7 -1
- package/types/mohamedatia-fly-design-system.d.ts +651 -13
- package/types/mohamedatia-fly-design-system.d.ts.map +1 -1
|
@@ -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
|
-
/**
|
|
3562
|
-
|
|
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`
|
|
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.
|
|
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
|
|
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
|
|
@@ -4640,6 +5314,31 @@ function isRtlLocaleEntry(entry) {
|
|
|
4640
5314
|
* centralising the canonicalization rule that strips `-service` from
|
|
4641
5315
|
* JWT client_ids (`circles-service` → `circles`) so chips, banners,
|
|
4642
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`.
|
|
4643
5342
|
* v2.5.1: New `MessageBoxService.showAcknowledged()` entry point returning
|
|
4644
5343
|
* `DialogResultWithAcknowledgement` for "Don't ask again"-style flows.
|
|
4645
5344
|
* The `dontAskAgain` config (`MessageBoxDontAskAgainConfig`) lives ONLY on
|
|
@@ -4667,5 +5366,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
4667
5366
|
* Generated bundle index. Do not edit.
|
|
4668
5367
|
*/
|
|
4669
5368
|
|
|
4670
|
-
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, FlySecureSrcDirective, 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 };
|
|
4671
5370
|
//# sourceMappingURL=mohamedatia-fly-design-system.mjs.map
|