@flyos/design-system 3.13.0 → 3.16.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 _angular_core from '@angular/core';
2
- import { Type, InjectionToken, Signal, WritableSignal, OnInit, EventEmitter, OnChanges, OnDestroy, SimpleChanges, EnvironmentProviders, PipeTransform, signal, AfterViewInit, ElementRef, AfterViewChecked, ErrorHandler, Provider, TemplateRef } from '@angular/core';
2
+ import { Type, InjectionToken, Signal, WritableSignal, OnInit, EventEmitter, OnChanges, OnDestroy, SimpleChanges, EnvironmentProviders, PipeTransform, signal, AfterViewInit, ElementRef, AfterViewChecked, Provider, ErrorHandler, TemplateRef } from '@angular/core';
3
3
  import * as rxjs from 'rxjs';
4
4
  import { Observable, MonoTypeOperatorFunction } from 'rxjs';
5
5
  import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors } from '@angular/forms';
@@ -122,7 +122,7 @@ interface LaunchContext {
122
122
  * provisioning. Remotes subscribe to deep links via the federation-safe surfaces
123
123
  * instead: `FLYOS_LAUNCH_EVENT` (window CustomEvent) plus the
124
124
  * `__FLYOS_PENDING_LAUNCHES__` registry on `globalThis`. See
125
- * <c>skills/cross-app-deep-linking.md</c>.
125
+ * <c>skills/platform-deep-linking.md</c>.
126
126
  */
127
127
  declare const LAUNCH_CONTEXT: InjectionToken<Signal<LaunchContext | null>>;
128
128
  /**
@@ -168,7 +168,7 @@ type FlyosPendingLaunches = Record<string, LaunchContext>;
168
168
  *
169
169
  * Only the shell may act on this: it owns the app registry and the window manager,
170
170
  * neither of which is reachable from a federated remote. See
171
- * `skills/cross-app-deep-linking.md` § "Initiator side (federated remote)".
171
+ * `skills/platform-deep-linking.md` § "Initiator side (federated remote)".
172
172
  */
173
173
  interface FlyLaunchRequestDetail {
174
174
  /** Shell-registry app id of the app to open, e.g. `'chats'`. */
@@ -2377,7 +2377,7 @@ declare class FlyRemoteRouter {
2377
2377
  * `/trends/abc`) instead would leave a host-unroutable path in the bar — on
2378
2378
  * reload the shell router falls through to its `**` wildcard, redirects to
2379
2379
  * `/desktop`, and the deep link is silently lost. See
2380
- * skills/cross-app-deep-linking.md.
2380
+ * skills/platform-deep-linking.md.
2381
2381
  *
2382
2382
  * `flyRemoteUrl` is still stashed in the history state so the popstate
2383
2383
  * listener restores the exact remote URL for back/forward without re-parsing
@@ -2467,6 +2467,21 @@ interface ConnectRemoteLaunchOptions {
2467
2467
  * `route => void flyRouter.navigateByUrl(route)`.
2468
2468
  */
2469
2469
  readonly navigate: (route: string) => void;
2470
+ /**
2471
+ * Optional side-channel for the rest of the launch — most importantly `ctx.params`,
2472
+ * which {@link navigate} cannot carry (it is handed a route string).
2473
+ *
2474
+ * Called once per accepted (version-deduped) launch, BEFORE {@link navigate}, and
2475
+ * called even when `ctx.route` is `null` — a params-only launch ("re-filter what is
2476
+ * already open") is a real case and must not be swallowed by the null-route
2477
+ * navigation no-op.
2478
+ *
2479
+ * Prefer {@link FlyLaunchContextService} over this callback for anything that isn't
2480
+ * the root component: the helper publishes every accepted launch there too, so a
2481
+ * lazily-mounted leaf can `inject(FlyLaunchContextService).contextFor(appId)` without
2482
+ * the root having to thread a value down. This hook is for root-local reactions.
2483
+ */
2484
+ readonly onLaunch?: (ctx: LaunchContext) => void;
2470
2485
  /**
2471
2486
  * Gate. Pass your `isEmbedded` flag — when `false` the helper is a no-op (a
2472
2487
  * standalone remote has no shell dispatcher). Defaults to `true`. The helper is
@@ -2479,7 +2494,7 @@ interface ConnectRemoteLaunchOptions {
2479
2494
  * Federation-safe deep-link RECEIVER for an Angular Native-Federation remote — the
2480
2495
  * remote-side counterpart of the shell's `connectWindowRoute`. Collapses the
2481
2496
  * drain + dedupe + listen + cleanup boilerplate every remote root used to
2482
- * hand-roll (see `skills/cross-app-deep-linking.md` § "Receiver side") into one
2497
+ * hand-roll (see `skills/platform-deep-linking.md` § "Receiver side") into one
2483
2498
  * call, so the correct contract can't be hand-rolled wrong.
2484
2499
  *
2485
2500
  * **Must be called from an injection context** (component constructor or a field
@@ -2502,12 +2517,32 @@ interface ConnectRemoteLaunchOptions {
2502
2517
  * `appId`: the {@link FLYOS_LAUNCH_EVENT} window `CustomEvent` and the
2503
2518
  * `globalThis[FlyosPendingLaunchesGlobalKey]` registry. This helper consumes both.
2504
2519
  *
2520
+ * ## The launch PARAMS
2521
+ * `navigate` receives a route string, so it structurally cannot carry `ctx.params` —
2522
+ * the payload dashboard drill-down rides on (`skills/dashboard-reports.md` §
2523
+ * "Drill-down" sends `{ [filterParam]: clickedValue }`). Before DS 3.15.0 this helper
2524
+ * read `ctx.route` and dropped the rest, so every drill-down into a federated remote
2525
+ * opened an UNFILTERED register, and consumers hand-rolled a second, non-destructive
2526
+ * peek at the same pending-launch registry — with a load-bearing construction-ordering
2527
+ * dependency on running before this helper's drain deleted the entry.
2528
+ *
2529
+ * Every accepted launch is now published to {@link FlyLaunchContextService}, keyed by
2530
+ * `appId`, so any component in the remote reads the full context — params included —
2531
+ * with no ordering constraint:
2532
+ *
2533
+ * ```ts
2534
+ * private readonly ctx = inject(FlyLaunchContextService).contextFor('ppm');
2535
+ * ```
2536
+ *
2537
+ * {@link ConnectRemoteLaunchOptions.onLaunch} is the callback form, for a root-local
2538
+ * reaction that needs no injectable.
2539
+ *
2505
2540
  * ## Two inbound paths, one dedupe
2506
2541
  * 1. **First-mount drain.** The launch event fires BEFORE this listener attaches
2507
2542
  * for the very first launch, so the shell stashes the latest context per
2508
2543
  * `appId` on the `globalThis` registry. The helper reads it then **deletes**
2509
2544
  * it — never leaving a stale entry behind (a leftover entry would re-apply on
2510
- * every re-mount; this is the failure mode `skills/cross-app-deep-linking.md`
2545
+ * every re-mount; this is the failure mode `skills/platform-deep-linking.md`
2511
2546
  * § "What NOT to do" warns about).
2512
2547
  * 2. **Live event.** Re-launches into an already-mounted remote arrive on
2513
2548
  * {@link FLYOS_LAUNCH_EVENT}; the `appId` filter is mandatory.
@@ -2528,6 +2563,111 @@ interface ConnectRemoteLaunchOptions {
2528
2563
  */
2529
2564
  declare function connectRemoteLaunch(opts: ConnectRemoteLaunchOptions): void;
2530
2565
 
2566
+ /**
2567
+ * Shared cross-bundle store key + sync event for the inbound launch channel.
2568
+ *
2569
+ * **Public contract**, for the same reason {@link FLY_REMOTE_CONTEXT_STORE_KEY} is: a
2570
+ * consumer pinned to a DS older than 3.15.0 can read this slot directly (same shape,
2571
+ * keyed by appId) rather than re-deriving the peek-the-pending-registry workaround.
2572
+ * Keep these literals stable; they are an integration boundary.
2573
+ */
2574
+ declare const FLY_LAUNCH_CONTEXT_STORE_KEY = "__flyLaunchContext__";
2575
+ declare const FLY_LAUNCH_CONTEXT_EVENT = "fly:launch-context";
2576
+ /**
2577
+ * Shell → remote launch channel: the **whole** {@link LaunchContext} of the most
2578
+ * recent launch into each app, `params` included.
2579
+ *
2580
+ * Why this exists
2581
+ * ---------------
2582
+ * {@link connectRemoteLaunch} consumes the two DI-free launch surfaces
2583
+ * (`FLYOS_LAUNCH_EVENT` + the `__FLYOS_PENDING_LAUNCHES__` registry) and applies the
2584
+ * `route` half by navigating. Until DS 3.15.0 it read `ctx.route` and **nothing else**,
2585
+ * so `ctx.params` — the payload dashboard drill-down carries (`skills/dashboard-reports.md`
2586
+ * § "Drill-down": `{ [filterParam]: clickedValue }`) — was silently dropped, and every
2587
+ * drill-down into a federated remote landed on an UNFILTERED register.
2588
+ *
2589
+ * The shell-side answer to "read the launch params" is `inject(LAUNCH_CONTEXT)`, which a
2590
+ * federated remote must not use: Native Federation can split that `InjectionToken`
2591
+ * instance across host and remote bundles, so `inject()` returns `null` (see
2592
+ * `LAUNCH_CONTEXT`'s own doc). This service is the federation-safe counterpart —
2593
+ * `connectRemoteLaunch` publishes here, and any component in the remote (however deep, in
2594
+ * whatever lazy chunk) injects this and reads the same context.
2595
+ *
2596
+ * Why `globalThis`, not just the DI singleton
2597
+ * -------------------------------------------
2598
+ * Identical rationale to {@link FlyRemoteContextService}: the shell builds the design
2599
+ * system from workspace SOURCE while remotes consume the published npm package, and NF's
2600
+ * version negotiation has silently split `providedIn: 'root'` before. State therefore
2601
+ * lives on `globalThis` (see {@link FLY_LAUNCH_CONTEXT_STORE_KEY}); {@link context} /
2602
+ * {@link params} read it directly (split-proof, synchronous) and a per-instance signal
2603
+ * mirrors it for reactive consumers, re-synced from a `globalThis` event whenever another
2604
+ * copy of the service mutates the store.
2605
+ *
2606
+ * Reading it
2607
+ * ----------
2608
+ * ```ts
2609
+ * private readonly launch = inject(FlyLaunchContextService);
2610
+ * private readonly drillDown = this.launch.contextFor('ppm');
2611
+ * private lastVersion = -1; // -1 never collides: the shell counts from 1
2612
+ *
2613
+ * constructor() {
2614
+ * effect(() => {
2615
+ * const ctx = this.drillDown();
2616
+ * if (!ctx || ctx.version === this.lastVersion) return; // version-dedupe is the consumer's job
2617
+ * this.lastVersion = ctx.version;
2618
+ * const status = ctx.params?.['status'];
2619
+ * if (typeof status === 'string') this.applyStatusFilter(status);
2620
+ * });
2621
+ * }
2622
+ * ```
2623
+ *
2624
+ * Staleness
2625
+ * ---------
2626
+ * An entry is **not** dropped once read — a lazily-mounted consumer must still find the
2627
+ * launch that opened the window, which may have arrived long before its chunk loaded (the
2628
+ * exact ordering hazard the hand-rolled "peek the pending registry before
2629
+ * `connectRemoteLaunch` drains it" workaround existed to dodge). It cannot go stale across
2630
+ * a remount for the same reason `connectRemoteLaunch`'s `lastVersion` reset is safe: the
2631
+ * shell is the single writer and `reloadWindowContent` ALWAYS re-publishes the window's
2632
+ * CURRENT context (`params: null`, bumped version) before dropping the injector, so the
2633
+ * remount's drain overwrites this slot before any consumer mounts. Consumers dedupe on
2634
+ * `version` regardless, exactly as they must for the live-event path.
2635
+ *
2636
+ * Keyed by `appId` (last launch wins) — a single live window per app, matching
2637
+ * `__FLYOS_PENDING_LAUNCHES__`'s own keying.
2638
+ */
2639
+ declare class FlyLaunchContextService {
2640
+ private readonly _byApp;
2641
+ /** Every app's latest inbound launch context, keyed by app id. Reactive. */
2642
+ readonly contexts: Signal<ReadonlyMap<string, LaunchContext>>;
2643
+ /**
2644
+ * `contextFor` memo — one computed per app id, so repeated calls (a component field
2645
+ * initializer re-running per instance) hand back the SAME signal instead of leaking a
2646
+ * new computed each time.
2647
+ */
2648
+ private readonly perApp;
2649
+ constructor();
2650
+ /**
2651
+ * Record an inbound launch for an app. Called by {@link connectRemoteLaunch} once per
2652
+ * accepted (version-deduped) launch — a remote should not normally call this itself.
2653
+ */
2654
+ publish(appId: string, ctx: LaunchContext): void;
2655
+ /** Latest launch context for an app as a signal — the reactive read path. */
2656
+ contextFor(appId: string): Signal<LaunchContext | null>;
2657
+ /** Latest launch context for an app, read straight off the store. `null` when none. */
2658
+ context(appId: string): LaunchContext | null;
2659
+ /**
2660
+ * The `params` half of an app's latest launch — the drill-down payload. `null` when the
2661
+ * app has no launch context, or when the launch carried no params. The **consumer**
2662
+ * interprets the keys against its own filter contract; the platform never does.
2663
+ */
2664
+ params(appId: string): Readonly<Record<string, unknown>> | null;
2665
+ /** Drop an app's launch context. No-op if absent. Mainly for teardown and tests. */
2666
+ clear(appId: string): void;
2667
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyLaunchContextService, never>;
2668
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<FlyLaunchContextService>;
2669
+ }
2670
+
2531
2671
  /**
2532
2672
  * A federated remote's current navigation context, as published to the shell.
2533
2673
  *
@@ -3903,7 +4043,7 @@ interface AgentDragPayload<T = unknown> {
3903
4043
  * DS package is shell-agnostic and shouldn't grow a second copy of the
3904
4044
  * URL grammar. Apps building envelopes are expected to stamp routes that
3905
4045
  * match their own internal route schema (e.g. dashboard publishes
3906
- * `/reports/{id}` and `/custom/{id}`); see `skills/cross-app-deep-linking.md`.
4046
+ * `/reports/{id}` and `/custom/{id}`); see `skills/platform-deep-linking.md`.
3907
4047
  *
3908
4048
  * Backwards-compat: absent on v1 payloads and on v2 payloads built
3909
4049
  * before this field was added. Persisted JSONB chips without the field
@@ -5983,7 +6123,7 @@ interface FlySelectPanelPosition {
5983
6123
  * input still wins. It used to default to a hardcoded English literal that every consumer had to
5984
6124
  * remember to override — none did, so a searchable select read "Search…" under ar/fr/ur.
5985
6125
  * Styling composes the Nova token language (`--w*` ramp, `--accent`, `nova-glass.popover`) — see
5986
- * `skills/desktop-design-language.md` §3.2 and `.workflow/plans/ux-refresh/notes/S1.7-form-family.md`.
6126
+ * `skills/shell-design-language.md` §3.2 and `.workflow/plans/ux-refresh/notes/S1.7-form-family.md`.
5987
6127
  *
5988
6128
  * **Positioning (S1.7 — Nova):** the panel is `position: fixed`, measured from the trigger, and
5989
6129
  * **portaled to `<body>`** the same way {@link ContextMenuComponent} escapes a clipping/glass
@@ -6514,6 +6654,21 @@ interface GanttRow {
6514
6654
  * expanding it emits {@link FlyGanttComponent.rowExpand} instead of revealing nothing.
6515
6655
  */
6516
6656
  hasChildren?: boolean;
6657
+ /**
6658
+ * Whether a row that currently has NO children is still a valid drag-to-reorder PARENT —
6659
+ * dropping another row directly beneath it files that row as its first child instead of as its
6660
+ * sibling. Independent of {@link hasChildren}: that field also paints a chevron, which is wrong
6661
+ * for a container with nothing in it yet (a milestone that has no tasks is a point-in-time
6662
+ * marker, not an expandable list with zero items). This field changes ONLY what
6663
+ * `resolveGanttReorderDrop` does with a drop on this row; it paints nothing.
6664
+ *
6665
+ * Existing rows with real children need no help — `hasChildren && !collapsed` already nests
6666
+ * into them. This exists for the row that CAN adopt a child but has not yet: the indent action
6667
+ * already reaches this case (`canIndentGanttRow` only checks depth, never child count), so
6668
+ * without this flag a drag and an indent onto the same empty container disagreed — one nested
6669
+ * the row, the other filed it as a root/backlog sibling with no explanation.
6670
+ */
6671
+ canAdoptChildren?: boolean;
6517
6672
  /**
6518
6673
  * Optional baseline (plan-of-record) date, ISO `YYYY-MM-DD`. When both `baselineStart` and
6519
6674
  * `baselineEnd` are set and {@link FlyGanttComponent.showBaselines} is on, the chart renders a
@@ -6569,6 +6724,43 @@ interface GanttDependencyCreate {
6569
6724
  toId: string;
6570
6725
  type: GanttDependencyType;
6571
6726
  }
6727
+ /**
6728
+ * Payload of {@link FlyGanttComponent.rowReorder} — a label row dragged to a new place in the tree.
6729
+ *
6730
+ * Carries the FULL new sibling order rather than a `before`/`after` hint, because that is the shape
6731
+ * every ordering endpoint actually takes (an ordered, complete id set) and because re-deriving it
6732
+ * consumer-side is where the chart's order and the consumer's start to disagree. `parentId` is the
6733
+ * parent AFTER the move — `null` for a root row — so a drop that also re-homes the row (dragging a
6734
+ * task from one summary group into another) is one event, not two.
6735
+ *
6736
+ * The chart does NOT judge whether a level change is legal: only the consumer knows whether its
6737
+ * two outline levels are the same kind of thing. Reject the move by simply not persisting it; the
6738
+ * rows are re-rendered from the consumer's own input either way, so nothing is left inconsistent.
6739
+ */
6740
+ interface GanttRowReorder {
6741
+ /** The row that moved. */
6742
+ readonly id: string;
6743
+ /** Its parent after the move; `null` = a root row. */
6744
+ readonly parentId: string | null;
6745
+ /** Zero-based slot among `parentId`'s children after the move. */
6746
+ readonly index: number;
6747
+ /** Every child of `parentId` after the move, in their new order (includes {@link id}). */
6748
+ readonly siblingIds: readonly string[];
6749
+ }
6750
+ /**
6751
+ * Payload of {@link FlyGanttComponent.rowIndentChange} — the outline-level nudge behind an
6752
+ * indent / outdent action.
6753
+ *
6754
+ * `delta` is `+1` (indent — become a child of the row above) or `-1` (outdent — become a sibling of
6755
+ * the current parent). `parentId` is the resulting parent, resolved by the chart from the row's
6756
+ * neighbours so every consumer does not re-implement "which row would adopt it": `null` means the
6757
+ * row lands at the root.
6758
+ */
6759
+ interface GanttRowIndentChange {
6760
+ readonly id: string;
6761
+ readonly delta: 1 | -1;
6762
+ readonly parentId: string | null;
6763
+ }
6572
6764
  /** Payload of {@link FlyGanttComponent.dependencyDelete} — the edge the user removed. */
6573
6765
  interface GanttDependencyDelete {
6574
6766
  fromId: string;
@@ -6605,6 +6797,10 @@ interface GanttFlatRow {
6605
6797
  hasChildren: boolean;
6606
6798
  collapsed: boolean;
6607
6799
  }
6800
+ interface ResolvedDates {
6801
+ start: Date | null;
6802
+ end: Date | null;
6803
+ }
6608
6804
  /** Render shape a row resolves to — decides which geometry fields on its VM are meaningful. */
6609
6805
  type RowShape = 'bar' | 'milestone' | 'group' | 'empty';
6610
6806
  /** Which end of a bar a link gesture grabbed / was dropped on. */
@@ -6650,7 +6846,7 @@ interface GanttLinkGesture {
6650
6846
  * `gantt-label-pane.ts`: a few pure functions plus one signal-based class composed by the host).
6651
6847
  * The functions below remain dependency-free — no Angular, no DOM — for the same reason
6652
6848
  * `gantt-scale.ts`'s are: jsdom lays nothing out, so pure functions are the only directly
6653
- * testable form of this math (skill: design-system-gantt.md §8).
6849
+ * testable form of this math (skill: ds-gantt.md §8).
6654
6850
  *
6655
6851
  * ## The `viewBox`-pan trick this math drives
6656
6852
  * The body `<svg>` stays `position: absolute`, sized to only the rendered window
@@ -6718,17 +6914,120 @@ interface GanttRowVm {
6718
6914
  /** Baseline overlay geometry, or `null` when the row has none (or baselines are hidden). */
6719
6915
  baseline: GanttBaselineVm | null;
6720
6916
  }
6721
- interface LinkVm {
6917
+ /**
6918
+ * What the geometry needs from its host, as signals rather than a component reference — the same
6919
+ * one-way shape {@link GanttLabelPane} and {@link GanttLinkGestures} take.
6920
+ */
6921
+ interface GanttRowGeometryDeps {
6922
+ readonly rowHeight: Signal<number>;
6923
+ readonly rtl: Signal<boolean>;
6924
+ readonly domain: Signal<GanttDomain>;
6925
+ readonly pxPerDay: Signal<number>;
6926
+ readonly innerWidth: Signal<number>;
6927
+ readonly showBaselines: Signal<boolean>;
6928
+ readonly readonly: Signal<boolean>;
6929
+ /** Every row's effective `{start,end}` (`resolveRowDates`), including derived group spans. */
6930
+ readonly resolvedDates: Signal<ReadonlyMap<string, ResolvedDates>>;
6931
+ /** The in-flight bar drag, folded into the geometry so the bar follows the pointer. */
6932
+ readonly dragPreview: Signal<{
6933
+ id: string;
6934
+ start: Date;
6935
+ end: Date;
6936
+ } | null>;
6937
+ /** The row-band tint, already faded to its low alpha (or `null`). */
6938
+ readonly tintFor: (row: GanttRow) => string | null;
6939
+ /** The localized accessible label for a row. */
6940
+ readonly ariaFor: (row: GanttRow, start: Date | null, end: Date | null, shape: RowShape) => string;
6941
+ }
6942
+ /**
6943
+ * Render-space geometry for a Gantt row: the view-model one flattened row resolves to, and every
6944
+ * shape derived from it.
6945
+ *
6946
+ * ## Why this is not in the component
6947
+ * `fly-gantt` composes its gestures out of one-concern classes already ({@link GanttLabelPane},
6948
+ * {@link GanttLinkGestures}, {@link GanttViewport}, {@link GanttPan}) and this is the matching
6949
+ * carve on the RENDER side: everything here is "given a row and the current scale, where is it and
6950
+ * what shape is it", with no knowledge of selection, collapse, windowing, events or i18n beyond the
6951
+ * one label callback. Keeping it here means the component's remaining bulk is state and wiring, and
6952
+ * the geometry — the part that is actually hard to read — sits beside the scale math it calls.
6953
+ *
6954
+ * ## RTL
6955
+ * Nothing here branches on direction except through `mapX`/`projectBar` and the two `sign` terms
6956
+ * below, because the whole coordinate space is pre-mirrored (see `gantt-scale.ts`). The
6957
+ * consequence worth remembering: under RTL a row's `startX` is GREATER than its `endX`.
6958
+ */
6959
+ declare class GanttRowGeometry {
6960
+ private readonly deps;
6961
+ constructor(deps: GanttRowGeometryDeps);
6962
+ /** Bar height for the current row height — the band minus its vertical padding. */
6963
+ readonly barHeight: Signal<number>;
6964
+ /** Full geometry for one row — used for rendered rows AND for link endpoints outside the window. */
6965
+ forFlat(flat: GanttFlatRow, index: number): GanttRowVm;
6966
+ barTop(vm: GanttRowVm): number;
6967
+ /** Baseline underbar's top edge — a thin strip directly beneath the main bar. */
6968
+ baselineBarTop(vm: GanttRowVm): number;
6969
+ private milestoneR;
6970
+ /** Diamond polygon points for a milestone marker. */
6971
+ diamondPoints(vm: GanttRowVm): string;
6972
+ /** Hollow outline diamond for a milestone row's baseline date (its own x, same row's y). */
6973
+ baselineDiamondPoints(vm: GanttRowVm): string;
6974
+ /**
6975
+ * Link connector x for a milestone. A diamond has no width, so the connector is nudged clear
6976
+ * of the marker in the forward-in-time direction (which flips under RTL).
6977
+ */
6978
+ milestoneConnectorX(vm: GanttRowVm): number;
6979
+ /**
6980
+ * Connector x for one END of a bar, pushed into the gutter BESIDE the bar rather than sitting on
6981
+ * its edge (see {@link CONNECTOR_GAP}). Forward-in-time is `+x` LTR and `-x` RTL, so the start
6982
+ * connector always lands before the bar and the finish connector after it, in reading order.
6983
+ */
6984
+ connectorX(vm: GanttRowVm, anchor: 'start' | 'finish'): number;
6985
+ /** Summary-bracket path (thin bar + down-turned end caps) for a group row. */
6986
+ groupPath(vm: GanttRowVm): string;
6987
+ /** Inline-start label indent for a tree depth. */
6988
+ indentFor(depth: number): number;
6989
+ }
6990
+
6991
+ /** Which part of a bar the press grabbed. */
6992
+ type GanttBarDragMode = 'move' | 'resize-start' | 'resize-end';
6993
+ /** The follow-the-cursor date readout shown while a drag runs. */
6994
+ interface GanttDragTooltip {
6995
+ readonly x: number;
6996
+ readonly y: number;
6997
+ readonly text: string;
6998
+ }
6999
+
7000
+ /**
7001
+ * Which end of each row a relationship touches.
7002
+ *
7003
+ * The two letters ARE the anchors — `FS` is "from my **F**inish to your **S**tart" — so this is
7004
+ * the exact inverse of `linkTypeFor`, and stating it as a function rather than re-reading the
7005
+ * letters at each call site is what keeps the arrowhead, the re-target handle and the type
7006
+ * derivation from ever disagreeing about which end an arrow lands on.
7007
+ */
7008
+ declare function linkAnchorsFor(type: GanttDependencyType): {
7009
+ from: GanttLinkAnchor;
7010
+ to: GanttLinkAnchor;
7011
+ };
7012
+ /** A dependency arrow, ready to draw. Render space throughout. */
7013
+ interface GanttLinkVm {
6722
7014
  key: string;
6723
7015
  fromId: string;
6724
7016
  toId: string;
6725
7017
  type: GanttDependencyType;
6726
7018
  path: string;
6727
- /** Anchor for the delete affordance shown while the link is selected (render space). */
7019
+ /** Anchor for the delete affordance shown while the link is selected. */
6728
7020
  badgeX: number;
6729
7021
  badgeY: number;
7022
+ /** The arrowhead end — where the re-target handle sits while the link is selected. */
7023
+ headX: number;
7024
+ headY: number;
7025
+ /** The end of the SOURCE row this arrow leaves from, which a re-target drag has to start on. */
7026
+ fromAnchor: GanttLinkAnchor;
6730
7027
  ariaLabel: string;
6731
7028
  }
7029
+
7030
+ /** One dependency arrow, routed and ready for the template. */
6732
7031
  /**
6733
7032
  * **`fly-gantt`** — the design-system SVG Gantt chart.
6734
7033
  *
@@ -6762,9 +7061,21 @@ interface LinkVm {
6762
7061
  declare class FlyGanttComponent {
6763
7062
  readonly i18n: I18nService;
6764
7063
  private readonly destroyRef;
7064
+ /**
7065
+ * App-level presentation defaults, when the consuming app provided any. Optional by
7066
+ * construction: a chart with no provider behaves exactly as it did before the token existed.
7067
+ */
7068
+ private readonly appDefaults;
6765
7069
  readonly rows: _angular_core.InputSignal<readonly GanttRow[]>;
6766
7070
  readonly dependencies: _angular_core.InputSignal<readonly GanttDependency[]>;
6767
- readonly zoom: _angular_core.InputSignal<GanttZoom>;
7071
+ /**
7072
+ * Time-grid resolution. `null` defers to {@link FlyGanttDefaults.zoom}, then to `'week'`.
7073
+ *
7074
+ * Widened from `GanttZoom` to `GanttZoom | null` so the same three-step precedence
7075
+ * (input → app default → built-in) applies to every presentation input. Passing a concrete zoom
7076
+ * is unchanged.
7077
+ */
7078
+ readonly zoom: _angular_core.InputSignal<GanttZoom | null>;
6768
7079
  /** Draw the vertical "today" marker when today falls inside the domain. */
6769
7080
  readonly showToday: _angular_core.InputSignal<boolean>;
6770
7081
  /** Globally disable drag/resize/link (individual rows can also be `readonly`). */
@@ -6774,11 +7085,11 @@ declare class FlyGanttComponent {
6774
7085
  * divider from there; once they have, this input only re-seeds the pane if
6775
7086
  * {@link resetLabelWidth} is called (double-clicking the divider does exactly that).
6776
7087
  */
6777
- readonly labelWidth: _angular_core.InputSignal<number>;
7088
+ readonly labelWidth: _angular_core.InputSignal<number | null>;
6778
7089
  /** Let the user drag the divider between the label pane and the time grid. */
6779
7090
  readonly resizableLabels: _angular_core.InputSignal<boolean>;
6780
- /** Row band height in px. */
6781
- readonly rowHeight: _angular_core.InputSignal<number>;
7091
+ /** Row band height in px. `null` defers to {@link FlyGanttDefaults.rowHeight}, then to `34`. */
7092
+ readonly rowHeight: _angular_core.InputSignal<number | null>;
6782
7093
  /** Hard cap on the flattened row list; beyond it the list is truncated (see class doc).
6783
7094
  * `0` (the default) is uncapped — windowing (below) is what keeps an uncapped chart cheap. */
6784
7095
  readonly maxRows: _angular_core.InputSignal<number>;
@@ -6793,6 +7104,31 @@ declare class FlyGanttComponent {
6793
7104
  readonly virtualized: _angular_core.InputSignal<boolean>;
6794
7105
  /** Render the baseline underbar/diamond for rows carrying `baselineStart`/`baselineEnd`. */
6795
7106
  readonly showBaselines: _angular_core.InputSignal<boolean>;
7107
+ /**
7108
+ * Render a search field in the pinned corner that filters the label tree.
7109
+ *
7110
+ * Filtering preserves tree CONTEXT rather than flattening to a hit list — see
7111
+ * `gantt-search.ts` for why a plain `rows.filter()` silently re-parents matching children and
7112
+ * de-groups matching parents. A live query also expands every group (a match hidden inside a
7113
+ * collapsed summary is indistinguishable from no match at all) and suspends drag-reorder, since
7114
+ * a filtered sibling list is not a complete one to reorder against.
7115
+ *
7116
+ * `null` defers to {@link FlyGanttDefaults.searchable}, then to `false`.
7117
+ */
7118
+ readonly searchable: _angular_core.InputSignal<boolean | null>;
7119
+ /**
7120
+ * Let the user drag label rows to reorder / re-home them, emitting {@link rowReorder}.
7121
+ *
7122
+ * `null` defers to {@link FlyGanttDefaults.reorderable}, then to `false` — the chart never
7123
+ * offers a gesture whose result nobody is persisting.
7124
+ */
7125
+ readonly reorderable: _angular_core.InputSignal<boolean | null>;
7126
+ /**
7127
+ * The label-tree filter text. Two-way bindable, so a consumer can seed it from a deep link or
7128
+ * clear it on navigation; the built-in field writes it as the user types (debounced by
7129
+ * `fly-search-input`). Ignored unless {@link searchable}.
7130
+ */
7131
+ readonly searchQuery: _angular_core.ModelSignal<string>;
6796
7132
  /** Fires on a committed bar move / resize with the new ISO `start`/`end`. */
6797
7133
  readonly rowDatesChange: _angular_core.OutputEmitterRef<GanttRowDatesChange>;
6798
7134
  /**
@@ -6812,32 +7148,72 @@ declare class FlyGanttComponent {
6812
7148
  readonly rowExpand: _angular_core.OutputEmitterRef<string>;
6813
7149
  /** The component coarsened the requested zoom to stay under `maxCanvasPx`. */
6814
7150
  readonly effectiveZoomChange: _angular_core.OutputEmitterRef<GanttZoom>;
7151
+ /**
7152
+ * A label row was dragged to a new place. Carries the complete new sibling order, so a consumer
7153
+ * persists it with one ordered-set write. See {@link GanttRowReorder}.
7154
+ */
7155
+ readonly rowReorder: _angular_core.OutputEmitterRef<GanttRowReorder>;
7156
+ /**
7157
+ * A row's outline level was nudged by {@link indentRow} / {@link outdentRow}, with the resulting
7158
+ * parent already resolved from the row's neighbours. The chart does not re-render on its own —
7159
+ * like every other gesture here, the consumer persists and feeds new `rows` back in.
7160
+ */
7161
+ readonly rowIndentChange: _angular_core.OutputEmitterRef<GanttRowIndentChange>;
6815
7162
  private readonly _uid;
6816
7163
  readonly markerId: string;
6817
7164
  readonly HEADER_H: number;
6818
7165
  readonly HEADER_UPPER_H = 22;
6819
7166
  readonly HEADER_LOWER_H = 22;
6820
7167
  readonly HANDLE_W = 8;
7168
+ /** Connector radius — small, and parked outside the bar, so it never steals the resize
7169
+ * handle's press (`gantt-row-vm.ts`'s CONNECTOR_R note). */
7170
+ readonly CONNECTOR_R = 3;
6821
7171
  readonly MIN_LABEL_W = 140;
6822
7172
  readonly MAX_LABEL_W = 720;
6823
7173
  readonly BASELINE_BAR_H: number;
6824
7174
  readonly selectedId: _angular_core.WritableSignal<string | null>;
6825
7175
  /** Uncontrolled collapse state — the source of truth whenever the `collapsedIds` input is `null`. */
6826
7176
  private readonly _uncontrolledCollapsed;
6827
- /** Live drag preview `{id,start,end}` folded into geometry while a gesture runs. */
7177
+ /** Live drag preview `{id,start,end}` folded into geometry while a bar gesture runs, and the
7178
+ * cursor-following date readout beside it — both owned by {@link GanttBarDrag}, re-exposed here
7179
+ * because the row geometry reads the first and the template renders the second. */
6828
7180
  private readonly _dragPreview;
6829
- readonly dragTooltip: _angular_core.WritableSignal<{
6830
- x: number;
6831
- y: number;
6832
- text: string;
6833
- } | null>;
7181
+ readonly dragTooltip: _angular_core.Signal<GanttDragTooltip | null>;
6834
7182
  private readonly bodyRef;
6835
7183
  private readonly scrollElRef;
6836
7184
  private readonly canvasElRef;
6837
7185
  private readonly labelsElRef;
6838
7186
  readonly rtl: _angular_core.Signal<boolean>;
7187
+ /** The zoom the consumer asked for, before `maxCanvasPx` coarsening ({@link effectiveZoom}). */
7188
+ readonly requestedZoom: _angular_core.Signal<GanttZoom>;
7189
+ /** Row band height actually rendered. */
7190
+ readonly effectiveRowHeight: _angular_core.Signal<number>;
7191
+ /** The width the label pane falls back to until the user drags the divider. */
7192
+ private readonly _labelWidthInput;
7193
+ /** Whether the corner search field is rendered at all. */
7194
+ readonly searchEnabled: _angular_core.Signal<boolean>;
7195
+ /** The live filter text, or `''` when searching is off — the one signal every filter path reads. */
7196
+ readonly activeQuery: _angular_core.Signal<string>;
7197
+ /**
7198
+ * Whether a label row can be dragged right now.
7199
+ *
7200
+ * Suspended while a search is live, deliberately: {@link rowReorder} emits the COMPLETE sibling
7201
+ * order, and a filtered tree only holds the siblings that matched. Persisting that list would
7202
+ * quietly drop every row the filter is hiding — the sort of data loss whose cause is invisible
7203
+ * afterwards. A read-only chart never reorders either.
7204
+ */
7205
+ readonly reorderEnabled: _angular_core.Signal<boolean>;
6839
7206
  /** Effective collapse set: the controlled input when the consumer supplied one, else internal state. */
6840
7207
  private readonly _collapsed;
7208
+ /**
7209
+ * The consumer's rows narrowed by the live search filter (identity-stable when not searching).
7210
+ *
7211
+ * NOTE the domain and the resolved dates below deliberately stay on the UNFILTERED `rows()`: a
7212
+ * timeline whose axis and whose summary spans jumped on every keystroke would make the filter
7213
+ * feel like it was editing the plan. Filtering decides which rows are LISTED, not what the dates
7214
+ * are.
7215
+ */
7216
+ readonly filteredRows: _angular_core.Signal<readonly GanttRow[]>;
6841
7217
  /** Full visible list after tree flatten/collapse (no `maxRows` cap applied yet). */
6842
7218
  private readonly _flat;
6843
7219
  /** `_flat`, capped at `maxRows` when it is positive — unchanged from the pre-A9 contract. */
@@ -6877,31 +7253,28 @@ declare class FlyGanttComponent {
6877
7253
  readonly rowVms: _angular_core.Signal<GanttRowVm[]>;
6878
7254
  /** Only the rows carrying a tint, so the band pass does not emit an empty rect per row. */
6879
7255
  readonly tintedRows: _angular_core.Signal<GanttRowVm[]>;
6880
- /** Dependency arrows whose y-span intersects the row window — O(E), not O(N). Endpoints are
6881
- * resolved via {@link _vmForFlat} directly (not off `rowVms()`) since an edge can cross the
6882
- * window with one endpoint outside it the memo §4.3 rule-2 fix for the pre-A9 "both
6883
- * endpoints rendered" predicate, which would erase exactly those crossing arrows. */
6884
- readonly linkVms: _angular_core.Signal<LinkVm[]>;
6885
- /** Full geometry for one row — shared by `rowVms` and `linkVms` (endpoints outside the window). */
6886
- private _vmForFlat;
7256
+ /**
7257
+ * Render-space geometry, in {@link GanttRowGeometry} the row view-model plus every shape
7258
+ * derived from it. Composed for the same reason as the gestures below it: given a row and the
7259
+ * current scale it answers "where is this and what shape is it", and knows nothing about
7260
+ * selection, collapse, windowing or events. `geo` is public because the TEMPLATE calls it.
7261
+ */
7262
+ readonly geo: GanttRowGeometry;
7263
+ /** Bar height for the current row height — bound directly by the template. */
6887
7264
  readonly barHeight: _angular_core.Signal<number>;
6888
- barTop(vm: GanttRowVm): number;
6889
- /** Baseline underbar's top edge — a thin strip directly beneath the main bar. */
6890
- baselineBarTop(vm: GanttRowVm): number;
6891
- private milestoneR;
6892
- /** Diamond polygon points for a milestone marker. */
6893
- diamondPoints(vm: GanttRowVm): string;
6894
- /** Hollow outline diamond for a milestone row's baseline date (its own x, same row's y). */
6895
- baselineDiamondPoints(vm: GanttRowVm): string;
6896
7265
  /**
6897
- * Link connector x for a milestone. A diamond has no width, so the connector is nudged clear
6898
- * of the marker in the forward-in-time direction (which flips under RTL).
7266
+ * Dependency arrows, in {@link GanttLinkVms} the drawing half of the link surface, paired with
7267
+ * the dragging half in `gantt-link-gestures.ts`. Declared after {@link geo} because it is handed
7268
+ * that instance: both halves of a link resolve through the SAME row geometry the rows draw from,
7269
+ * so an arrow can never land somewhere its own endpoint is not.
7270
+ *
7271
+ * Only arrows whose y-span intersects the row window are built — O(E), not O(N) — and the
7272
+ * predicate is CROSSES, not "both endpoints rendered": an edge with one end scrolled out of the
7273
+ * window still has to be drawn through it (memo §4.3 rule 2).
6899
7274
  */
6900
- milestoneConnectorX(vm: GanttRowVm): number;
6901
- /** Summary-bracket path (thin bar + down-turned end caps) for a group row. */
6902
- groupPath(vm: GanttRowVm): string;
6903
- /** Inline-start label indent for a tree depth. */
6904
- indentFor(depth: number): number;
7275
+ private readonly _linkVms;
7276
+ /** The arrows the template draws. */
7277
+ readonly linkVms: _angular_core.Signal<GanttLinkVm[]>;
6905
7278
  /**
6906
7279
  * The divider's own state machine, in {@link GanttLabelPane}. It is composed rather than
6907
7280
  * inlined because it is the one region of this component with no coupling to the chart: give it
@@ -6936,12 +7309,42 @@ declare class FlyGanttComponent {
6936
7309
  onLabelRowKeydown(ev: Event, id: string): void;
6937
7310
  onRowClick(id: string): void;
6938
7311
  onRowDblClick(id: string): void;
6939
- onLinkClick(ev: Event, vm: LinkVm): void;
6940
- deleteLink(ev: Event, vm: LinkVm): void;
6941
- onGridKeydown(ev: KeyboardEvent): void;
6942
- /** Shift both dates of the selected bar by `deltaDays` and emit (respecting read-only). */
6943
- private _nudgeSelected;
6944
- onBarPointerDown(ev: PointerEvent, vm: GanttRowVm, mode: 'move' | 'resize-start' | 'resize-end'): void;
7312
+ /**
7313
+ * Press on a row's own SHAPE — the group bracket today; bars and milestones reach the same place
7314
+ * through {@link onBarPointerDown}, which additionally starts a drag.
7315
+ *
7316
+ * `stopPropagation` is what keeps this apart from the background pan: the full-width lane rect
7317
+ * behind every row is deliberately NOT a selection target any more (clicking the empty part of a
7318
+ * row is a pan, not a pick), so only a press that lands on a painted shape may select.
7319
+ */
7320
+ onShapePointerDown(ev: PointerEvent, id: string): void;
7321
+ /** Settled (debounced) text from the corner search field. */
7322
+ onSearchQueryChange(query: string): void;
7323
+ onLinkClick(ev: Event, vm: GanttLinkVm): void;
7324
+ /**
7325
+ * Claim the press on a dependency arrow before the canvas can read it as the start of a pan.
7326
+ *
7327
+ * An arrow is a CONTROL sitting on the pan surface, and it selects on `click` — but the pan
7328
+ * gesture swallows the trailing click of any press that travelled more than
7329
+ * `PAN_THRESHOLD_PX`, so on a 1.5px line a perfectly ordinary click (a few pixels of hand
7330
+ * movement between press and release) selected nothing and nudged the chart sideways instead.
7331
+ * The bar drag and the connector drag were never affected because both already stop the press
7332
+ * here; the arrow was the one interactive thing on the canvas that did not.
7333
+ */
7334
+ onLinkPathPointerDown(ev: PointerEvent): void;
7335
+ /**
7336
+ * Grab the head of the selected arrow and re-point it. Resolves the arrow's SOURCE row, because
7337
+ * that is where the gesture has to originate for the drop to derive a relationship type.
7338
+ */
7339
+ onLinkHeadPointerDown(ev: PointerEvent, vm: GanttLinkVm): void;
7340
+ deleteLink(ev: Event, vm: GanttLinkVm): void;
7341
+ /**
7342
+ * Moving and resizing a bar, in {@link GanttBarDrag} — the last of the three pointer gestures
7343
+ * to be composed rather than inlined, for the same reason as the other two: it needs a scale, a
7344
+ * direction and somewhere to report a committed pair of dates, and nothing else about the chart.
7345
+ */
7346
+ private readonly _barDrag;
7347
+ onBarPointerDown(ev: PointerEvent, vm: GanttRowVm, mode: GanttBarDragMode): void;
6945
7348
  /**
6946
7349
  * The dependency-drawing gesture, in {@link GanttLinkGestures}. Composed rather than inlined
6947
7350
  * because it is a self-contained second gesture whose interesting part is a RULE — the end you
@@ -6955,1584 +7358,1839 @@ declare class FlyGanttComponent {
6955
7358
  readonly linkGesture: _angular_core.WritableSignal<GanttLinkGesture | null>;
6956
7359
  /** Rubber-band path for an in-flight link gesture (render space). */
6957
7360
  readonly linkGesturePath: _angular_core.Signal<string | null>;
6958
- onLinkPointerDown(ev: PointerEvent, vm: GanttRowVm, anchor: GanttLinkAnchor): void;
6959
7361
  /**
6960
- * Resolve a row's `backgroundColor` into the low-alpha band colour actually painted.
6961
- * `color-mix` does the fade in the consumer's own colour space, so a token, a hex, an
6962
- * `rgb()` or an `oklch()` all work and none of them need pre-computing per theme.
7362
+ * The chart's keyboard map, in {@link GanttKeyboard} — composed for the same reason as the three
7363
+ * pointer gestures below it. Declared after the link surface because it is handed both halves:
7364
+ * Delete belongs to a selected ARROW before it belongs to a selected row.
6963
7365
  */
6964
- private _tintFor;
7366
+ private readonly _keyboard;
7367
+ onGridKeydown(ev: KeyboardEvent): void;
7368
+ onLinkPointerDown(ev: PointerEvent, vm: GanttRowVm, anchor: GanttLinkAnchor): void;
7369
+ /**
7370
+ * Ctrl+wheel scrolls horizontally and a press-drag on empty chart space pans, both in
7371
+ * {@link GanttPan}. Composed for the same reason as the other two gestures: it needs a scrolling
7372
+ * element and nothing else about the chart, and its two subtleties (a non-passive wheel listener
7373
+ * is the only kind that can out-vote browser zoom; a pan must swallow its own trailing click)
7374
+ * read better beside each other than scattered through this class.
7375
+ */
7376
+ private readonly _pan;
7377
+ /** True mid drag-pan — drives the host's grabbing cursor. */
7378
+ readonly panning: _angular_core.WritableSignal<boolean>;
7379
+ /**
7380
+ * Press on the chart background. Every interactive shape stops propagation on its own
7381
+ * `pointerdown`, so only genuinely empty space (the lane rects, the weekend bands, the
7382
+ * gridlines, the canvas itself) ever reaches this.
7383
+ */
7384
+ onPanPointerDown(ev: PointerEvent): void;
7385
+ /**
7386
+ * The flattened rows in the narrow shape the reorder resolver needs.
7387
+ *
7388
+ * `parentId` is recomputed from the rendered DEPTHS rather than copied off `GanttRow.parentId`,
7389
+ * and the difference is load-bearing: `flattenRows` treats an unknown, self- or cyclic parent as
7390
+ * a root, so a row can render at depth 0 while still carrying a `parentId` string. Trusting the
7391
+ * field would have the resolver build a sibling list for a parent that is not on screen.
7392
+ */
7393
+ private readonly _reorderRows;
7394
+ private readonly _reorder;
7395
+ /** Id of the row being dragged, or `null`. */
7396
+ readonly reorderDraggingId: _angular_core.WritableSignal<string | null>;
7397
+ /** Absolute row-space `y` of the drop indicator, or `null` when no drag is live. */
7398
+ readonly reorderIndicatorTop: _angular_core.Signal<number | null>;
7399
+ onLabelRowPointerDown(ev: PointerEvent, id: string): void;
7400
+ /** Whether `id` can become a child of the row above it (see `canIndentGanttRow`). */
7401
+ canIndentRow(id: string): boolean;
7402
+ /** Whether `id` can be lifted out of its parent — i.e. whether it has one. */
7403
+ canOutdentRow(id: string): boolean;
7404
+ /** Reactive twins for the currently selected row — what a magic-bar publisher binds to. */
7405
+ readonly canIndentSelected: _angular_core.Signal<boolean>;
7406
+ readonly canOutdentSelected: _angular_core.Signal<boolean>;
7407
+ /** Make `id` a child of the row directly above it, if it has an eligible sibling there. */
7408
+ indentRow(id: string): void;
7409
+ /** Lift `id` out to its grandparent's level (`null` = the root). */
7410
+ outdentRow(id: string): void;
6965
7411
  private _mapTicks;
7412
+ /** Bridges the pure a11y label builder to this component's localizer + Date-typed geometry. */
6966
7413
  private _ariaFor;
6967
7414
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyGanttComponent, never>;
6968
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyGanttComponent, "fly-gantt", never, { "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "dependencies": { "alias": "dependencies"; "required": false; "isSignal": true; }; "zoom": { "alias": "zoom"; "required": false; "isSignal": true; }; "showToday": { "alias": "showToday"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "resizableLabels": { "alias": "resizableLabels"; "required": false; "isSignal": true; }; "rowHeight": { "alias": "rowHeight"; "required": false; "isSignal": true; }; "maxRows": { "alias": "maxRows"; "required": false; "isSignal": true; }; "collapsedIds": { "alias": "collapsedIds"; "required": false; "isSignal": true; }; "overscanRows": { "alias": "overscanRows"; "required": false; "isSignal": true; }; "maxCanvasPx": { "alias": "maxCanvasPx"; "required": false; "isSignal": true; }; "virtualized": { "alias": "virtualized"; "required": false; "isSignal": true; }; "showBaselines": { "alias": "showBaselines"; "required": false; "isSignal": true; }; }, { "rowDatesChange": "rowDatesChange"; "dependencyCreate": "dependencyCreate"; "dependencyDelete": "dependencyDelete"; "rowClick": "rowClick"; "rowDblClick": "rowDblClick"; "labelWidthChange": "labelWidthChange"; "collapsedIdsChange": "collapsedIdsChange"; "rowExpand": "rowExpand"; "effectiveZoomChange": "effectiveZoomChange"; }, never, never, true, never>;
7415
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyGanttComponent, "fly-gantt", never, { "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "dependencies": { "alias": "dependencies"; "required": false; "isSignal": true; }; "zoom": { "alias": "zoom"; "required": false; "isSignal": true; }; "showToday": { "alias": "showToday"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "resizableLabels": { "alias": "resizableLabels"; "required": false; "isSignal": true; }; "rowHeight": { "alias": "rowHeight"; "required": false; "isSignal": true; }; "maxRows": { "alias": "maxRows"; "required": false; "isSignal": true; }; "collapsedIds": { "alias": "collapsedIds"; "required": false; "isSignal": true; }; "overscanRows": { "alias": "overscanRows"; "required": false; "isSignal": true; }; "maxCanvasPx": { "alias": "maxCanvasPx"; "required": false; "isSignal": true; }; "virtualized": { "alias": "virtualized"; "required": false; "isSignal": true; }; "showBaselines": { "alias": "showBaselines"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "reorderable": { "alias": "reorderable"; "required": false; "isSignal": true; }; "searchQuery": { "alias": "searchQuery"; "required": false; "isSignal": true; }; }, { "searchQuery": "searchQueryChange"; "rowDatesChange": "rowDatesChange"; "dependencyCreate": "dependencyCreate"; "dependencyDelete": "dependencyDelete"; "rowClick": "rowClick"; "rowDblClick": "rowDblClick"; "labelWidthChange": "labelWidthChange"; "collapsedIdsChange": "collapsedIdsChange"; "rowExpand": "rowExpand"; "effectiveZoomChange": "effectiveZoomChange"; "rowReorder": "rowReorder"; "rowIndentChange": "rowIndentChange"; }, never, never, true, never>;
6969
7416
  }
6970
7417
 
6971
- /** The 14 rendered field kinds. Drives the control the renderer picks. */
6972
- type FlyFormQuestionType = 'Text' | 'MultilineText' | 'Number' | 'Email' | 'Date' | 'DateTime' | 'Boolean' | 'SingleSelect' | 'MultipleSelect' | 'RadioButton' | 'Country' | 'File' | 'MultiFile' | 'Image';
6973
- /**
6974
- * A selectable option on a choice field (SingleSelect / MultipleSelect /
6975
- * RadioButton). `linkedQuestionIds` drive conditional branching: selecting this
6976
- * option reveals those (branch-target) fields. `score` is author-only and is only
6977
- * surfaced to the viewer when the host opts in via `[showScore]` (a scorecard);
6978
- * a respondent-facing form leaves `showScore` false and never renders it.
6979
- */
6980
- interface FlyFormOption {
6981
- id: string;
6982
- label: string;
6983
- score?: number;
6984
- linkedQuestionIds?: string[];
6985
- }
6986
- /** Per-type constraints/affordances an author can attach to a field. */
6987
- interface FlyFormFieldOptions {
6988
- minLength?: number;
6989
- maxLength?: number;
6990
- numericMin?: number;
6991
- numericMax?: number;
6992
- rows?: number;
6993
- maxFileSizeMb?: number;
6994
- maxFileCount?: number;
6995
- allowedExtensions?: string[];
6996
- hasSearch?: boolean;
6997
- }
6998
7418
  /**
6999
- * A single form field. `isBranchTarget` fields start hidden. `sectionId` /
7000
- * `criteriaGroup` are additive grouping hints a scorecard uses; they are ignored
7001
- * by the respondent-facing survey path, so binding them changes nothing there.
7419
+ * App-level defaults for every {@link FlyGanttComponent} under a given injector.
7420
+ *
7421
+ * ## Why a token and not "just use the input"
7422
+ * Every field here is already a per-instance `input()`, and for a one-off chart that is the right
7423
+ * knob. But a planning app draws the SAME chart on four or five screens (a project plan, a
7424
+ * schedule, a portfolio roadmap, a programme rollup), and the presentation decisions — how wide the
7425
+ * label pane starts, how tall a row band is, whether the label pane is searchable — are properties
7426
+ * of the APP, not of any one screen. Repeating them at every call site is how the fifth screen ends
7427
+ * up with a 240px pane while the other four use 360, with no single place that says which is right.
7428
+ *
7429
+ * So: provide once at the app (or a feature-area) injector, override per instance where a screen
7430
+ * genuinely differs. An explicit input ALWAYS wins over the token, and the token over the built-in
7431
+ * default — the plain three-step precedence, stated once in `FlyGanttComponent`'s resolvers.
7432
+ *
7433
+ * ```ts
7434
+ * // app.config.ts
7435
+ * providers: [provideFlyGanttDefaults({ labelWidth: 360, searchable: true })]
7436
+ * ```
7437
+ *
7438
+ * Every field is optional: a partial object overrides only what it names, so an app can move the
7439
+ * label width without also inheriting an opinion about row height.
7002
7440
  */
7003
- interface FlyFormQuestion {
7004
- id: string;
7005
- text: string;
7006
- description?: string;
7007
- type: FlyFormQuestionType;
7008
- required: boolean;
7009
- isBranchTarget: boolean;
7010
- /** Additive: an author-hidden field. `false` excludes it from the visible set (and thus from
7011
- * required-enforcement); omitted / `true` renders as normal. Mirrors the backend `IsVisible`. */
7012
- isVisible?: boolean;
7013
- fieldOptions?: FlyFormFieldOptions;
7014
- options: FlyFormOption[];
7015
- /** Additive: groups fields into a visual section (scorecard). Ignored by surveys. */
7016
- sectionId?: string;
7017
- /** Additive: labels the weighted-criteria group a scored field belongs to. Ignored by surveys. */
7018
- criteriaGroup?: string;
7019
- }
7020
- /** A full form definition — the top-level `[definition]` binding. */
7021
- interface FlyFormDefinition {
7022
- title: string;
7023
- description?: string;
7024
- declaration?: string;
7025
- questions: FlyFormQuestion[];
7441
+ interface FlyGanttDefaults {
7442
+ /**
7443
+ * Initial inline-start label-pane width in px, before the user drags the divider. Clamped into
7444
+ * `[GANTT_LABEL_W_MIN, GANTT_LABEL_W_MAX]` like every other width source.
7445
+ */
7446
+ readonly labelWidth?: number;
7447
+ /** Row band height in px. */
7448
+ readonly rowHeight?: number;
7449
+ /** Time-grid resolution a chart opens at. */
7450
+ readonly zoom?: GanttZoom;
7451
+ /** Render the label-pane search field (see `FlyGanttComponent.searchable`). */
7452
+ readonly searchable?: boolean;
7453
+ /** Allow dragging label rows to reorder them (see `FlyGanttComponent.reorderable`). */
7454
+ readonly reorderable?: boolean;
7026
7455
  }
7027
- /** How an opted-in `[showScore]` host renders a choice option's points. */
7028
- type FlyFormScoreDisplay = 'inline' | 'badge';
7029
7456
  /**
7030
- * The polymorphic answer payload for one field. Only the field(s) relevant to the
7031
- * type are populated:
7032
- * - text: Text / MultilineText / Email
7033
- * - number: Number
7034
- * - dateValue: Date / DateTime (raw `<input>` value string)
7035
- * - boolValue: Boolean
7036
- * - selectedOptionIds: SingleSelect / MultipleSelect / RadioButton / Country
7037
- * - fileIds: File / MultiFile / Image (filled by the host after upload; this
7038
- * component emits it empty and surfaces the raw selection via `fileSelected`)
7457
+ * DI token carrying {@link FlyGanttDefaults}. Optional everywhere a chart with no provider falls
7458
+ * back to the component's own built-in defaults, which is what every pre-existing consumer gets.
7039
7459
  */
7040
- interface FlyFormAnswerValue {
7041
- text?: string;
7042
- number?: number;
7043
- dateValue?: string;
7044
- boolValue?: boolean;
7045
- selectedOptionIds?: string[];
7046
- fileIds?: string[];
7047
- }
7048
- /** One field's answer — the unit emitted by `answersChange` / `submit`. */
7049
- interface FlyFormAnswer {
7050
- questionId: string;
7051
- value: FlyFormAnswerValue;
7052
- }
7460
+ declare const FLY_GANTT_DEFAULTS: InjectionToken<FlyGanttDefaults>;
7461
+ /** Provider helper — `providers: [provideFlyGanttDefaults({ labelWidth: 360 })]`. */
7462
+ declare function provideFlyGanttDefaults(defaults: FlyGanttDefaults): Provider;
7463
+
7464
+ /** Label-pane width bounds (px) the splitter clamps to. */
7465
+ declare const GANTT_LABEL_W_MIN = 140;
7466
+ declare const GANTT_LABEL_W_MAX = 720;
7053
7467
  /**
7054
- * Raw file selection surfaced by File / MultiFile / Image fields. The host wires
7055
- * this to its own upload API and, once ids are known, feeds them back via the
7056
- * `[answers]` prefill (`fileIds`). The DS component never touches an upload endpoint.
7468
+ * Width (px) a label pane opens at when neither the consumer's `labelWidth` input nor an app-level
7469
+ * `FlyGanttDefaults` says otherwise.
7470
+ *
7471
+ * Raised from the original 240. A Gantt label is a full work-breakdown title, routinely carrying a
7472
+ * prefix the consumer composed in (PPM puts the assignee's initials on every task row and a weight
7473
+ * suffix on every milestone), and at 240px the ellipsis landed inside the NAME for a majority of
7474
+ * real rows — which turns the pane into a list of indistinguishable truncations rather than a
7475
+ * readable outline. It remains an INITIAL value only: the divider drag, the keyboard resize and
7476
+ * `resetLabelWidth()` all behave exactly as before.
7057
7477
  */
7058
- interface FlyFileSelection {
7059
- questionId: string;
7060
- files: File[];
7061
- }
7478
+ declare const GANTT_LABEL_W_DEFAULT = 320;
7062
7479
 
7063
- /** A render segment: either an ungrouped field (`group === null`) or a titled `criteriaGroup` section.
7064
- * `index` is the field's running position across the whole form (drives unique element ids). */
7065
- interface FlyFormFieldGroup {
7066
- group: string | null;
7067
- items: {
7068
- q: FlyFormQuestion;
7069
- index: number;
7070
- }[];
7071
- }
7072
7480
  /**
7073
- * **`fly-dynamic-form`** the design-system data-contract-driven form renderer.
7074
- *
7075
- * Renders a {@link FlyFormDefinition} (14 field types) into an accessible,
7076
- * theme-aware form, owning all the fiddly behaviour a form needs: conditional
7077
- * **branching** (options reveal branch-target fields), per-visible-field
7078
- * **required validation**, a **declaration** acceptance gate, and a **readonly**
7079
- * replay mode. It is a platform surface — zero coupling to any specific app, and
7080
- * (deliberately) no coupling to an upload API: File / Image / MultiFile fields
7081
- * surface the raw selection via {@link fileSelected} and emit `fileIds` empty; the
7082
- * host wires the upload and feeds ids back through the `[answers]` prefill.
7481
+ * Magic-bar contribution contract (UX v2 "Nova", D5 §4).
7083
7482
  *
7084
- * The historical **`fly-survey-form`** selector is retained on this same component
7085
- * (multi-selector), so every existing `<fly-survey-form>` binding keeps working
7086
- * unchanged. Surveys never sets {@link showScore}, so its behaviour (never rendering
7087
- * `option.score`) is identical. A scorecard host opts into score display with
7088
- * `[showScore]="true"`.
7483
+ * The magic bar is the shell's top-bar toolbar pill. Unlike the fixed right
7484
+ * cluster (Help / Tasks / Notifications / assistant orb — shell-owned, an app can
7485
+ * never remove it), the pill's contents belong to **whatever view the user is
7486
+ * looking at**, and the design's reference implementation re-publishes the whole
7487
+ * set on every selection, tab and save (`pubTabActions`). Two consequences shape
7488
+ * every type below:
7089
7489
  *
7090
- * i18n is self-sufficient through the `form.*` keys in `DS_BASELINE_LOCALES`
7091
- * (en/ar/fr/ur), overridable by any consumer key of the same name. RTL works via
7092
- * logical CSS. Styling uses DS theme tokens (`--surface-*`, `--label-*`,
7093
- * `--separator`, `--fill-*`, `--accent`, `--system-*`) with light-neutral
7094
- * fallbacks so it stays readable even in a consumer that hasn't mapped them.
7490
+ * 1. **A contribution is a full replace, never a patch.** There is no "add one
7491
+ * action" call, because the publisher never knows what the previous view left
7492
+ * behind. That also collapses D5's `search: null` ("suppress") vs `search`
7493
+ * absent distinction under full replace both simply mean "this view offers
7494
+ * no search".
7495
+ * 2. **Render data and behaviour are separated in the type system.** Each family
7496
+ * has a `*Spec` base carrying ONLY the fields that affect what is painted;
7497
+ * the publisher-facing type extends it with callbacks, and the shell-facing
7498
+ * `*View` type extends it with bound invokers. The base IS the equality
7499
+ * surface {@link MagicBarRegistry} dedupes on — a re-publish whose specs are
7500
+ * unchanged must not disturb the shell, even though its closures are brand
7501
+ * new objects every time (an arrow function in a component method never
7502
+ * compares equal to its predecessor). See `magic-bar-projection.ts`.
7095
7503
  *
7096
- * @example
7097
- * ```html
7098
- * <fly-dynamic-form
7099
- * [definition]="def"
7100
- * [answers]="prefill"
7101
- * [showScore]="true"
7102
- * (answersChange)="draft = $event"
7103
- * (validityChange)="valid = $event"
7104
- * (fileSelected)="upload($event)"
7105
- * (submit)="persist($event)" />
7106
- * ```
7504
+ * **Strings are i18n keys, never resolved text** — same rule as
7505
+ * {@link AgentCommandRegistration} (`labelKey` / `descriptionKey` / `hintKey`),
7506
+ * for the same two reasons: a contribution outlives a locale switch (a resolved
7507
+ * label would silently go stale until the publisher happened to re-publish), and
7508
+ * keys are locale-invariant so the dedupe fingerprint doesn't churn when the user
7509
+ * changes language. Keys resolve in the OWNING app's bundle.
7510
+ *
7511
+ * **Every `*Key` has an optional `*Params` sibling** ({@link MagicBarTextParams}),
7512
+ * fed straight to `I18nService.t(key, params)`. Without it a count-carrying string
7513
+ * ("Search 1,204 tasks…") forces the publisher to pass pre-formatted TEXT as the
7514
+ * key: it renders correctly in `en`, renders identically untranslated in ar/fr/ur,
7515
+ * and no locale tool can see it. Params participate in the dedupe fingerprint, so
7516
+ * a changing count re-paints.
7517
+ *
7518
+ * Which of D5 §4's "dynamics the shell must honor" live where:
7519
+ *
7520
+ * | # | Dynamic | Owner |
7521
+ * |---|---|---|
7522
+ * | 1 | Live re-publication, idempotent replace | {@link MagicBarRegistry} (here) |
7523
+ * | 2 | Disabled ≠ hidden, auto disabled-tooltip | contract keeps `disabled` rows; `fly-magic-actions` renders + suffixes |
7524
+ * | 3 | `toolbarPopIn` 40 ms stagger | renderer — it tracks on {@link MagicBarActionSpec.id}, which is why ids are unique across the WHOLE contribution |
7525
+ * | 4 | i18n on every label/tip | contract (keys + params, above) |
7526
+ * | 5 | Fixed right cluster is not contributable | contract — no slot exists for it |
7527
+ * | 6 | Mobile renders the same contribution | contract carries no form-factor branch; `openWidth` is a desktop hint mobile ignores |
7528
+ * | 7 | `shortcut` reserved (design has no shortcut affordance yet) | contract — carried, unrendered |
7529
+ *
7530
+ * On dynamic 3 specifically: the registry's dedupe does NOT stop the stagger
7531
+ * replaying, and believing it does would produce a renderer that re-animates on
7532
+ * every click. Dedupe fires only when *nothing painted* changed, and the dominant
7533
+ * case — `disabled: !hasSelection` — changes the fingerprint on every selection.
7534
+ * What holds the DOM still is the renderer's `@for (…; track item.id)`.
7107
7535
  */
7108
- declare class FlyDynamicFormComponent implements OnInit {
7109
- private readonly _i18n;
7110
- /** The form to render. */
7111
- readonly definition: _angular_core.InputSignal<FlyFormDefinition>;
7112
- /** Replay mode every control is disabled and no validation errors / submit show.
7113
- * Aliased to `[readonly]` (the public binding); the class member avoids shadowing
7114
- * the TypeScript `readonly` keyword. */
7115
- readonly isReadonly: _angular_core.InputSignal<boolean>;
7116
- /** Hides the submit button while leaving every control interactive — the author's
7117
- * pre-publish PREVIEW, which must let you open dropdowns and try the form out but has
7118
- * nothing to submit to. Distinct from `readonly`, which disables the controls outright
7119
- * (a submission replay). A host that hides submit should not bind `(submit)`. */
7120
- readonly hideSubmit: _angular_core.InputSignal<boolean>;
7121
- /** Prefill answers (e.g. a saved draft or a completed response). Answers for
7122
- * currently-hidden branch targets are ignored. */
7123
- readonly answers: _angular_core.InputSignal<readonly FlyFormAnswer[]>;
7124
- /** Additive, opt-in: render each choice option's `score` (a weighted scorecard).
7125
- * Defaults false so the respondent-facing survey path never leaks the answer key. */
7126
- readonly showScore: _angular_core.InputSignal<boolean>;
7127
- /** How an opted-in score is rendered next to an option (`badge` chip or `inline` text). */
7128
- readonly scoreDisplay: _angular_core.InputSignal<FlyFormScoreDisplay>;
7129
- /** The full visible answer set on every change. */
7130
- readonly answersChange: _angular_core.OutputEmitterRef<FlyFormAnswer[]>;
7131
- /** Required-field validity on every change (does NOT include the declaration gate). */
7132
- readonly validityChange: _angular_core.OutputEmitterRef<boolean>;
7133
- /** Fires only when the form is valid AND (if a declaration is set) accepted. */
7134
- readonly submit: _angular_core.OutputEmitterRef<FlyFormAnswer[]>;
7135
- /** Raw file selection for a File / MultiFile / Image field — the host uploads
7136
- * and feeds the resulting ids back via `[answers]`. */
7137
- readonly fileSelected: _angular_core.OutputEmitterRef<FlyFileSelection>;
7138
- private readonly _base;
7139
- /** questionId → answer value. Only ever contains VISIBLE, non-empty answers. */
7140
- private readonly _values;
7141
- /** questionId locally-picked File[] (transient, pre-upload). Never emitted as ids. */
7142
- private readonly _fileSelections;
7143
- private readonly _declarationAccepted;
7144
- private readonly _submitAttempted;
7145
- /** questionIds the user has interacted with — gates when a required error shows. */
7146
- private readonly _interacted;
7147
- /** Per-field local file-validation error message (already localized). */
7148
- private readonly _fileErrors;
7149
- private readonly _countryOptions;
7150
- constructor();
7151
- ngOnInit(): void;
7152
- /** The set of field ids currently shown (base fields + revealed branch targets). */
7153
- readonly visibleQuestionIds: _angular_core.Signal<Set<string>>;
7154
- /** Definition fields filtered to the visible set, in definition order. */
7155
- readonly visibleQuestions: _angular_core.Signal<FlyFormQuestion[]>;
7536
+ /**
7537
+ * How an action paints. `'text'` is the labelled-pill form (calendar's "Today").
7538
+ *
7539
+ * `kind` and {@link MagicBarActionSpec.iconPath} are INDEPENDENT, and the three useful
7540
+ * combinations are all reachable:
7541
+ *
7542
+ * | `kind` | `iconPath` | Paints |
7543
+ * |---|---|---|
7544
+ * | absent / `'icon'` | set | icon only — the default, for a verb with a universally-read glyph |
7545
+ * | `'text'` | absent | label only for a value with no honest glyph ("Shortlisted", "Deferred") |
7546
+ * | `'text'` | set | **icon + label** for a verb whose glyph needs naming ("Promote to schedule") |
7547
+ *
7548
+ * Prefer icon-only for the small set of verbs a user reads without help — add, edit, delete, close,
7549
+ * save. Anything else earns its label: an unlabelled glyph the reader has to hover to identify is a
7550
+ * quiz, and the tooltip is not an answer on touch.
7551
+ */
7552
+ type MagicBarActionKind = 'icon' | 'text' | 'toggle';
7553
+ /**
7554
+ * Semantic colour of an action. `'danger'` is the red Delete treatment (red glyph
7555
+ * + red 18 % hover tint, D7); `'success'` is the green "Mark as complete" pressed
7556
+ * state (D5 §1.4 + §4 prop table: "pressed bg tint-sel / green for complete").
7557
+ *
7558
+ * A union rather than the boolean `danger` this started as: the design already
7559
+ * demands a second colour, and widening a shipped `danger?: boolean` into a union
7560
+ * later would mean REMOVING an interface member — MAJOR under `tools/ds-compat`
7561
+ * (rule 7), which forks the federation singleton for every remote.
7562
+ */
7563
+ type MagicBarTone = 'default' | 'danger' | 'success';
7564
+ /**
7565
+ * Interpolation params for one i18n key — the `params` argument of
7566
+ * `I18nService.t(key, params)`, substituting `{{name}}` placeholders.
7567
+ *
7568
+ * Values are `string | number` only: the fingerprint has to encode them, and the
7569
+ * renderer resolves them through `t()`, which stringifies. A `Date` or a nested
7570
+ * object would fingerprint as `{}` and paint as `[object Object]`.
7571
+ */
7572
+ type MagicBarTextParams = Readonly<Record<string, string | number>>;
7573
+ /**
7574
+ * Identity of a contribution's publisher, and the arbitration key the shell uses
7575
+ * to pick which contribution the bar renders.
7576
+ *
7577
+ * `windowId` is optional but strongly preferred: two windows of the same app can
7578
+ * be open on different selections, and keying on `appId` alone would let the
7579
+ * background one paint the focused one's toolbar. It is optional only because a
7580
+ * federated remote that predates per-window plumbing can still contribute
7581
+ * {@link MagicBarRegistry} resolves an `appId`-keyed slot as a fallback.
7582
+ *
7583
+ * It only has to be unique **within the publishing app** (the shell passes its own
7584
+ * `win-<appId>-<timestamp>` window id; a tab index or a route-derived id is equally
7585
+ * valid) — {@link magicBarOwnerKey} scopes it by `appId`, so a value another app
7586
+ * also uses can never cross over.
7587
+ */
7588
+ interface MagicBarOwnerRef {
7589
+ readonly appId: string;
7590
+ readonly windowId?: string;
7591
+ }
7592
+ /**
7593
+ * Storage/arbitration key for an owner. Exported because the shell computes it
7594
+ * on the janitor path (`clear` after a window closes) without holding the
7595
+ * publisher.
7596
+ *
7597
+ * **Both fields key the slot**, even though the shell's own ids
7598
+ * (`win-<appId>-<timestamp>`) are already unique. `windowId` is publisher-supplied
7599
+ * DATA that crosses the federation boundary, and the field is documented only as
7600
+ * "two windows of the same app" — nothing stops a remote passing a tab index or a
7601
+ * route-derived id. Keyed on `windowId` alone, two apps that both call their first
7602
+ * window `"1"` would share a slot: one silently paints the other's chrome, and the
7603
+ * shell's janitor `clear()` wipes the wrong one.
7604
+ */
7605
+ declare function magicBarOwnerKey(owner: MagicBarOwnerRef): string;
7606
+ /** One `menuitemradio` row. `value` is the publisher's own vocabulary. */
7607
+ interface MagicBarRadioOption {
7608
+ readonly value: string;
7156
7609
  /**
7157
- * The visible fields laid out in render order, segmented by `criteriaGroup`: fields that share a
7158
- * (non-empty) group render together inside one titled section (a scorecard's weighted-criteria
7159
- * grouping); a field with no `criteriaGroup` is its own ungrouped segment and renders flat, exactly
7160
- * as before. Each item keeps its running index across the whole form so element ids stay unique.
7610
+ * i18n key for this row. Required, and it stays required the overwhelming case is a fixed
7611
+ * vocabulary (statuses, sort orders) that MUST translate. When {@link label} is supplied this
7612
+ * is the accessible fallback used if the row's data-derived text is ever empty, so give it a
7613
+ * generic key ("Untitled") rather than inventing a per-value one.
7161
7614
  */
7162
- readonly groupedQuestions: _angular_core.Signal<FlyFormFieldGroup[]>;
7163
- readonly declarationRequired: _angular_core.Signal<boolean>;
7164
- /** Required-field validity (declaration NOT included — see {@link canSubmit}). */
7165
- readonly isValid: _angular_core.Signal<boolean>;
7166
- /** Whether `(submit)` may fire: valid required set AND declaration accepted (if any). */
7167
- readonly canSubmit: _angular_core.Signal<boolean>;
7168
- readonly declarationAccepted: _angular_core.Signal<boolean>;
7169
- fieldId(index: number): string;
7170
- descId(index: number): string;
7171
- errId(index: number): string;
7172
- describedBy(q: FlyFormQuestion, index: number): string | null;
7173
- isChoice(type: FlyFormQuestionType): boolean;
7174
- isFile(type: FlyFormQuestionType): boolean;
7175
- /** Types rendered as a native input group inside `<fieldset><legend>`. */
7176
- isFieldset(type: FlyFormQuestionType): boolean;
7177
- isCountry(type: FlyFormQuestionType): boolean;
7615
+ readonly labelKey: string;
7616
+ /** Params for {@link labelKey} — the count-badged option ("All statuses ({{count}})"). */
7617
+ readonly labelParams?: MagicBarTextParams;
7178
7618
  /**
7179
- * Types rendered with the DS `<fly-select>` (labelled via `ariaLabel`).
7619
+ * ALREADY-RESOLVED display text, for a row whose label is TENANT DATA rather than a caption —
7620
+ * a campaign name, an area of focus, an owner. When present it wins over {@link labelKey}.
7180
7621
  *
7181
- * Every select-like type now qualifies. `hasSearch` used to gate the CONTROL, which left a plain
7182
- * SingleSelect on a native `<select>` an OS-chrome popup that cannot be themed (it paints its
7183
- * rows with the control's translucent background over a white backing, so the list rendered
7184
- * white-on-white on the dark shell). `hasSearch` now only decides whether the DS panel carries a
7185
- * search box; see {@link selectSearchable}.
7622
+ * ## Why this had to exist
7623
+ * Everything else in this contract is an i18n key, deliberately (see the module header). But a
7624
+ * key can only name a string the APP ships in its locale bundles, and a tenant's campaign names
7625
+ * are rows in their database. Without this field a publisher had exactly two bad options: put
7626
+ * the name in `labelKey`, which renders correctly in `en`, renders identically untranslated in
7627
+ * ar/fr/ur, and is invisible to every locale tool; or leave the filter out of the bar entirely.
7628
+ * Thoughts' `top-trending` chose the second and kept its campaign/area filters in-page — that
7629
+ * is the gap this closes.
7630
+ *
7631
+ * ## What it does NOT license
7632
+ * Do not use it for captions. If the string is something your app authored — "All statuses",
7633
+ * "Newest first", an action name — it belongs in a locale bundle and `labelKey` is the field.
7634
+ * The test is where the string comes from: shipped with the app ⇒ `labelKey`; read from the
7635
+ * tenant's data ⇒ `label`. A resolved caption here is untranslatable text smuggled past the
7636
+ * locale tooling, which is precisely what the key-only rule exists to prevent.
7637
+ *
7638
+ * It participates in the dedupe fingerprint, so a renamed campaign repaints.
7186
7639
  */
7187
- usesDsSelect(q: FlyFormQuestion): boolean;
7188
- /** Search box: always for Country (the list is ~200 long), else only when the author asked. */
7189
- selectSearchable(q: FlyFormQuestion): boolean;
7640
+ readonly label?: string;
7641
+ }
7642
+ /** Render surface of a radio menu — the dedupe key for {@link MagicBarRadioMenu}. */
7643
+ interface MagicBarRadioMenuSpec {
7644
+ /** Eyebrow above the options ("Filter by status" / "Sort by"). */
7645
+ readonly titleKey: string;
7646
+ /** Params for {@link titleKey}. */
7647
+ readonly titleParams?: MagicBarTextParams;
7648
+ readonly options: readonly MagicBarRadioOption[];
7649
+ /** Current selection — check glyph + 600 weight on the matching row. */
7650
+ readonly value: string;
7651
+ /** The unfiltered/unsorted baseline; the trigger shows its accent dot iff `value !== defaultValue`. */
7652
+ readonly defaultValue: string;
7653
+ }
7654
+ /** What a publisher supplies for a radio menu. */
7655
+ interface MagicBarRadioMenu extends MagicBarRadioMenuSpec {
7656
+ readonly onSelect?: (value: string) => void;
7657
+ }
7658
+ /** What the shell renders. `select` always exists; it no-ops when the publisher supplied no handler. */
7659
+ interface MagicBarRadioMenuView extends MagicBarRadioMenuSpec {
7660
+ select(value: string): void;
7661
+ }
7662
+ /**
7663
+ * Render surface of an action — the dedupe key for {@link MagicBarAction}.
7664
+ *
7665
+ * `id` must be unique across the WHOLE contribution (every group, plus `primary`
7666
+ * and `overflow`), not merely within its group: it is the address the shell's
7667
+ * invoker resolves against the publisher's latest handlers.
7668
+ */
7669
+ interface MagicBarActionSpec {
7670
+ readonly id: string;
7671
+ /** i18n key — the `aria-label` and the default tooltip. */
7672
+ readonly labelKey: string;
7673
+ /** Params for {@link labelKey}, e.g. `{ count: 3 }` for "Delete {{count}} items". */
7674
+ readonly labelParams?: MagicBarTextParams;
7190
7675
  /**
7191
- * The points to show for a choice option, or null to render nothing. Non-null only when the host
7192
- * opted in via `[showScore]` and the option actually carries a score this is the sole gate that
7193
- * keeps a respondent-facing survey (which never binds `showScore`) from leaking the answer key.
7676
+ * Inner-SVG geometry for a 24×24 glyph (`stroke-width: 1.8`, round caps)
7677
+ * the design's icon transport. Reference {@link FLY_MAGIC_BAR_ICONS} by name
7678
+ * rather than hand-authoring; a publisher MAY author its own, subject to the
7679
+ * contract below.
7680
+ *
7681
+ * **This value is not trusted.** The renderer parses it against a closed
7682
+ * allowlist — self-closing `path`/`circle`/`rect`/`ellipse`/`line`/`polyline`/
7683
+ * `polygon` elements carrying geometry attributes only (`d`, `cx`, `points`,
7684
+ * …) — and binds the result as real attributes. It is never assigned as HTML.
7685
+ * Anything else (a container element, an `on*` handler, `style`, `href`,
7686
+ * `fill="url(…)"`, a stray text node) rejects the WHOLE fragment: the button
7687
+ * renders with no glyph and logs. See `magic-actions-icon.ts`.
7688
+ *
7689
+ * The type stays `string` deliberately. Narrowing it to a branded/opaque type
7690
+ * would be a ds-compat MAJOR, and it would not buy anything the renderer's own
7691
+ * validation does not already guarantee — a nominal type describes where a
7692
+ * value came from, not what it contains.
7194
7693
  */
7195
- optionScore(opt: FlyFormOption): number | null;
7196
- /** Whether a required error should be visible for a field right now. */
7197
- showError(q: FlyFormQuestion): boolean;
7198
- fileError(q: FlyFormQuestion): string | null;
7199
- textValue(q: FlyFormQuestion): string;
7200
- numberValue(q: FlyFormQuestion): number | null;
7201
- dateValue(q: FlyFormQuestion): string;
7202
- boolValue(q: FlyFormQuestion): boolean | null;
7203
- singleValue(q: FlyFormQuestion): string | null;
7204
- isSelected(q: FlyFormQuestion, optionId: string): boolean;
7205
- countryOptions(q: FlyFormQuestion): readonly FlySelectOption[];
7206
- selectOptions(q: FlyFormQuestion): readonly FlySelectOption[];
7207
- fileNames(q: FlyFormQuestion): string[];
7208
- acceptFor(q: FlyFormQuestion): string;
7209
- onText(q: FlyFormQuestion, value: string): void;
7210
- onNumber(q: FlyFormQuestion, value: string): void;
7211
- onDate(q: FlyFormQuestion, value: string): void;
7212
- onBoolean(q: FlyFormQuestion, value: boolean): void;
7213
- onSingleSelect(q: FlyFormQuestion, value: string | readonly string[] | null): void;
7214
- onRadio(q: FlyFormQuestion, optionId: string): void;
7215
- onMultiToggle(q: FlyFormQuestion, optionId: string, checked: boolean): void;
7216
- onFiles(q: FlyFormQuestion, input: HTMLInputElement): void;
7217
- onDeclarationToggle(checked: boolean): void;
7218
- onSubmit(): void;
7219
- private _touch;
7220
- private _setFileError;
7221
- private _mutate;
7222
- private _emit;
7223
- private _collectAnswers;
7224
- private _isAnswered;
7225
- private _isEmptyValue;
7226
- private _selectedIds;
7694
+ readonly iconPath?: string;
7695
+ /** Default `'icon'`. */
7696
+ readonly kind?: MagicBarActionKind;
7697
+ /** Dimmed (`opacity .38`), NEVER hidden — a vanishing action teaches the user nothing. */
7698
+ readonly disabled?: boolean;
7227
7699
  /**
7228
- * The set of visible field ids for a given answer map. Base (non-branch-target)
7229
- * fields are always visible; a branch target becomes visible when a
7230
- * currently-visible field has a selected option that links to it. Computed to a
7231
- * fixpoint so chained reveals work, and cycle-safe: the visible set only grows and
7232
- * iteration is capped at `questions.length` passes, so an A↔B link cycle terminates.
7700
+ * Tooltip override while disabled. Absent is the normal case: the renderer
7701
+ * auto-suffixes the resolved label ("… select an item first"). Resolution
7702
+ * and the connector phrasing are the renderer's job precisely because they are
7703
+ * locale work, and this store holds keys rather than text.
7704
+ */
7705
+ readonly disabledTipKey?: string;
7706
+ /** Params for {@link disabledTipKey}. */
7707
+ readonly disabledTipParams?: MagicBarTextParams;
7708
+ /**
7709
+ * Mirror the glyph horizontally under `dir="rtl"`.
7233
7710
  *
7234
- * This is the TypeScript twin of the backend Fly.Sdk.Forms `BranchResolver`
7235
- * fixpoint the two must stay in lockstep so server + client agree on which fields
7236
- * are active.
7711
+ * Most marks in {@link FLY_MAGIC_BAR_ICONS} are direction-neutral a trash can, a gear, a
7712
+ * calendar grid read the same in every locale, and mirroring them would be wrong. A minority
7713
+ * encode a DIRECTION as their meaning: indent/outdent, promote (an up-and-right arrow), a
7714
+ * next/previous chevron, an export tray. Those are mirrored by every RTL desktop application
7715
+ * there is, and left unmirrored they point at the wrong edge of the screen in `ar` and `ur` —
7716
+ * three of this platform's four locales.
7717
+ *
7718
+ * Opt-in rather than automatic because no property of the geometry distinguishes the two
7719
+ * groups: only the publisher knows whether its arrow means "forward" (mirror) or "north-east"
7720
+ * (do not). Defaults to `false`, so every glyph shipped before this field existed paints exactly
7721
+ * as it did.
7722
+ *
7723
+ * It flips only the ICON. The button, its label and the bar's own item order already mirror
7724
+ * through logical CSS properties — see `magic-actions.component.scss`'s header.
7237
7725
  */
7238
- private _computeVisibleIds;
7239
- /** Imperative localization for file-validation messages (the pipe covers view strings). */
7240
- private _t;
7241
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyDynamicFormComponent, never>;
7242
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyDynamicFormComponent, "fly-dynamic-form, fly-survey-form", never, { "definition": { "alias": "definition"; "required": true; "isSignal": true; }; "isReadonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "hideSubmit": { "alias": "hideSubmit"; "required": false; "isSignal": true; }; "answers": { "alias": "answers"; "required": false; "isSignal": true; }; "showScore": { "alias": "showScore"; "required": false; "isSignal": true; }; "scoreDisplay": { "alias": "scoreDisplay"; "required": false; "isSignal": true; }; }, { "answersChange": "answersChange"; "validityChange": "validityChange"; "submit": "submit"; "fileSelected": "fileSelected"; }, never, never, true, never>;
7243
- }
7244
-
7245
- interface FlyCountry {
7246
- /** ISO 3166-1 alpha-2 code (uppercase), e.g. `AE`. */
7247
- code: string;
7248
- /** English display name. */
7249
- name: string;
7726
+ readonly mirrorInRtl?: boolean;
7727
+ /** Tooltip override while enabled. Absent the renderer uses `labelKey`. */
7728
+ readonly tipKey?: string;
7729
+ /** Params for {@link tipKey}. */
7730
+ readonly tipParams?: MagicBarTextParams;
7731
+ /**
7732
+ * Semantic colour. Default `'default'`; `'danger'` is the ONLY thing that makes
7733
+ * an icon red (D7), `'success'` the green completed-toggle treatment.
7734
+ */
7735
+ readonly tone?: MagicBarTone;
7736
+ /** `aria-pressed` for `kind: 'toggle'`. */
7737
+ readonly pressed?: boolean;
7738
+ /** Small accent dot — a non-default filter/sort is active. */
7739
+ readonly badgeDot?: boolean;
7740
+ /**
7741
+ * Reserved (D5 §4 item 7). The designs carry no keyboard-shortcut affordance
7742
+ * anywhere; the field exists so adding one later is a renderer change rather
7743
+ * than a contract break. Nothing renders it today.
7744
+ */
7745
+ readonly shortcut?: string;
7250
7746
  }
7251
- /** Frozen alpha-2 list, alphabetical by English name. */
7252
- declare const FLY_COUNTRIES: readonly FlyCountry[];
7253
-
7254
- /** Backend `CommentReportReason` enum — string-persisted, member names frozen. */
7255
- type FlyCommentReportReason = 'Spam' | 'Harassment' | 'OffTopic' | 'Misinformation' | 'Inappropriate' | 'Other';
7256
- /** Backend `CommentReportStatus` enumstring-persisted, member names frozen. */
7257
- type FlyCommentReportStatus = 'Open' | 'UnderReview' | 'ActionTaken' | 'Dismissed';
7258
- /**
7259
- * One comment, at any depth. `isDeleted` comments render as a tombstone (the
7260
- * host still supplies `body`-less structural fields so the reply subtree stays
7261
- * navigable; the component never assumes `body` is present when `isDeleted`).
7262
- * `canEdit` / `canDelete` / `canModerate` are pre-resolved by the host (Cerbos
7263
- * decision or equivalent) — the component never evaluates authorization itself.
7264
- */
7265
- interface FlyComment {
7266
- id: string;
7267
- authorUserId: string;
7268
- authorDisplayName?: string;
7269
- authorAvatarUrl?: string;
7270
- parentCommentId?: string;
7271
- depth: number;
7272
- body: string;
7273
- replyCount: number;
7274
- isEdited: boolean;
7275
- isDeleted: boolean;
7276
- createdAt: string;
7277
- updatedAt?: string;
7278
- canEdit: boolean;
7279
- canDelete: boolean;
7280
- canModerate: boolean;
7747
+ /** What a publisher supplies for one action. */
7748
+ interface MagicBarAction extends MagicBarActionSpec {
7749
+ readonly menu?: MagicBarRadioMenu;
7750
+ /**
7751
+ * Invoked on activation. The argument is the DOM element the renderer painted
7752
+ * for this actionsee {@link MagicBarActionView.run} for why it is passed and
7753
+ * what a publisher may do with it. Ignore it and this is the zero-argument
7754
+ * handler it has always been.
7755
+ */
7756
+ readonly onSelect?: (trigger?: HTMLElement) => void;
7281
7757
  }
7282
7758
  /**
7283
- * A paged slice of comments at one nesting level mirrors the service's
7284
- * `GET .../comments?parentCommentId=&page=&pageSize=` shape. Every depth is
7285
- * paged independently: the root page and each expanded parent's reply page are
7286
- * separate {@link FlyCommentPage} instances tracked by the host.
7759
+ * What the shell renders. `run()` always exists and always dispatches to the
7760
+ * publisher's LATEST handler for this id — including after a re-publish the
7761
+ * registry deliberately did not propagate (see `magic-bar-projection.ts`).
7287
7762
  */
7288
- interface FlyCommentPage {
7289
- items: FlyComment[];
7290
- page: number;
7291
- pageSize: number;
7292
- total: number;
7293
- hasMore: boolean;
7294
- }
7295
- /** `loadReplies` request expand a collapsed reply subtree, one page at a time. */
7296
- interface FlyCommentLoadRepliesRequest {
7297
- parentCommentId: string;
7298
- page: number;
7299
- }
7300
- /** `loadMore` request next page at the root level (`parentCommentId` omitted) or under a parent. */
7301
- interface FlyCommentLoadMoreRequest {
7302
- parentCommentId?: string;
7303
- page: number;
7304
- }
7305
- /** `submit` request a new root comment (`parentCommentId` omitted) or a reply. */
7306
- interface FlyCommentSubmitRequest {
7307
- body: string;
7308
- parentCommentId?: string;
7309
- }
7310
- /** `edit` request author-only body edit of an existing comment. */
7311
- interface FlyCommentEditRequest {
7312
- commentId: string;
7313
- body: string;
7763
+ interface MagicBarActionView extends MagicBarActionSpec {
7764
+ readonly menu: MagicBarRadioMenuView | null;
7765
+ /**
7766
+ * Fire the publisher's newest handler for this id.
7767
+ *
7768
+ * ## Why it carries the trigger element
7769
+ * An action that opens a POPOVER the publisher owns — the canonical case is a
7770
+ * listing screen's advanced-filter panel has to position that popover under
7771
+ * the button the user just pressed. The publisher cannot find that button:
7772
+ * embedded, it is painted by `fly-magic-actions` inside the shell's top-bar
7773
+ * pill, in the shell's DOM, several stacking contexts and one federation
7774
+ * boundary away from the remote that published the action. Before this argument
7775
+ * existed, PPM's projects register had to anchor its panel to its own page
7776
+ * header instead, which put the panel in the middle of the window while the
7777
+ * button that opened it sat in the chrome above — an affordance with no visible
7778
+ * relationship to its own trigger.
7779
+ *
7780
+ * Passing the element rather than modelling the popover is deliberate. The
7781
+ * panel's CONTENT is app schema (see {@link MagicBarSearch.onOpenFilters} for
7782
+ * the same ruling), so it must stay in the publisher's own DOM and injection
7783
+ * context; only its POSITION is chrome-relative. An element reference crosses
7784
+ * the federation boundary without any of the coupling a descriptor would need —
7785
+ * a DOM node is a DOM node in every bundle. `fly-filter-panel`'s `[anchorEl]`
7786
+ * is the intended consumer.
7787
+ *
7788
+ * ## What a publisher must not assume
7789
+ * The element is the trigger AS PAINTED RIGHT NOW. It is valid for the duration
7790
+ * of the popover, not beyond: a re-publish that drops this action id destroys it
7791
+ * (`@for (…; track action.id)`), and a mode switch repaints it in a different
7792
+ * chrome entirely. Hold it in a signal the popover's open-state clears, never in
7793
+ * long-lived state, and never mutate it.
7794
+ *
7795
+ * It is OPTIONAL because not every renderer has one to give — a keyboard-driven
7796
+ * invocation, a synthetic call in a test, an inline composer that paints no
7797
+ * button. A publisher that receives `undefined` must still work: for a popover
7798
+ * that means falling back to its own in-page anchoring, which is exactly what
7799
+ * `fly-filter-panel` does when `[anchorEl]` is null.
7800
+ */
7801
+ run(trigger?: HTMLElement): void;
7314
7802
  }
7315
- /** `delete` requestsoft delete (tombstone), author or moderator. */
7316
- interface FlyCommentDeleteRequest {
7317
- commentId: string;
7803
+ /** Render surface of a group the dedupe key for {@link MagicBarGroup}. */
7804
+ interface MagicBarGroupSpec {
7805
+ readonly id: string;
7806
+ /** i18n key for the `role="group"` aria-label ("Signal actions"). */
7807
+ readonly labelKey: string;
7808
+ /** Framed variant: 2 px gaps + a trailing separator. Plain (default) uses 8 px gaps. */
7809
+ readonly framed?: boolean;
7318
7810
  }
7319
- /** `report` request typed reason + optional free-text detail. */
7320
- interface FlyCommentReportRequest {
7321
- commentId: string;
7322
- reason: FlyCommentReportReason;
7323
- detail?: string;
7811
+ interface MagicBarGroup extends MagicBarGroupSpec {
7812
+ readonly items: readonly MagicBarAction[];
7324
7813
  }
7325
- /** `lockToggle` request moderator lock/unlock of the whole thread. */
7326
- interface FlyCommentLockToggleRequest {
7327
- locked: boolean;
7814
+ interface MagicBarGroupView extends MagicBarGroupSpec {
7815
+ readonly items: readonly MagicBarActionView[];
7328
7816
  }
7329
-
7330
7817
  /**
7331
- * **`fly-comment-thread`** — the design-system generic threaded-comments renderer.
7332
- *
7333
- * Renders a {@link FlyCommentPage} (root comments) plus lazily-fetched nested
7334
- * reply pages into an accessible, theme-aware thread: reply composer, inline
7335
- * edit, soft-delete (tombstone render), typed report-with-reason dialog, and
7336
- * moderator affordances (lock/unlock, delete-any). It is a platform surface —
7337
- * transport-agnostic like `fly-survey-form` / `fly-dynamic-form`: this component
7338
- * never calls an API. It only renders the pages the host hands it via
7339
- * `[rootPage]` / `[repliesByParent]` and emits typed requests
7340
- * (`loadReplies`/`loadMore`/`submit`/`edit`/`delete`/`report`/`lockToggle`) —
7341
- * the host owns HTTP, optimistic-insert reconciliation, and any live-update
7342
- * channel (SignalR, polling, …).
7343
- *
7344
- * Nested replies are fetched **lazily on expand** (`loadReplies`, never an
7345
- * eager load-all) — the explicit fix for the legacy unpaged-children scale bug
7346
- * — and each level pages independently via `loadMore`.
7347
- *
7348
- * The `comments` core app owns no user profile data, so `FlyComment` carries
7349
- * `authorUserId` and an OPTIONAL `authorDisplayName`. Rather than make every host
7350
- * map names in itself (and render GUIDs when it forgets), bind
7351
- * `[resolveDisplayName]` to `FlyUserDirectoryService.label` — see
7352
- * {@link FlyCommentThreadComponent.resolveDisplayName}. Still transport-agnostic:
7353
- * the resolver is a function the host hands over, not a call this component makes.
7354
- *
7355
- * i18n is self-sufficient through the `comment.*` keys in `DS_BASELINE_LOCALES`
7356
- * (en/ar/fr/ur), overridable by any consumer key of the same name. RTL works via
7357
- * logical CSS. Styling uses DS theme tokens (`--surface-*`, `--label-*`,
7358
- * `--separator`, `--fill-*`, `--accent`, `--system-*`) with light-neutral
7359
- * fallbacks so it stays readable even in a consumer that hasn't mapped them.
7818
+ * Render surface of the expanding search field — the dedupe key for
7819
+ * {@link MagicBarSearch}.
7360
7820
  *
7361
- * @example
7362
- * ```html
7363
- * <fly-comment-thread
7364
- * [rootPage]="rootPage()"
7365
- * [repliesByParent]="repliesByParent()"
7366
- * [currentUserId]="me.id"
7367
- * [resolveDisplayName]="directory.label"
7368
- * [canModerateThread]="canModerate()"
7369
- * [isLocked]="thread().isLocked"
7370
- * (loadReplies)="onLoadReplies($event)"
7371
- * (loadMore)="onLoadMore($event)"
7372
- * (submit)="onSubmit($event)"
7373
- * (edit)="onEdit($event)"
7374
- * (delete)="onDelete($event)"
7375
- * (report)="onReport($event)"
7376
- * (lockToggle)="onLockToggle($event)" />
7377
- * ```
7821
+ * This is a per-page typeahead, NOT a command palette: neither design package
7822
+ * has a global ⌘K surface, and the only "command" affordance is the Ask-AI menu.
7378
7823
  */
7379
- declare class FlyCommentThreadComponent {
7380
- /** The root-level comment page (accumulated across `loadMore` calls by the host). */
7381
- readonly rootPage: _angular_core.InputSignal<FlyCommentPage>;
7382
- /**
7383
- * Accumulated reply page per expanded parent (keyed by `parentCommentId`), also
7384
- * accumulated across `loadMore` calls by the host. Additive to the frozen
7385
- * contract's `rootPage` input — required because a lazily-nested tree has no
7386
- * other channel to carry fetched reply data back into the component.
7387
- */
7388
- readonly repliesByParent: _angular_core.InputSignal<Readonly<Record<string, FlyCommentPage>>>;
7389
- /** Current sort order label (host owns re-fetch on {@link sortChange}; purely descriptive here otherwise). */
7390
- readonly sort: _angular_core.InputSignal<"newest" | "oldest">;
7391
- /** Locked threads disable the composer (root + reply) entirely, for every user. */
7392
- readonly isLocked: _angular_core.InputSignal<boolean>;
7393
- /** The signed-in user's id — gates the "report" affordance (can't report your own comment). */
7394
- readonly currentUserId: _angular_core.InputSignal<string | null>;
7395
- /** Replay / read-only mode — hides composer, reply, edit, delete, report, and lock affordances.
7396
- * Aliased to `[readonly]` (the public binding); the class member avoids shadowing the
7397
- * TypeScript `readonly` keyword. */
7398
- readonly isReadonly: _angular_core.InputSignal<boolean>;
7399
- /** Thread-scoped moderator permission (lock/unlock). Additive — distinct from the
7400
- * per-comment `canModerate` flag, which gates delete-any on that specific comment. */
7401
- readonly canModerateThread: _angular_core.InputSignal<boolean>;
7824
+ interface MagicBarSearchSpec {
7825
+ /** Context-aware placeholder key ("Search tasks…", "Search events…"). */
7826
+ readonly placeholderKey: string;
7827
+ /** Params for {@link placeholderKey}, e.g. `{ count: 1204 }` for "Search {{count}} tasks…". */
7828
+ readonly placeholderParams?: MagicBarTextParams;
7402
7829
  /**
7403
- * Optional id→name lookup, for the common case where the backend DTO carries only
7404
- * `authorUserId`. The `comments` core app owns no user profile data, so without
7405
- * this every host had to map display names into `authorDisplayName` itself — and
7406
- * a host that forgot rendered raw GUIDs at the reader.
7830
+ * The text the search field should SHOW literal text, not an i18n key, because
7831
+ * it is the user's own query rather than a caption.
7407
7832
  *
7408
- * Bind `FlyUserDirectoryService.label` (a signal-backed read-through cache that
7409
- * resolves on demand and batches). Because the component reads it during render,
7410
- * the name appears by itself once the lookup lands the host keeps a plain
7411
- * page signal, with no `computed` derivation and no priming.
7833
+ * Exists because a view can arrive **already filtered**: a deep link carrying
7834
+ * `?q=…`, a restored session, a filter applied from a detail pane. Without it the
7835
+ * box renders empty over a visibly filtered list, and the user is looking at a
7836
+ * contradiction the chrome cannot explain.
7412
7837
  *
7413
- * The component still never calls an API: this is a function the HOST supplies,
7414
- * exactly like `fly-entity-lookup`'s `LOOKUP_APP_NAME_RESOLVER`. Absent (or
7415
- * returning `null`), rendering falls back as before. See {@link authorLabel} for
7416
- * the resolution order.
7417
- */
7418
- readonly resolveDisplayName: _angular_core.InputSignal<((userId: string) => string | null) | null>;
7419
- /** Expand a collapsed reply subtree always page 1 of that parent. */
7420
- readonly loadReplies: _angular_core.OutputEmitterRef<{
7421
- parentCommentId: string;
7422
- page: number;
7423
- }>;
7424
- /** Fetch the next page at a level: root when `parentCommentId` is omitted, else that parent's replies. */
7425
- readonly loadMore: _angular_core.OutputEmitterRef<{
7426
- parentCommentId?: string;
7427
- page: number;
7428
- }>;
7429
- /** A new root comment (`parentCommentId` omitted) or reply. */
7430
- readonly submit: _angular_core.OutputEmitterRef<{
7431
- body: string;
7432
- parentCommentId?: string;
7433
- }>;
7434
- /** Author-only body edit of an existing comment. */
7435
- readonly edit: _angular_core.OutputEmitterRef<{
7436
- commentId: string;
7437
- body: string;
7438
- }>;
7439
- /** Soft delete (tombstone) author's own comment, or any comment for a moderator. */
7440
- readonly delete: _angular_core.OutputEmitterRef<{
7441
- commentId: string;
7442
- }>;
7443
- /** Typed report submission. */
7444
- readonly report: _angular_core.OutputEmitterRef<{
7445
- commentId: string;
7446
- reason: FlyCommentReportReason;
7447
- detail?: string;
7448
- }>;
7449
- /** Moderator lock/unlock of the whole thread. */
7450
- readonly lockToggle: _angular_core.OutputEmitterRef<{
7451
- locked: boolean;
7452
- }>;
7453
- /** Additive: a sort-order pick from the header control — the host re-fetches page 1 in that order. */
7454
- readonly sortChange: _angular_core.OutputEmitterRef<"newest" | "oldest">;
7455
- private readonly _base;
7456
- readonly reportReasons: readonly FlyCommentReportReason[];
7457
- /** commentIds whose reply subtree is currently expanded/visible. */
7458
- private readonly _expanded;
7459
- /** Composer draft text, keyed by parentCommentId (root uses {@link ROOT_DRAFT_KEY}). */
7460
- private readonly _drafts;
7461
- /** commentIds whose reply composer box is currently open (distinct from `_expanded`, which toggles the read view). */
7462
- private readonly _replyBoxOpen;
7463
- private readonly _editingId;
7464
- private readonly _editDraft;
7465
- private readonly _confirmingDeleteId;
7466
- private readonly _reportingId;
7467
- private readonly _reportReason;
7468
- private readonly _reportDetail;
7469
- readonly rootDraft: _angular_core.Signal<string>;
7470
- readonly editingId: _angular_core.Signal<string | null>;
7471
- readonly confirmingDeleteId: _angular_core.Signal<string | null>;
7472
- readonly reportingId: _angular_core.Signal<string | null>;
7473
- readonly reportReason: _angular_core.Signal<FlyCommentReportReason | null>;
7474
- readonly reportDetail: _angular_core.Signal<string>;
7475
- fieldId(suffix: string): string;
7476
- isExpanded(commentId: string): boolean;
7477
- isReplyBoxOpen(commentId: string): boolean;
7478
- repliesFor(commentId: string): FlyCommentPage | undefined;
7479
- replyDraft(commentId: string): string;
7480
- isOwn(comment: FlyComment): boolean;
7838
+ * ## Why it is not called `query`
7839
+ * {@link MagicBarSearchView} which extends this interface — already declares
7840
+ * `query(text: string): void`, the late-bound invoker. A `query?: string` here
7841
+ * would make that view an illegal extension (TS2430: `(text: string) => void` is
7842
+ * not assignable to `string | undefined`), and the only other way out — renaming
7843
+ * the invoker removes a member of a shipped exported interface, which is a
7844
+ * ds-compat MAJOR (rule 7) and forks the federation singleton for every remote.
7845
+ * So the field takes the name that pairs with the invoker instead of colliding
7846
+ * with it: `queryText` is the text, `query()` submits it.
7847
+ *
7848
+ * ## Precedence — this is a SEED, not a controlled value
7849
+ * The field's live text is owned by the **shell**, because the user types into
7850
+ * it. This field is the publisher's chance to move it, and the shell adopts the
7851
+ * published value exactly three times:
7852
+ *
7853
+ * 1. the first time it binds this view,
7854
+ * 2. when {@link MagicBarContribution.viewKey} changes (reset-on-navigate the
7855
+ * previous view's query must not survive into the next one), and
7856
+ * 3. when the published value **itself changes** from the one previously
7857
+ * published.
7858
+ *
7859
+ * A re-publish carrying an UNCHANGED `queryText` never touches what the user has
7860
+ * typed. That third clause is the whole rule: the dominant re-publish is
7861
+ * `disabled: !hasSelection` flipping on a selection change, and a shell that
7862
+ * re-read `queryText` on every emitted view would revert a keystroke every time
7863
+ * the user selected a row mid-search — the classic controlled-input bug. The rule
7864
+ * is authored once, as {@link magicBarSearchText}, so no renderer re-derives it.
7865
+ *
7866
+ * `undefined` and `''` are **different** and the difference is load-bearing:
7867
+ * absent means "this view does not manage the field" (nothing is ever forced),
7868
+ * `''` means "clear it". Both the fingerprint and the seed comparison distinguish
7869
+ * them.
7870
+ *
7871
+ * ## Do not echo the user's keystrokes back through this field
7872
+ * A publisher that mirrors its `onQuery` argument straight back into `queryText`
7873
+ * is harmless (the seed changes to text the field already shows). A publisher
7874
+ * that mirrors it back **debounced** is not: typing "abc" then publishing
7875
+ * `queryText: 'a'` 300 ms later is, by rule 3, a genuine publisher-initiated
7876
+ * change, and the field snaps back to "a". Publish here only for state the view
7877
+ * owns independently of the field.
7878
+ */
7879
+ readonly queryText?: string;
7481
7880
  /**
7482
- * The name to render for a comment's author. Resolution order:
7483
- * explicit `authorDisplayName` (a host that already knows the name, or a
7484
- * backend that returns one, always wins) → {@link resolveDisplayName} →
7485
- * the raw `authorUserId`.
7881
+ * Expanded width in px a **desktop layout hint**, carried verbatim.
7486
7882
  *
7487
- * The raw id remains the last resort rather than a placeholder: it is stable,
7488
- * unique, and lets a reader correlate two comments by the same author even
7489
- * when the directory is unreachable.
7883
+ * The design's range is 140–420 (default 230), but this store deliberately does
7884
+ * NOT validate or clamp it: enforcing a paint range here would reject a whole
7885
+ * contribution over a cosmetic hint (leaving the previous view's actions in the
7886
+ * chrome), and mobile ignores the field entirely, so there is no single range to
7887
+ * enforce. `fly-magic-actions` clamps to the design range at paint time and is
7888
+ * the only place that decides what an out-of-range or non-finite value means.
7490
7889
  */
7491
- authorLabel(comment: FlyComment): string;
7492
- /** First character of {@link authorLabel}, for the avatar-initials fallback. */
7493
- authorInitial(comment: FlyComment): string;
7494
- canReport(comment: FlyComment): boolean;
7495
- canReply(): boolean;
7496
- canDeleteComment(comment: FlyComment): boolean;
7497
- canEditComment(comment: FlyComment): boolean;
7498
- formatDate(iso: string): string;
7499
- onRootDraftChange(value: string): void;
7500
- submitRoot(): void;
7501
- toggleReplies(comment: FlyComment): void;
7502
- private _expand;
7503
- loadMoreRoot(): void;
7504
- loadMoreReplies(parentId: string): void;
7505
- openReplyBox(commentId: string): void;
7506
- closeReplyBox(commentId: string): void;
7507
- onReplyDraftChange(commentId: string, value: string): void;
7508
- submitReply(commentId: string): void;
7509
- startEdit(comment: FlyComment): void;
7510
- onEditDraftChange(value: string): void;
7511
- saveEdit(comment: FlyComment): void;
7512
- cancelEdit(): void;
7513
- requestDelete(comment: FlyComment): void;
7514
- confirmDelete(comment: FlyComment): void;
7515
- cancelDelete(): void;
7516
- openReport(comment: FlyComment): void;
7517
- onReportReasonChange(reason: FlyCommentReportReason): void;
7518
- onReportDetailChange(value: string): void;
7519
- submitReport(): void;
7520
- closeReport(): void;
7521
- /** Space would otherwise scroll the page while the backdrop (role="button") is focused. */
7522
- onOverlayBackdropSpace(event: Event): void;
7523
- toggleLock(): void;
7524
- onSortPick(next: 'newest' | 'oldest'): void;
7525
- private _setDraft;
7526
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyCommentThreadComponent, never>;
7527
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyCommentThreadComponent, "fly-comment-thread", never, { "rootPage": { "alias": "rootPage"; "required": true; "isSignal": true; }; "repliesByParent": { "alias": "repliesByParent"; "required": false; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; "isLocked": { "alias": "isLocked"; "required": false; "isSignal": true; }; "currentUserId": { "alias": "currentUserId"; "required": false; "isSignal": true; }; "isReadonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "canModerateThread": { "alias": "canModerateThread"; "required": false; "isSignal": true; }; "resolveDisplayName": { "alias": "resolveDisplayName"; "required": false; "isSignal": true; }; }, { "loadReplies": "loadReplies"; "loadMore": "loadMore"; "submit": "submit"; "edit": "edit"; "delete": "delete"; "report": "report"; "lockToggle": "lockToggle"; "sortChange": "sortChange"; }, never, never, true, never>;
7890
+ readonly openWidth?: number;
7891
+ /** Accent dot on the Filters button a non-default filter is applied. */
7892
+ readonly filtersActive?: boolean;
7528
7893
  }
7529
-
7530
- /**
7531
- * A proof-of-work challenge issued by `Fly.Sdk.Captcha` (`CaptchaChallenge`).
7532
- * The solver must find `n` in `[0, maxNumber)` such that
7533
- * `sha256HexLower(salt + n) === challenge`.
7534
- */
7535
- interface FlyCaptchaChallenge {
7536
- /** Always `"SHA-256"` today. */
7537
- algorithm: string;
7538
- /** Lowercase-hex SHA-256 of `salt + number` for the secret solution number. */
7539
- challenge: string;
7540
- /** Base64 random salt, unique per challenge. */
7541
- salt: string;
7542
- /** HMAC signature binding challenge + maxNumber + expiresAt (server-verified). */
7543
- signature: string;
7544
- /** Exclusive upper bound of the search space (difficulty). */
7545
- maxNumber: number;
7546
- /** ISO-8601 instant after which the solution is rejected. Echoed back verbatim. */
7547
- expiresAt: string;
7894
+ interface MagicBarSearch extends MagicBarSearchSpec {
7895
+ readonly onQuery?: (query: string) => void;
7896
+ /**
7897
+ * Opens the view's own Advanced-search overlay. Presence of this handler is
7898
+ * what makes the Filters button exist — the DS deliberately does not model the
7899
+ * dialog itself. Its fields are app schema (Circles' PESTLE/horizon grid), and
7900
+ * a descriptor for them would be business logic the design system cannot
7901
+ * validate or render honestly.
7902
+ */
7903
+ readonly onOpenFilters?: (trigger?: HTMLElement) => void;
7548
7904
  }
7549
- /**
7550
- * The solved payload, matching `Fly.Sdk.Captcha.CaptchaSolution` field-for-field
7551
- * (camelCase Web JSON). Serialized to the opaque base64 token the backend decodes:
7552
- * `base64(utf8(JSON.stringify(solution)))` — i.e. `Convert.ToBase64String` +
7553
- * `JsonSerializerDefaults.Web`. Every field except `number` is echoed from the
7554
- * challenge unchanged so the server's stateless signature check passes.
7555
- */
7556
- interface FlyCaptchaSolution {
7557
- algorithm: string;
7558
- challenge: string;
7559
- salt: string;
7560
- number: number;
7561
- signature: string;
7562
- maxNumber: number;
7563
- expiresAt: string;
7905
+ interface MagicBarSearchView extends MagicBarSearchSpec {
7906
+ /** True iff the publisher can open an advanced-search overlay. */
7907
+ readonly hasFilters: boolean;
7908
+ /**
7909
+ * Submit the field's current text to the publisher's newest `onQuery`. The
7910
+ * inbound half what the field should SHOW is
7911
+ * {@link MagicBarSearchSpec.queryText}, and that asymmetry in naming is forced:
7912
+ * this member shipped first, and renaming it would be a ds-compat MAJOR.
7913
+ */
7914
+ query(text: string): void;
7915
+ /**
7916
+ * Open the publisher's advanced-search overlay. Carries the Filters button the
7917
+ * shell painted, for the same reason and under the same caveats as
7918
+ * {@link MagicBarActionView.run} — a publisher whose overlay is a popover
7919
+ * anchors it there instead of guessing at a position inside its own page.
7920
+ */
7921
+ openFilters(trigger?: HTMLElement): void;
7564
7922
  }
7565
- /** The solver lifecycle surfaced by the component's `state()` signal. */
7566
- type FlyCaptchaState = 'idle' | 'verifying' | 'verified' | 'expired' | 'error';
7567
-
7568
- /** Lowercase-hex SHA-256 of `input` via Web Crypto — matches `Convert.ToHexStringLower(SHA256…)`. */
7569
- declare function flyCaptchaSha256Hex(input: string): Promise<string>;
7570
- /** Standard base64 of the UTF-8 bytes of `s` — matches .NET `Convert.ToBase64String`. */
7571
- declare function flyCaptchaBase64Utf8(s: string): string;
7572
7923
  /**
7573
- * Builds the opaque base64 token the backend's `CaptchaSolution.FromToken` expects.
7574
- * Exported so a host (or a test) can construct/verify the token shape without the
7575
- * component.
7924
+ * One view's complete claim on the magic bar.
7925
+ *
7926
+ * Identity (`appId` / `windowId`) is deliberately absent: it belongs to the
7927
+ * {@link MagicBarPublisher} that was minted for this owner, so a later publish
7928
+ * cannot re-target another app's slot.
7576
7929
  */
7577
- declare function flyCaptchaBuildToken(solution: FlyCaptchaSolution): string;
7930
+ interface MagicBarContribution {
7931
+ /**
7932
+ * What the publisher is showing, e.g. `'tasks:detail'`. A change here is
7933
+ * reset-on-navigate: the registry replaces the slot wholesale rather than
7934
+ * reconciling, so nothing from the previous view can survive.
7935
+ */
7936
+ readonly viewKey: string;
7937
+ readonly search?: MagicBarSearch | null;
7938
+ /** Ordered clusters, separator-joined. */
7939
+ readonly groups?: readonly MagicBarGroup[];
7940
+ /** The accent-filled CTA ("New task", "Add signal"). */
7941
+ readonly primary?: MagicBarAction | null;
7942
+ /**
7943
+ * Ids of {@link AgentCommand}s this view considers most relevant, most-relevant
7944
+ * first — the Ask-AI menu's per-view sets.
7945
+ *
7946
+ * Ids, not command objects: the platform already models commands (manifest
7947
+ * `commands[]` → `AgentCommandRegistry`), and D5 §6 item 9 is explicit that
7948
+ * production must source these from manifests instead of the mock's hardcoded
7949
+ * `{ title, note, prompt }` triples. Duplicating that model here would fork the
7950
+ * palette and reintroduce hardcoded prompt text. Unknown ids are dropped by the
7951
+ * resolver; an empty list leaves the shell's affinity ranking in charge.
7952
+ */
7953
+ readonly quickCommandIds?: readonly string[];
7954
+ /** The "…" overflow set. Reserved design intent (D5 §6 item 8) — Print / Export / Settings. */
7955
+ readonly overflow?: readonly MagicBarAction[];
7956
+ }
7957
+ /** The shell-facing projection of a contribution. See `magic-bar-projection.ts`. */
7958
+ interface MagicBarView {
7959
+ readonly appId: string;
7960
+ readonly windowId?: string;
7961
+ /** {@link magicBarOwnerKey} of the publisher. */
7962
+ readonly ownerKey: string;
7963
+ readonly viewKey: string;
7964
+ readonly search: MagicBarSearchView | null;
7965
+ readonly groups: readonly MagicBarGroupView[];
7966
+ readonly primary: MagicBarActionView | null;
7967
+ readonly quickCommandIds: readonly string[];
7968
+ readonly overflow: readonly MagicBarActionView[];
7969
+ }
7578
7970
  /**
7579
- * **`fly-captcha`** the design-system client for the self-hosted proof-of-work
7580
- * captcha (`Fly.Sdk.Captcha`).
7581
- *
7582
- * Given a {@link FlyCaptchaChallenge}, it brute-forces `n` in `[0, maxNumber)`
7583
- * until `sha256HexLower(salt + n) === challenge`, then emits the solution as the
7584
- * base64 token the backend decodes ({@link flyCaptchaBuildToken}). The search
7585
- * hashes candidates with a vendored **synchronous** SHA-256 ({@link sha256HexSync}
7586
- * from `./sha256` — no `crypto.subtle`/promise overhead per candidate, which used
7587
- * to dominate solve time), in chunks scheduled on `requestAnimationFrame` so a
7588
- * chunk boundary still yields back to the browser between slices. It auto-starts
7589
- * whenever the `[challenge]` input changes. Implements
7590
- * {@link ControlValueAccessor} so it drops into a reactive form / `[(ngModel)]` —
7591
- * the control value IS the token string.
7592
- *
7593
- * Expiry is the host's concern to refresh: the component checks `expiresAt` up
7594
- * front and while solving, emits {@link expired}, and stops. i18n is
7595
- * self-sufficient via the `captcha.*` baseline keys; RTL works via logical CSS.
7596
- *
7597
- * @example
7598
- * ```html
7599
- * <fly-captcha [challenge]="challenge()" (solved)="post($event)" (expired)="refresh()" />
7600
- * ```
7971
+ * A view's handle on its magic-bar slot. Minted once per publisher (not per
7972
+ * publish) because the publish rate is the whole point of this surface: the
7973
+ * sibling registries hand back a handle per `register()` and expect a handful of
7974
+ * calls per app lifetime, whereas a magic-bar publisher re-emits on every
7975
+ * selection change. Handing out a fresh handle each time would leave the caller
7976
+ * holding a dead one and force it to reassign on every keystroke.
7601
7977
  */
7602
- declare class FlyCaptchaComponent implements ControlValueAccessor, OnDestroy {
7603
- /** The challenge to solve. Setting (or replacing) it auto-starts a fresh solve. */
7604
- readonly challenge: _angular_core.InputSignal<FlyCaptchaChallenge | null>;
7605
- /** Candidates hashed synchronously in one slice before yielding back to the browser
7606
- * on the next animation frame. Higher = fewer `requestAnimationFrame` hops (each
7607
- * carries ~16ms of frame-boundary overhead), more synchronous work per slice.
7608
- * 20,000 keeps a slice's own hashing time well under a long-task budget on modern
7609
- * hardware (the vendored sync SHA-256 does several hundred thousand hashes/sec)
7610
- * while needing only a handful of hops to clear the library's default difficulty. */
7611
- readonly chunkSize: _angular_core.InputSignal<number>;
7612
- /** The solution token once solved — the exact string to POST back to the backend. */
7613
- readonly solved: _angular_core.OutputEmitterRef<string>;
7614
- /** The challenge expired (up-front or mid-solve). The host should request a new one. */
7615
- readonly expired: _angular_core.OutputEmitterRef<void>;
7616
- readonly state: _angular_core.WritableSignal<FlyCaptchaState>;
7617
- readonly token: _angular_core.WritableSignal<string | null>;
7618
- /** Monotonic generation — bumped on every restart/destroy so a stale async chunk
7619
- * or timer that captured an older generation no-ops instead of racing. */
7620
- private _gen;
7621
- private _raf;
7622
- private _expiryTimer;
7623
- private _onChange;
7624
- private _onTouched;
7625
- constructor();
7626
- ngOnDestroy(): void;
7627
- private _restart;
7978
+ interface MagicBarPublisher {
7979
+ readonly owner: MagicBarOwnerRef;
7628
7980
  /**
7629
- * Hashes `[start, end)` synchronously with {@link sha256HexSync} no per-candidate
7630
- * `await`, so there is no promise/microtask overhead between candidates — then
7631
- * yields to the next animation frame for the remainder. Bails to `error` state if
7632
- * hashing itself throws (defensive; a pure function over a short string should not)
7633
- * or if the whole `[0, maxNumber)` space is exhausted with no match, which means the
7634
- * challenge is corrupt or was tampered with.
7981
+ * Replace this owner's contribution. No-op after {@link dispose}, and no-op
7982
+ * after the shell has {@link MagicBarRegistry.clear}ed this owner key.
7983
+ *
7984
+ * **Publish is last-writer-wins and eviction is not** a deliberate asymmetry.
7985
+ * A stale publisher (one that has been replaced on this key by a newer one)
7986
+ * cannot `dispose()` the newer one out of the slot, but it *can* still take the
7987
+ * slot back by publishing. Symmetry would need a "newest publisher wins" rule
7988
+ * enforced on write, which would make the first publish of a replacement
7989
+ * publisher silently drop whenever two live views briefly share a key. The
7990
+ * exposure is bounded: two live publishers on one owner key only happen while a
7991
+ * view is being replaced in place, and the loser re-publishes on its next
7992
+ * selection change. The unbounded case — a publisher that outlives its window —
7993
+ * is closed by `clear()`'s tombstone, not by write ordering.
7994
+ */
7995
+ publish(contribution: MagicBarContribution): void;
7996
+ /**
7997
+ * Release the slot. Idempotent, and a no-op if another publisher has since
7998
+ * taken this owner key — the same stale-handle rule the sibling registries use.
7635
7999
  */
7636
- private _solveChunk;
7637
- private _onSolved;
7638
- private _cancel;
7639
- private _schedule;
7640
- private _cancelSchedule;
7641
- writeValue(value: string | null): void;
7642
- registerOnChange(fn: (v: string | null) => void): void;
7643
- registerOnTouched(fn: () => void): void;
7644
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyCaptchaComponent, never>;
7645
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyCaptchaComponent, "fly-captcha", never, { "challenge": { "alias": "challenge"; "required": false; "isSignal": true; }; "chunkSize": { "alias": "chunkSize"; "required": false; "isSignal": true; }; }, { "solved": "solved"; "expired": "expired"; }, never, never, true, never>;
8000
+ dispose(): void;
7646
8001
  }
7647
8002
 
7648
8003
  /**
7649
- * Singleton registry of slash commands offered by the agent input palette.
8004
+ * The Gantt **Period** control as a magic-bar radio-menu item.
7650
8005
  *
7651
- * Lives in the DS so it crosses the federation boundary as a single instance via
7652
- * `sharedMappings: ['@flyos/design-system']`. Federated remotes register
7653
- * their commands at boot (and dispose on window close) without forking the shell.
8006
+ * ## Why it is called Period and not Zoom
8007
+ * The control does not scale the chart — it changes the unit the time axis is measured in
8008
+ * (day / week / month / quarter), which is what a planner calls the *period*. "Zoom" invites the
8009
+ * browser's own Ctrl+wheel meaning, which this chart deliberately remaps to horizontal scrolling
8010
+ * (`gantt-pan.ts`), so two different verbs would have shared one word on the same surface. The
8011
+ * underlying type stays {@link GanttZoom} — renaming a shipped exported type is MAJOR under
8012
+ * `tools/ds-compat` and would fork the federation singleton for every remote over a caption.
7654
8013
  *
7655
- * Storage is a signal store. `register` is O(1) for the unique-id case and O(n) when
7656
- * replacing an existing id (filter then push). The append-then-replace strategy is
7657
- * deliberate: registrations are rare (each one corresponds to a remote's app-init
7658
- * effect), and the tradeoff buys us a stable id-collision contract the *latest*
7659
- * registration wins, and the previous handle's `dispose()` becomes a no-op rather
7660
- * than removing the new entry.
8014
+ * ## Why it lives in the design system
8015
+ * A period picker is not app vocabulary: every screen that draws a `fly-gantt` needs exactly these
8016
+ * four levels under exactly these labels, and the first app to grow a second Gantt screen
8017
+ * immediately has two copies to keep in step (PPM had a project plan, a project schedule and a
8018
+ * portfolio roadmap). Shipping the item beside the chart means the labels ride the DS baseline
8019
+ * locales in all four languages and a consumer supplies only state and a callback.
8020
+ *
8021
+ * `defaultValue` is the CALLER's load-time default — `'week'` on a single project's plan, `'month'`
8022
+ * on a portfolio spanning years — because the trigger's accent "non-default" dot should light only
8023
+ * once the user has left that screen's own entry point.
8024
+ *
8025
+ * `labelKeyPrefix` exists for a consumer that already ships its own translated period labels and
8026
+ * does not want the DS baseline (PPM's `ppm.projects.plan.zoom.*` predate this helper). It appends
8027
+ * `.label` for the trigger and `.<zoom>` per option, matching the baseline layout exactly.
7661
8028
  */
7662
- declare class AgentCommandRegistry {
7663
- private readonly _commands;
7664
- /** All currently-registered commands, in insertion order. */
7665
- readonly all: Signal<readonly AgentCommandRegistration[]>;
7666
- /**
7667
- * Returns a signal of commands whose scope is `'global'` OR whose `scope.appId` is in
7668
- * the live app set. The signal recomputes when either the registry or `liveAppIds`
7669
- * changes — pass a `Signal<ReadonlySet<string>>` from the host's app-registry service
7670
- * for reactive filtering.
7671
- */
7672
- visible(liveAppIds: ReadonlySet<string> | Signal<ReadonlySet<string>>): Signal<readonly AgentCommandRegistration[]>;
7673
- /**
7674
- * Register a single command. Returns a handle whose `dispose()` removes the row by
7675
- * id. If the same id is later re-registered, the original handle's `dispose()`
7676
- * becomes a no-op (the newer registration owns the row). Idempotent disposal.
7677
- */
7678
- register(cmd: AgentCommandRegistration): AgentCommandHandle;
7679
- /**
7680
- * Bulk register. Rolls back on duplicate id within the input batch (throws before any
7681
- * row lands). Cross-batch duplicates against existing rows follow the standard
7682
- * "latest wins" rule and do NOT trigger rollback.
7683
- *
7684
- * Returns a handle whose `dispose()` tears down every row registered by this call.
7685
- */
7686
- registerAll(cmds: readonly AgentCommandRegistration[]): AgentCommandHandle;
7687
- /** Tear down by id. Idempotent. */
7688
- unregister(id: string): void;
7689
- /** Monotonic counter; identifies which registration call currently owns each id. */
7690
- private _generation;
7691
- /** id generation. Used so a stale handle's `dispose()` is a no-op after replacement. */
7692
- private readonly _owners;
7693
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentCommandRegistry, never>;
7694
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentCommandRegistry>;
7695
- }
8029
+ declare function ganttPeriodBarItem(spec: {
8030
+ value: GanttZoom;
8031
+ defaultValue: GanttZoom;
8032
+ disabled?: boolean;
8033
+ /** Key namespace for the trigger + option captions. Defaults to the DS baseline `gantt.period`. */
8034
+ labelKeyPrefix?: string;
8035
+ onSelect: (zoom: GanttZoom) => void;
8036
+ }): MagicBarAction;
8037
+ /**
8038
+ * The **Indent** / **Outdent** pair as magic-bar actions, for a chart whose consumer maps outline
8039
+ * level onto something it can persist.
8040
+ *
8041
+ * Two actions rather than one toggle: they are opposite verbs with independent availability (a row
8042
+ * can be indentable and not outdentable, and at the top of a list neither applies), and a toggle
8043
+ * would have to pick a label for a state the user cannot see.
8044
+ *
8045
+ * ## Icon-only, and why they are still discoverable
8046
+ * Both are icon-only despite `skills/shell-magic-bar-views.md` §5's default, because the
8047
+ * indent/outdent glyph pair is one of the few marks a user genuinely reads without help — it is the
8048
+ * same pair every word processor, IDE and desktop planner has shipped for thirty years, and the two
8049
+ * are identified by being MIRRORS of each other, which a label cannot improve on. They also sort
8050
+ * inside Add/Delete/Edit under {@link orderMagicBarItems}, so they never take the outer edge from a
8051
+ * verb that needs it.
8052
+ *
8053
+ * ## Omitted, never dimmed
8054
+ * An unavailable verb is left OUT of the returned array — the magic bar renders no disabled
8055
+ * buttons at all (`skills/shell-magic-bar-views.md` §6). The bar is one small shared strip
8056
+ * and its cost is measured in how many things it shows at once, so a dimmed button spends that
8057
+ * budget on an action the user cannot take. This pair is the clearest case: at most ONE of the two
8058
+ * is ever available for a given row, so dimming doubled the group's width to say nothing. What
8059
+ * availability depends on is also on screen while the bar is being read — a row already nested
8060
+ * cannot nest further — so the dimmed state was not teaching anything the chart did not show.
8061
+ *
8062
+ * Returns 0, 1 or 2 actions. Spread it; never index it.
8063
+ */
8064
+ declare function ganttOutlineBarItems(spec: {
8065
+ canIndent: boolean;
8066
+ canOutdent: boolean;
8067
+ onIndent: () => void;
8068
+ onOutdent: () => void;
8069
+ }): MagicBarAction[];
7696
8070
 
7697
8071
  /**
7698
- * Singleton registry of entity lookups offered by the `/lookup` typeahead.
8072
+ * Normalise a label / query for comparison: case-folded and stripped of combining marks, so a
8073
+ * search for `resume` finds `Résumé` and an Arabic query matches regardless of harakat.
7699
8074
  *
7700
- * Mirrors {@link AgentCommandRegistry}'s federation-singleton story
7701
- * (`sharedMappings: ['@flyos/design-system']`), id-collision
7702
- * "latest wins" contract, and disposable-handle ergonomics. OS-core entities
7703
- * (note / calendar event / file) register once at shell bootstrap via
7704
- * `CORE_APP_LOOKUPS`; federated remotes (Circles: scenario / trend / signal)
7705
- * register at remote-component boot and dispose on window close.
8075
+ * `NFD` + strip `\p{M}` rather than a hand-rolled accent table: the table approach covers Latin-1
8076
+ * and silently fails on every other script this platform ships (`ar`, `ur`), which is precisely the
8077
+ * locale pair where a user is most likely to type an unmarked form.
8078
+ */
8079
+ declare function normalizeGanttSearchText(value: string): string;
8080
+ /**
8081
+ * Filter a Gantt row set to the rows a query is "about", **preserving tree context**.
7706
8082
  *
7707
- * **Scope semantics diverge from commands.** Commands HIDE when their `appId`
7708
- * isn't in `liveAppIds`. Lookups DO NOT they're always offered, and
7709
- * `{appId}` is just a *priority hint* that bumps that lookup to the top of
7710
- * the entity picker when the app is live. See {@link LookupRegistration.scope}
7711
- * for the rationale.
8083
+ * A flat `rows.filter(matches)` is the obvious implementation and it is wrong here for two
8084
+ * independent reasons, both of which change what the user sees rather than merely how it looks:
7712
8085
  *
7713
- * Storage is a signal store keyed on {@link LookupRegistration.entity}. Because
7714
- * `entity` is the collision key, an app re-registering the same entity replaces
7715
- * the prior descriptor; a stale handle's `dispose()` then no-ops.
8086
+ * 1. **A matching child whose parent is filtered out becomes a root.** `flattenRows` treats an
8087
+ * unknown `parentId` as a root, so a task matching "deploy" would jump to depth 0 and lose the
8088
+ * milestone that gives it meaning. The user asked to find a row, not to re-parent it.
8089
+ * 2. **A matching parent whose children are filtered out stops being a group.** Its chevron
8090
+ * disappears and its derived span collapses to its own dates — so searching for a milestone
8091
+ * silently changes the bar the chart draws for it.
8092
+ *
8093
+ * So the kept set is the **closure** of the matches over both directions of the tree: every match,
8094
+ * every ancestor of a match (context above), and every descendant of a match (context below).
8095
+ * Searching a milestone's name shows that milestone with its whole task list intact; searching a
8096
+ * task's name shows the task under its own milestone.
8097
+ *
8098
+ * Order is preserved — this is a filter, never a re-sort. An empty / whitespace query returns the
8099
+ * input array by reference, so a non-searching chart pays nothing and its `computed` never
8100
+ * invalidates on identity alone.
7716
8101
  */
7717
- declare class AgentLookupRegistry {
7718
- private readonly _lookups;
7719
- /** All currently-registered lookups, in insertion order. */
7720
- readonly all: Signal<readonly LookupRegistration[]>;
7721
- /**
7722
- * All registered lookups, sorted by affinity to `liveAppIds`:
7723
- *
7724
- * 1. Lookups whose `scope.appId` is in the live app set (in registration
7725
- * order within that bucket).
7726
- * 2. Then everything else `'global'` lookups AND scoped lookups whose
7727
- * app isn't currently live — in registration order.
7728
- *
7729
- * Recomputes when either the registry or `liveAppIds` changes. Pass a
7730
- * `Signal<ReadonlySet<string>>` from the host's app-registry for reactive
7731
- * re-sorting. **Always returns the full registry** — see the type doc on
7732
- * {@link LookupRegistration.scope} for why this differs from
7733
- * {@link AgentCommandRegistry.visible}.
7734
- */
7735
- visible(liveAppIds: ReadonlySet<string> | Signal<ReadonlySet<string>>): Signal<readonly LookupRegistration[]>;
7736
- /**
7737
- * Register one lookup. Returns a handle whose `dispose()` removes the row by
7738
- * `entity`. A later re-registration of the same entity makes the original
7739
- * handle's `dispose()` a no-op (the newer registration owns the row).
7740
- */
7741
- register(lookup: LookupRegistration): LookupHandle;
7742
- /**
7743
- * Bulk register. Rolls back on a duplicate entity WITHIN the input batch
7744
- * (throws before any row lands). Cross-batch duplicates against existing rows
7745
- * follow the standard "latest wins" rule and do NOT trigger rollback.
7746
- */
7747
- registerAll(lookups: readonly LookupRegistration[]): LookupHandle;
7748
- /**
7749
- * Resolve a deep-link anchor to a concrete launch target.
7750
- *
7751
- * `kind` is the dotted `<appId>.<entity>` token the agents backend emits
7752
- * inside `flyos:<kind>/<id>` chat-answer anchors — the same entity-kind
7753
- * vocabulary as drag-payload kinds and `ref` parts. Returns
7754
- * `{ appId, route }` when a registered lookup for that `(appId, entity)`
7755
- * pair carries a {@link LookupDescriptor.deepLinkRoute} template; `null`
7756
- * otherwise (unknown entity, app mismatch, or no template — e.g. the
7757
- * owning app isn't installed) so the caller renders plain text rather than
7758
- * a dead link.
7759
- *
7760
- * `appId` and `entity` are both dot-free by their own grammars, so the
7761
- * FIRST dot is the unambiguous split point; a dotless `kind` can't carry an
7762
- * app and never resolves. The template's single `{id}` placeholder is
7763
- * substituted URL-encoded.
7764
- */
7765
- resolveDeepLink(kind: string, id: string): {
7766
- readonly appId: string;
7767
- readonly route: string;
7768
- } | null;
7769
- /**
7770
- * Resolve a deep-link target from a bare `(entity, id)` pair — the shape a
7771
- * `/lookup` ref carries (it has no `<appId>.<entity>` kind token; the owning
7772
- * app is implicit in the registered descriptor). `entity` is the registry's
7773
- * unique storage key, so it identifies the descriptor unambiguously without
7774
- * an app prefix.
7775
- *
7776
- * Returns `{ appId, route }` (the descriptor's {@link LookupDescriptor.appId}
7777
- * / affinity `scope.appId` as the owner, `{id}` substituted URL-encoded) when
7778
- * a matching descriptor carries a {@link LookupDescriptor.deepLinkRoute};
7779
- * `null` otherwise (unknown entity, no template, or the owning app has since
7780
- * unregistered) so callers render plain text rather than a dead link — the
7781
- * same graceful-degrade contract as {@link resolveDeepLink}.
7782
- */
7783
- resolveDeepLinkForEntity(entity: string, id: string): {
7784
- readonly appId: string;
7785
- readonly route: string;
7786
- } | null;
7787
- /** Tear down by entity. Idempotent. */
7788
- unregister(entity: string): void;
7789
- /** Monotonic counter; identifies which registration call currently owns each entity. */
7790
- private _generation;
7791
- /** entity → generation. Lets a stale handle's `dispose()` no-op after replacement. */
7792
- private readonly _owners;
7793
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentLookupRegistry, never>;
7794
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentLookupRegistry>;
8102
+ declare function filterGanttRows(rows: readonly GanttRow[], query: string): readonly GanttRow[];
8103
+
8104
+ /**
8105
+ * Apply a {@link GanttRowReorder} to a plain `GanttRow[]`, returning a new array.
8106
+ *
8107
+ * Optional: a consumer that persists to a server and re-reads does not need it. It exists for the
8108
+ * two cases that do — an optimistic update while the write is in flight, and an app whose row
8109
+ * order is client-side only — because the fiddly part is not the parent change, it is keeping the
8110
+ * moved row's DESCENDANTS with it and landing the block after the previous sibling's whole
8111
+ * subtree rather than immediately after that sibling's own row.
8112
+ *
8113
+ * The moved row's `parentId` is rewritten to the move's parent; every other row is untouched, so a
8114
+ * consumer's extra fields survive.
8115
+ */
8116
+ declare function applyGanttRowReorder(rows: readonly GanttRow[], move: GanttRowReorder): GanttRow[];
8117
+
8118
+ /** The 14 rendered field kinds. Drives the control the renderer picks. */
8119
+ type FlyFormQuestionType = 'Text' | 'MultilineText' | 'Number' | 'Email' | 'Date' | 'DateTime' | 'Boolean' | 'SingleSelect' | 'MultipleSelect' | 'RadioButton' | 'Country' | 'File' | 'MultiFile' | 'Image';
8120
+ /**
8121
+ * A selectable option on a choice field (SingleSelect / MultipleSelect /
8122
+ * RadioButton). `linkedQuestionIds` drive conditional branching: selecting this
8123
+ * option reveals those (branch-target) fields. `score` is author-only and is only
8124
+ * surfaced to the viewer when the host opts in via `[showScore]` (a scorecard);
8125
+ * a respondent-facing form leaves `showScore` false and never renders it.
8126
+ */
8127
+ interface FlyFormOption {
8128
+ id: string;
8129
+ label: string;
8130
+ score?: number;
8131
+ linkedQuestionIds?: string[];
8132
+ }
8133
+ /** Per-type constraints/affordances an author can attach to a field. */
8134
+ interface FlyFormFieldOptions {
8135
+ minLength?: number;
8136
+ maxLength?: number;
8137
+ numericMin?: number;
8138
+ numericMax?: number;
8139
+ rows?: number;
8140
+ maxFileSizeMb?: number;
8141
+ maxFileCount?: number;
8142
+ allowedExtensions?: string[];
8143
+ hasSearch?: boolean;
8144
+ }
8145
+ /**
8146
+ * A single form field. `isBranchTarget` fields start hidden. `sectionId` /
8147
+ * `criteriaGroup` are additive grouping hints a scorecard uses; they are ignored
8148
+ * by the respondent-facing survey path, so binding them changes nothing there.
8149
+ */
8150
+ interface FlyFormQuestion {
8151
+ id: string;
8152
+ text: string;
8153
+ description?: string;
8154
+ type: FlyFormQuestionType;
8155
+ required: boolean;
8156
+ isBranchTarget: boolean;
8157
+ /** Additive: an author-hidden field. `false` excludes it from the visible set (and thus from
8158
+ * required-enforcement); omitted / `true` renders as normal. Mirrors the backend `IsVisible`. */
8159
+ isVisible?: boolean;
8160
+ fieldOptions?: FlyFormFieldOptions;
8161
+ options: FlyFormOption[];
8162
+ /** Additive: groups fields into a visual section (scorecard). Ignored by surveys. */
8163
+ sectionId?: string;
8164
+ /** Additive: labels the weighted-criteria group a scored field belongs to. Ignored by surveys. */
8165
+ criteriaGroup?: string;
8166
+ }
8167
+ /** A full form definition — the top-level `[definition]` binding. */
8168
+ interface FlyFormDefinition {
8169
+ title: string;
8170
+ description?: string;
8171
+ declaration?: string;
8172
+ questions: FlyFormQuestion[];
8173
+ }
8174
+ /** How an opted-in `[showScore]` host renders a choice option's points. */
8175
+ type FlyFormScoreDisplay = 'inline' | 'badge';
8176
+ /**
8177
+ * The polymorphic answer payload for one field. Only the field(s) relevant to the
8178
+ * type are populated:
8179
+ * - text: Text / MultilineText / Email
8180
+ * - number: Number
8181
+ * - dateValue: Date / DateTime (raw `<input>` value string)
8182
+ * - boolValue: Boolean
8183
+ * - selectedOptionIds: SingleSelect / MultipleSelect / RadioButton / Country
8184
+ * - fileIds: File / MultiFile / Image (filled by the host after upload; this
8185
+ * component emits it empty and surfaces the raw selection via `fileSelected`)
8186
+ */
8187
+ interface FlyFormAnswerValue {
8188
+ text?: string;
8189
+ number?: number;
8190
+ dateValue?: string;
8191
+ boolValue?: boolean;
8192
+ selectedOptionIds?: string[];
8193
+ fileIds?: string[];
8194
+ }
8195
+ /** One field's answer — the unit emitted by `answersChange` / `submit`. */
8196
+ interface FlyFormAnswer {
8197
+ questionId: string;
8198
+ value: FlyFormAnswerValue;
8199
+ }
8200
+ /**
8201
+ * Raw file selection surfaced by File / MultiFile / Image fields. The host wires
8202
+ * this to its own upload API and, once ids are known, feeds them back via the
8203
+ * `[answers]` prefill (`fileIds`). The DS component never touches an upload endpoint.
8204
+ */
8205
+ interface FlyFileSelection {
8206
+ questionId: string;
8207
+ files: File[];
7795
8208
  }
7796
8209
 
8210
+ /** A render segment: either an ungrouped field (`group === null`) or a titled `criteriaGroup` section.
8211
+ * `index` is the field's running position across the whole form (drives unique element ids). */
8212
+ interface FlyFormFieldGroup {
8213
+ group: string | null;
8214
+ items: {
8215
+ q: FlyFormQuestion;
8216
+ index: number;
8217
+ }[];
8218
+ }
7797
8219
  /**
7798
- * Singleton registry of chip-renderer components and keyboard-alternative draggable
7799
- * items, keyed by `kind` and `appId`.
8220
+ * **`fly-dynamic-form`** the design-system data-contract-driven form renderer.
7800
8221
  *
7801
- * Like {@link AgentCommandRegistry}, this lives in the DS so it crosses the federation
7802
- * boundary as a single instance. Hosts (the shell `<fly-agent-input>`) lookup
7803
- * renderers; remotes register them.
8222
+ * Renders a {@link FlyFormDefinition} (14 field types) into an accessible,
8223
+ * theme-aware form, owning all the fiddly behaviour a form needs: conditional
8224
+ * **branching** (options reveal branch-target fields), per-visible-field
8225
+ * **required validation**, a **declaration** acceptance gate, and a **readonly**
8226
+ * replay mode. It is a platform surface — zero coupling to any specific app, and
8227
+ * (deliberately) no coupling to an upload API: File / Image / MultiFile fields
8228
+ * surface the raw selection via {@link fileSelected} and emit `fileIds` empty; the
8229
+ * host wires the upload and feeds ids back through the `[answers]` prefill.
7804
8230
  *
7805
- * Renderer lookup is `O(n)` over the registered list the registry is small (one or
7806
- * two entries per app) and lookups happen on drop, not per frame.
8231
+ * The historical **`fly-survey-form`** selector is retained on this same component
8232
+ * (multi-selector), so every existing `<fly-survey-form>` binding keeps working
8233
+ * unchanged. Surveys never sets {@link showScore}, so its behaviour (never rendering
8234
+ * `option.score`) is identical. A scorecard host opts into score display with
8235
+ * `[showScore]="true"`.
7807
8236
  *
7808
- * Draggable storage is per-`appId` writable signal cached in a Map, so each `appId`
7809
- * gets a stable {@link Signal} reference across reads (callers can `===`-compare).
8237
+ * i18n is self-sufficient through the `form.*` keys in `DS_BASELINE_LOCALES`
8238
+ * (en/ar/fr/ur), overridable by any consumer key of the same name. RTL works via
8239
+ * logical CSS. Styling uses DS theme tokens (`--surface-*`, `--label-*`,
8240
+ * `--separator`, `--fill-*`, `--accent`, `--system-*`) with light-neutral
8241
+ * fallbacks so it stays readable even in a consumer that hasn't mapped them.
8242
+ *
8243
+ * @example
8244
+ * ```html
8245
+ * <fly-dynamic-form
8246
+ * [definition]="def"
8247
+ * [answers]="prefill"
8248
+ * [showScore]="true"
8249
+ * (answersChange)="draft = $event"
8250
+ * (validityChange)="valid = $event"
8251
+ * (fileSelected)="upload($event)"
8252
+ * (submit)="persist($event)" />
8253
+ * ```
7810
8254
  */
7811
- declare class AgentDropRegistry {
7812
- private readonly _renderers;
7813
- /** Per-appId writable store for draggables. Read-only mirror returned to callers. */
7814
- private readonly _draggablesByApp;
8255
+ declare class FlyDynamicFormComponent implements OnInit {
8256
+ private readonly _i18n;
8257
+ /** The form to render. */
8258
+ readonly definition: _angular_core.InputSignal<FlyFormDefinition>;
8259
+ /** Replay mode — every control is disabled and no validation errors / submit show.
8260
+ * Aliased to `[readonly]` (the public binding); the class member avoids shadowing
8261
+ * the TypeScript `readonly` keyword. */
8262
+ readonly isReadonly: _angular_core.InputSignal<boolean>;
8263
+ /** Hides the submit button while leaving every control interactive — the author's
8264
+ * pre-publish PREVIEW, which must let you open dropdowns and try the form out but has
8265
+ * nothing to submit to. Distinct from `readonly`, which disables the controls outright
8266
+ * (a submission replay). A host that hides submit should not bind `(submit)`. */
8267
+ readonly hideSubmit: _angular_core.InputSignal<boolean>;
8268
+ /** Prefill answers (e.g. a saved draft or a completed response). Answers for
8269
+ * currently-hidden branch targets are ignored. */
8270
+ readonly answers: _angular_core.InputSignal<readonly FlyFormAnswer[]>;
8271
+ /** Additive, opt-in: render each choice option's `score` (a weighted scorecard).
8272
+ * Defaults false so the respondent-facing survey path never leaks the answer key. */
8273
+ readonly showScore: _angular_core.InputSignal<boolean>;
8274
+ /** How an opted-in score is rendered next to an option (`badge` chip or `inline` text). */
8275
+ readonly scoreDisplay: _angular_core.InputSignal<FlyFormScoreDisplay>;
8276
+ /** The full visible answer set on every change. */
8277
+ readonly answersChange: _angular_core.OutputEmitterRef<FlyFormAnswer[]>;
8278
+ /** Required-field validity on every change (does NOT include the declaration gate). */
8279
+ readonly validityChange: _angular_core.OutputEmitterRef<boolean>;
8280
+ /** Fires only when the form is valid AND (if a declaration is set) accepted. */
8281
+ readonly submit: _angular_core.OutputEmitterRef<FlyFormAnswer[]>;
8282
+ /** Raw file selection for a File / MultiFile / Image field — the host uploads
8283
+ * and feeds the resulting ids back via `[answers]`. */
8284
+ readonly fileSelected: _angular_core.OutputEmitterRef<FlyFileSelection>;
8285
+ private readonly _base;
8286
+ /** questionId → answer value. Only ever contains VISIBLE, non-empty answers. */
8287
+ private readonly _values;
8288
+ /** questionId → locally-picked File[] (transient, pre-upload). Never emitted as ids. */
8289
+ private readonly _fileSelections;
8290
+ private readonly _declarationAccepted;
8291
+ private readonly _submitAttempted;
8292
+ /** questionIds the user has interacted with — gates when a required error shows. */
8293
+ private readonly _interacted;
8294
+ /** Per-field local file-validation error message (already localized). */
8295
+ private readonly _fileErrors;
8296
+ private readonly _countryOptions;
8297
+ constructor();
8298
+ ngOnInit(): void;
8299
+ /** The set of field ids currently shown (base fields + revealed branch targets). */
8300
+ readonly visibleQuestionIds: _angular_core.Signal<Set<string>>;
8301
+ /** Definition fields filtered to the visible set, in definition order. */
8302
+ readonly visibleQuestions: _angular_core.Signal<FlyFormQuestion[]>;
7815
8303
  /**
7816
- * Look up the chip-renderer component class for a `kind`. The newest registration for
7817
- * a given `kind` wins, regardless of `appId` we scan the list in reverse so a later
7818
- * `register` call shadows an earlier one for the same `kind`.
7819
- *
7820
- * Returns `null` when no renderer is registered; the host falls back to a generic
7821
- * `plainTextFallback` chip.
8304
+ * The visible fields laid out in render order, segmented by `criteriaGroup`: fields that share a
8305
+ * (non-empty) group render together inside one titled section (a scorecard's weighted-criteria
8306
+ * grouping); a field with no `criteriaGroup` is its own ungrouped segment and renders flat, exactly
8307
+ * as before. Each item keeps its running index across the whole form so element ids stay unique.
7822
8308
  */
7823
- rendererFor(kind: string): Type<AgentChipHostInputs> | null;
8309
+ readonly groupedQuestions: _angular_core.Signal<FlyFormFieldGroup[]>;
8310
+ readonly declarationRequired: _angular_core.Signal<boolean>;
8311
+ /** Required-field validity (declaration NOT included — see {@link canSubmit}). */
8312
+ readonly isValid: _angular_core.Signal<boolean>;
8313
+ /** Whether `(submit)` may fire: valid required set AND declaration accepted (if any). */
8314
+ readonly canSubmit: _angular_core.Signal<boolean>;
8315
+ readonly declarationAccepted: _angular_core.Signal<boolean>;
8316
+ fieldId(index: number): string;
8317
+ descId(index: number): string;
8318
+ errId(index: number): string;
8319
+ describedBy(q: FlyFormQuestion, index: number): string | null;
8320
+ isChoice(type: FlyFormQuestionType): boolean;
8321
+ isFile(type: FlyFormQuestionType): boolean;
8322
+ /** Types rendered as a native input group inside `<fieldset><legend>`. */
8323
+ isFieldset(type: FlyFormQuestionType): boolean;
8324
+ isCountry(type: FlyFormQuestionType): boolean;
7824
8325
  /**
7825
- * Register a renderer for a `(kind, appId)` pair. Re-registering the same pair
7826
- * replaces the prior entry; the disposal handle for the prior registration becomes
7827
- * a no-op.
8326
+ * Types rendered with the DS `<fly-select>` (labelled via `ariaLabel`).
7828
8327
  *
7829
- * Returns a {@link AgentCommandHandle} (re-used to keep the disposable shape uniform
7830
- * across registries) whose `dispose()` removes this exact registration.
8328
+ * Every select-like type now qualifies. `hasSearch` used to gate the CONTROL, which left a plain
8329
+ * SingleSelect on a native `<select>` an OS-chrome popup that cannot be themed (it paints its
8330
+ * rows with the control's translucent background over a white backing, so the list rendered
8331
+ * white-on-white on the dark shell). `hasSearch` now only decides whether the DS panel carries a
8332
+ * search box; see {@link selectSearchable}.
7831
8333
  */
7832
- register<T = unknown>(reg: AgentDropRendererRegistration<T>): AgentCommandHandle;
8334
+ usesDsSelect(q: FlyFormQuestion): boolean;
8335
+ /** Search box: always for Country (the list is ~200 long), else only when the author asked. */
8336
+ selectSearchable(q: FlyFormQuestion): boolean;
7833
8337
  /**
7834
- * Apps publish their live "draggable from focused window" set so the keyboard
7835
- * "Attach from app…" menu can offer them. Hosts call this each time the user-visible
7836
- * draggable list changes; passing an empty array clears the entry for this `appId`.
8338
+ * The points to show for a choice option, or null to render nothing. Non-null only when the host
8339
+ * opted in via `[showScore]` and the option actually carries a score — this is the sole gate that
8340
+ * keeps a respondent-facing survey (which never binds `showScore`) from leaking the answer key.
7837
8341
  */
7838
- publishDraggables(appId: string, items: readonly AgentDraggableItem[]): void;
8342
+ optionScore(opt: FlyFormOption): number | null;
8343
+ /** Whether a required error should be visible for a field right now. */
8344
+ showError(q: FlyFormQuestion): boolean;
8345
+ fileError(q: FlyFormQuestion): string | null;
8346
+ textValue(q: FlyFormQuestion): string;
8347
+ numberValue(q: FlyFormQuestion): number | null;
8348
+ dateValue(q: FlyFormQuestion): string;
8349
+ boolValue(q: FlyFormQuestion): boolean | null;
8350
+ singleValue(q: FlyFormQuestion): string | null;
8351
+ isSelected(q: FlyFormQuestion, optionId: string): boolean;
8352
+ countryOptions(q: FlyFormQuestion): readonly FlySelectOption[];
8353
+ selectOptions(q: FlyFormQuestion): readonly FlySelectOption[];
8354
+ fileNames(q: FlyFormQuestion): string[];
8355
+ acceptFor(q: FlyFormQuestion): string;
8356
+ onText(q: FlyFormQuestion, value: string): void;
8357
+ onNumber(q: FlyFormQuestion, value: string): void;
8358
+ onDate(q: FlyFormQuestion, value: string): void;
8359
+ onBoolean(q: FlyFormQuestion, value: boolean): void;
8360
+ onSingleSelect(q: FlyFormQuestion, value: string | readonly string[] | null): void;
8361
+ onRadio(q: FlyFormQuestion, optionId: string): void;
8362
+ onMultiToggle(q: FlyFormQuestion, optionId: string, checked: boolean): void;
8363
+ onFiles(q: FlyFormQuestion, input: HTMLInputElement): void;
8364
+ onDeclarationToggle(checked: boolean): void;
8365
+ onSubmit(): void;
8366
+ private _touch;
8367
+ private _setFileError;
8368
+ private _mutate;
8369
+ private _emit;
8370
+ private _collectAnswers;
8371
+ private _isAnswered;
8372
+ private _isEmptyValue;
8373
+ private _selectedIds;
7839
8374
  /**
7840
- * Reactive read of the draggable set published for `appId`. Empty when none. The
7841
- * returned signal is stable across calls (cached by `appId`), so consumers can use
7842
- * it as a stable input to `computed()`.
8375
+ * The set of visible field ids for a given answer map. Base (non-branch-target)
8376
+ * fields are always visible; a branch target becomes visible when a
8377
+ * currently-visible field has a selected option that links to it. Computed to a
8378
+ * fixpoint so chained reveals work, and cycle-safe: the visible set only grows and
8379
+ * iteration is capped at `questions.length` passes, so an A↔B link cycle terminates.
8380
+ *
8381
+ * This is the TypeScript twin of the backend Fly.Sdk.Forms `BranchResolver`
8382
+ * fixpoint — the two must stay in lockstep so server + client agree on which fields
8383
+ * are active.
7843
8384
  */
7844
- draggablesFor(appId: string): Signal<readonly AgentDraggableItem[]>;
7845
- /** Lazy-init the per-appId writable bucket. Returns the writable handle for internal use. */
7846
- private bucketFor;
7847
- private _generation;
7848
- private readonly _owners;
7849
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentDropRegistry, never>;
7850
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentDropRegistry>;
8385
+ private _computeVisibleIds;
8386
+ /** Imperative localization for file-validation messages (the pipe covers view strings). */
8387
+ private _t;
8388
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyDynamicFormComponent, never>;
8389
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyDynamicFormComponent, "fly-dynamic-form, fly-survey-form", never, { "definition": { "alias": "definition"; "required": true; "isSignal": true; }; "isReadonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "hideSubmit": { "alias": "hideSubmit"; "required": false; "isSignal": true; }; "answers": { "alias": "answers"; "required": false; "isSignal": true; }; "showScore": { "alias": "showScore"; "required": false; "isSignal": true; }; "scoreDisplay": { "alias": "scoreDisplay"; "required": false; "isSignal": true; }; }, { "answersChange": "answersChange"; "validityChange": "validityChange"; "submit": "submit"; "fileSelected": "fileSelected"; }, never, never, true, never>;
7851
8390
  }
7852
8391
 
7853
- /**
7854
- * Imperative action published by an app, consumed by the agent panel.
7855
- *
7856
- * Re-uses {@link AgentDragPayload} as the wire envelope so a dragged item
7857
- * and a programmatic "Explain" click are byte-for-byte the same shape —
7858
- * renderers, validators, and serialisation paths never fork on transport.
7859
- *
7860
- * Adding a verb is a minor DS bump (consumers ignore unknown verbs in
7861
- * their `switch`). Removing one is a major DS bump.
7862
- */
7863
- type AgentActionVerb = 'explain' | 'why-empty' | 'compose-query' | 'compare' | 'summarize';
7864
- /**
7865
- * Whether the agent panel sends the staged payload immediately or stages
7866
- * the chip for the user to edit and send manually.
7867
- *
7868
- * Phase 1 (DS v2.6.0) supports `'stage'` only. Dispatching with `'auto'`
7869
- * throws {@link AgentActionUnsupportedDispatchError} so callers don't
7870
- * silently fail. `'auto'` lands once `AgentInputComponent.programmaticSubmit`
7871
- * is exposed and reviewed against the input's state machine.
7872
- */
7873
- type AgentActionDispatch = 'auto' | 'stage';
7874
- interface AgentAction<T = unknown> {
7875
- /** Intent the agent should apply to {@link payload}. */
7876
- readonly verb: AgentActionVerb;
7877
- /** The wire envelope. Validated against {@link validateAgentPayload}'s
7878
- * size caps before the bus fans it out. */
7879
- readonly payload: AgentDragPayload<T>;
7880
- /** Optional slash command id to bind before send (e.g. `'explain-report'`).
7881
- * Phase 1 captures this for telemetry only — actual binding lands when
7882
- * the input's programmatic-send API ships. An unknown id is dropped
7883
- * silently with a console warning, the chip still arrives. */
7884
- readonly autoCommandId?: string;
7885
- /** Phase 1 supports `'stage'` only. See {@link AgentActionDispatch}. */
7886
- readonly dispatch: AgentActionDispatch;
7887
- /** Source DOM rect for the FLIP entry animation. Omit to skip the
7888
- * animation (e.g. dispatching from a keyboard shortcut with no anchor). */
7889
- readonly originRect?: DOMRect;
7890
- /** Optional HTML snippet rendered inside the flight ghost. Callers are
7891
- * responsible for escaping untrusted text — the bus does not sanitise.
7892
- * Defaults (when omitted) to a strong-wrapped escape of the payload's
7893
- * `plainTextFallback` rendered by the panel host. */
7894
- readonly originPreviewHtml?: string;
8392
+ interface FlyCountry {
8393
+ /** ISO 3166-1 alpha-2 code (uppercase), e.g. `AE`. */
8394
+ code: string;
8395
+ /** English display name. */
8396
+ name: string;
8397
+ }
8398
+ /** Frozen alpha-2 list, alphabetical by English name. */
8399
+ declare const FLY_COUNTRIES: readonly FlyCountry[];
8400
+
8401
+ /** Backend `CommentReportReason` enum — string-persisted, member names frozen. */
8402
+ type FlyCommentReportReason = 'Spam' | 'Harassment' | 'OffTopic' | 'Misinformation' | 'Inappropriate' | 'Other';
8403
+ /** Backend `CommentReportStatus` enum — string-persisted, member names frozen. */
8404
+ type FlyCommentReportStatus = 'Open' | 'UnderReview' | 'ActionTaken' | 'Dismissed';
8405
+ /**
8406
+ * One comment, at any depth. `isDeleted` comments render as a tombstone (the
8407
+ * host still supplies `body`-less structural fields so the reply subtree stays
8408
+ * navigable; the component never assumes `body` is present when `isDeleted`).
8409
+ * `canEdit` / `canDelete` / `canModerate` are pre-resolved by the host (Cerbos
8410
+ * decision or equivalent) the component never evaluates authorization itself.
8411
+ */
8412
+ interface FlyComment {
8413
+ id: string;
8414
+ authorUserId: string;
8415
+ authorDisplayName?: string;
8416
+ authorAvatarUrl?: string;
8417
+ parentCommentId?: string;
8418
+ depth: number;
8419
+ body: string;
8420
+ replyCount: number;
8421
+ isEdited: boolean;
8422
+ isDeleted: boolean;
8423
+ createdAt: string;
8424
+ updatedAt?: string;
8425
+ canEdit: boolean;
8426
+ canDelete: boolean;
8427
+ canModerate: boolean;
7895
8428
  }
7896
8429
  /**
7897
- * Thrown synchronously by {@link AgentActionBus.dispatch} when a caller
7898
- * supplies a dispatch mode this DS version doesn't implement yet. Catching
7899
- * by class name lets a forward-compatible caller fall back to `'stage'`
7900
- * without depending on instanceof across federation boundaries.
8430
+ * A paged slice of comments at one nesting level — mirrors the service's
8431
+ * `GET .../comments?parentCommentId=&page=&pageSize=` shape. Every depth is
8432
+ * paged independently: the root page and each expanded parent's reply page are
8433
+ * separate {@link FlyCommentPage} instances tracked by the host.
7901
8434
  */
7902
- declare class AgentActionUnsupportedDispatchError extends Error {
7903
- readonly dispatch: AgentActionDispatch;
7904
- constructor(dispatch: AgentActionDispatch);
8435
+ interface FlyCommentPage {
8436
+ items: FlyComment[];
8437
+ page: number;
8438
+ pageSize: number;
8439
+ total: number;
8440
+ hasMore: boolean;
8441
+ }
8442
+ /** `loadReplies` request — expand a collapsed reply subtree, one page at a time. */
8443
+ interface FlyCommentLoadRepliesRequest {
8444
+ parentCommentId: string;
8445
+ page: number;
8446
+ }
8447
+ /** `loadMore` request — next page at the root level (`parentCommentId` omitted) or under a parent. */
8448
+ interface FlyCommentLoadMoreRequest {
8449
+ parentCommentId?: string;
8450
+ page: number;
8451
+ }
8452
+ /** `submit` request — a new root comment (`parentCommentId` omitted) or a reply. */
8453
+ interface FlyCommentSubmitRequest {
8454
+ body: string;
8455
+ parentCommentId?: string;
8456
+ }
8457
+ /** `edit` request — author-only body edit of an existing comment. */
8458
+ interface FlyCommentEditRequest {
8459
+ commentId: string;
8460
+ body: string;
8461
+ }
8462
+ /** `delete` request — soft delete (tombstone), author or moderator. */
8463
+ interface FlyCommentDeleteRequest {
8464
+ commentId: string;
8465
+ }
8466
+ /** `report` request — typed reason + optional free-text detail. */
8467
+ interface FlyCommentReportRequest {
8468
+ commentId: string;
8469
+ reason: FlyCommentReportReason;
8470
+ detail?: string;
8471
+ }
8472
+ /** `lockToggle` request — moderator lock/unlock of the whole thread. */
8473
+ interface FlyCommentLockToggleRequest {
8474
+ locked: boolean;
7905
8475
  }
7906
8476
 
7907
8477
  /**
7908
- * Imperative sibling to {@link AgentCommandRegistry} / {@link AgentDropRegistry}.
8478
+ * **`fly-comment-thread`** the design-system generic threaded-comments renderer.
7909
8479
  *
7910
- * Apps call {@link dispatch} to push a typed {@link AgentAction} onto the bus;
7911
- * the agent panel subscribes once at construct and routes by verb. The bus
7912
- * itself is a thin pass-through it does NOT decide UI behaviour. The
7913
- * subscriber (agent-panel) owns: showing the panel, staging the chip,
7914
- * triggering the flight animation, and binding the command. This keeps the
7915
- * DS free of host policy.
8480
+ * Renders a {@link FlyCommentPage} (root comments) plus lazily-fetched nested
8481
+ * reply pages into an accessible, theme-aware thread: reply composer, inline
8482
+ * edit, soft-delete (tombstone render), typed report-with-reason dialog, and
8483
+ * moderator affordances (lock/unlock, delete-any). It is a platform surface
8484
+ * transport-agnostic like `fly-survey-form` / `fly-dynamic-form`: this component
8485
+ * never calls an API. It only renders the pages the host hands it via
8486
+ * `[rootPage]` / `[repliesByParent]` and emits typed requests
8487
+ * (`loadReplies`/`loadMore`/`submit`/`edit`/`delete`/`report`/`lockToggle`) —
8488
+ * the host owns HTTP, optimistic-insert reconciliation, and any live-update
8489
+ * channel (SignalR, polling, …).
7916
8490
  *
7917
- * Federation-safe: `providedIn: 'root'` + `sharedMappings: ['@flyos/design-system']`
7918
- * give every federated remote the same singleton, so a remote's "Explain"
7919
- * button reaches the host's panel without any cross-bundle wiring.
8491
+ * Nested replies are fetched **lazily on expand** (`loadReplies`, never an
8492
+ * eager load-all) the explicit fix for the legacy unpaged-children scale bug
8493
+ * and each level pages independently via `loadMore`.
7920
8494
  *
7921
- * Validation runs synchronously inside `dispatch` so a caller that sends an
7922
- * oversize payload sees the throw at their site, not on the subscriber. The
7923
- * subscriber therefore never has to defend against malformed envelopes.
8495
+ * The `comments` core app owns no user profile data, so `FlyComment` carries
8496
+ * `authorUserId` and an OPTIONAL `authorDisplayName`. Rather than make every host
8497
+ * map names in itself (and render GUIDs when it forgets), bind
8498
+ * `[resolveDisplayName]` to `FlyUserDirectoryService.label` — see
8499
+ * {@link FlyCommentThreadComponent.resolveDisplayName}. Still transport-agnostic:
8500
+ * the resolver is a function the host hands over, not a call this component makes.
8501
+ *
8502
+ * i18n is self-sufficient through the `comment.*` keys in `DS_BASELINE_LOCALES`
8503
+ * (en/ar/fr/ur), overridable by any consumer key of the same name. RTL works via
8504
+ * logical CSS. Styling uses DS theme tokens (`--surface-*`, `--label-*`,
8505
+ * `--separator`, `--fill-*`, `--accent`, `--system-*`) with light-neutral
8506
+ * fallbacks so it stays readable even in a consumer that hasn't mapped them.
8507
+ *
8508
+ * @example
8509
+ * ```html
8510
+ * <fly-comment-thread
8511
+ * [rootPage]="rootPage()"
8512
+ * [repliesByParent]="repliesByParent()"
8513
+ * [currentUserId]="me.id"
8514
+ * [resolveDisplayName]="directory.label"
8515
+ * [canModerateThread]="canModerate()"
8516
+ * [isLocked]="thread().isLocked"
8517
+ * (loadReplies)="onLoadReplies($event)"
8518
+ * (loadMore)="onLoadMore($event)"
8519
+ * (submit)="onSubmit($event)"
8520
+ * (edit)="onEdit($event)"
8521
+ * (delete)="onDelete($event)"
8522
+ * (report)="onReport($event)"
8523
+ * (lockToggle)="onLockToggle($event)" />
8524
+ * ```
7924
8525
  */
7925
- declare class AgentActionBus {
7926
- private readonly _actions$;
7927
- /** Hot stream of actions in dispatch order. Subscribers receive only
7928
- * actions dispatched AFTER they subscribe — late subscribers see nothing
7929
- * retroactively. Use {@link lastAction} for the latest snapshot. */
7930
- readonly actions$: Observable<AgentAction>;
7931
- /** Most recent action — for DevTools, smoke tests, and late-subscriber
7932
- * catch-up. Null until the first successful dispatch. */
7933
- readonly lastAction: _angular_core.WritableSignal<AgentAction<unknown> | null>;
8526
+ declare class FlyCommentThreadComponent {
8527
+ /** The root-level comment page (accumulated across `loadMore` calls by the host). */
8528
+ readonly rootPage: _angular_core.InputSignal<FlyCommentPage>;
7934
8529
  /**
7935
- * The action currently being processed by the subscriber, or null when
7936
- * none. Set by {@link dispatch} immediately before emitting on
7937
- * {@link actions$}; cleared by the subscriber via {@link settle} once
7938
- * it finishes its handler (success or fail). Lets the dispatcher render
7939
- * a busy state on the originating control — e.g. a card swapping its
7940
- * sparkle icon for a spinner while the agent panel mints the optimistic
7941
- * thread and starts the request. Identity check (`bus.inFlight() === act`)
7942
- * is the panel-side contract; dispatchers usually project to a stable id
7943
- * inside the payload (e.g. <c>reportId</c>) to scope busy-state visually.
7944
- *
7945
- * If multiple dispatches race, the latest wins — the prior in-flight
7946
- * action is dropped on the floor here (the panel may still handle it,
7947
- * but the dispatcher's busy indicator follows the newer action). Apps
7948
- * that need stricter single-flight semantics should guard at the call
7949
- * site (the agent-panel's <c>_pendingTempThreadId</c> already does so
7950
- * for the explain verb).
8530
+ * Accumulated reply page per expanded parent (keyed by `parentCommentId`), also
8531
+ * accumulated across `loadMore` calls by the host. Additive to the frozen
8532
+ * contract's `rootPage` input required because a lazily-nested tree has no
8533
+ * other channel to carry fetched reply data back into the component.
7951
8534
  */
7952
- readonly inFlight: _angular_core.WritableSignal<AgentAction<unknown> | null>;
8535
+ readonly repliesByParent: _angular_core.InputSignal<Readonly<Record<string, FlyCommentPage>>>;
8536
+ /** Current sort order label (host owns re-fetch on {@link sortChange}; purely descriptive here otherwise). */
8537
+ readonly sort: _angular_core.InputSignal<"newest" | "oldest">;
8538
+ /** Locked threads disable the composer (root + reply) entirely, for every user. */
8539
+ readonly isLocked: _angular_core.InputSignal<boolean>;
8540
+ /** The signed-in user's id — gates the "report" affordance (can't report your own comment). */
8541
+ readonly currentUserId: _angular_core.InputSignal<string | null>;
8542
+ /** Replay / read-only mode — hides composer, reply, edit, delete, report, and lock affordances.
8543
+ * Aliased to `[readonly]` (the public binding); the class member avoids shadowing the
8544
+ * TypeScript `readonly` keyword. */
8545
+ readonly isReadonly: _angular_core.InputSignal<boolean>;
8546
+ /** Thread-scoped moderator permission (lock/unlock). Additive — distinct from the
8547
+ * per-comment `canModerate` flag, which gates delete-any on that specific comment. */
8548
+ readonly canModerateThread: _angular_core.InputSignal<boolean>;
7953
8549
  /**
7954
- * Push an action onto the bus.
8550
+ * Optional id→name lookup, for the common case where the backend DTO carries only
8551
+ * `authorUserId`. The `comments` core app owns no user profile data, so without
8552
+ * this every host had to map display names into `authorDisplayName` itself — and
8553
+ * a host that forgot rendered raw GUIDs at the reader.
7955
8554
  *
7956
- * Throws synchronously when:
7957
- * - `dispatch === 'auto'` (not implemented in this DS version) see
7958
- * {@link AgentActionUnsupportedDispatchError}.
7959
- * - the payload fails {@link validateAgentPayload} (oversize, invalid
7960
- * version, invalid kind). The error message carries the field path
7961
- * so the caller can fix the offending field.
8555
+ * Bind `FlyUserDirectoryService.label` (a signal-backed read-through cache that
8556
+ * resolves on demand and batches). Because the component reads it during render,
8557
+ * the name appears by itself once the lookup lands — the host keeps a plain
8558
+ * page signal, with no `computed` derivation and no priming.
7962
8559
  *
7963
- * Subscribers see the action via {@link actions$} on the next tick of
7964
- * the Subject; the {@link lastAction} signal updates synchronously
7965
- * before the Subject emits so an effect reading both stays consistent.
7966
- */
7967
- dispatch<T>(action: AgentAction<T>): void;
7968
- /**
7969
- * Subscriber contract: call after the handler for {@link inFlight}
7970
- * completes (success or fail). Only clears {@link inFlight} if it still
7971
- * points at the passed action — a no-op when a later dispatch already
7972
- * superseded it. Pass the same action reference the subscriber received
7973
- * from {@link actions$}; identity is the gate.
8560
+ * The component still never calls an API: this is a function the HOST supplies,
8561
+ * exactly like `fly-entity-lookup`'s `LOOKUP_APP_NAME_RESOLVER`. Absent (or
8562
+ * returning `null`), rendering falls back as before. See {@link authorLabel} for
8563
+ * the resolution order.
7974
8564
  */
7975
- settle(action: AgentAction): void;
8565
+ readonly resolveDisplayName: _angular_core.InputSignal<((userId: string) => string | null) | null>;
8566
+ /** Expand a collapsed reply subtree — always page 1 of that parent. */
8567
+ readonly loadReplies: _angular_core.OutputEmitterRef<{
8568
+ parentCommentId: string;
8569
+ page: number;
8570
+ }>;
8571
+ /** Fetch the next page at a level: root when `parentCommentId` is omitted, else that parent's replies. */
8572
+ readonly loadMore: _angular_core.OutputEmitterRef<{
8573
+ parentCommentId?: string;
8574
+ page: number;
8575
+ }>;
8576
+ /** A new root comment (`parentCommentId` omitted) or reply. */
8577
+ readonly submit: _angular_core.OutputEmitterRef<{
8578
+ body: string;
8579
+ parentCommentId?: string;
8580
+ }>;
8581
+ /** Author-only body edit of an existing comment. */
8582
+ readonly edit: _angular_core.OutputEmitterRef<{
8583
+ commentId: string;
8584
+ body: string;
8585
+ }>;
8586
+ /** Soft delete (tombstone) — author's own comment, or any comment for a moderator. */
8587
+ readonly delete: _angular_core.OutputEmitterRef<{
8588
+ commentId: string;
8589
+ }>;
8590
+ /** Typed report submission. */
8591
+ readonly report: _angular_core.OutputEmitterRef<{
8592
+ commentId: string;
8593
+ reason: FlyCommentReportReason;
8594
+ detail?: string;
8595
+ }>;
8596
+ /** Moderator lock/unlock of the whole thread. */
8597
+ readonly lockToggle: _angular_core.OutputEmitterRef<{
8598
+ locked: boolean;
8599
+ }>;
8600
+ /** Additive: a sort-order pick from the header control — the host re-fetches page 1 in that order. */
8601
+ readonly sortChange: _angular_core.OutputEmitterRef<"newest" | "oldest">;
8602
+ private readonly _base;
8603
+ readonly reportReasons: readonly FlyCommentReportReason[];
8604
+ /** commentIds whose reply subtree is currently expanded/visible. */
8605
+ private readonly _expanded;
8606
+ /** Composer draft text, keyed by parentCommentId (root uses {@link ROOT_DRAFT_KEY}). */
8607
+ private readonly _drafts;
8608
+ /** commentIds whose reply composer box is currently open (distinct from `_expanded`, which toggles the read view). */
8609
+ private readonly _replyBoxOpen;
8610
+ private readonly _editingId;
8611
+ private readonly _editDraft;
8612
+ private readonly _confirmingDeleteId;
8613
+ private readonly _reportingId;
8614
+ private readonly _reportReason;
8615
+ private readonly _reportDetail;
8616
+ readonly rootDraft: _angular_core.Signal<string>;
8617
+ readonly editingId: _angular_core.Signal<string | null>;
8618
+ readonly confirmingDeleteId: _angular_core.Signal<string | null>;
8619
+ readonly reportingId: _angular_core.Signal<string | null>;
8620
+ readonly reportReason: _angular_core.Signal<FlyCommentReportReason | null>;
8621
+ readonly reportDetail: _angular_core.Signal<string>;
8622
+ fieldId(suffix: string): string;
8623
+ isExpanded(commentId: string): boolean;
8624
+ isReplyBoxOpen(commentId: string): boolean;
8625
+ repliesFor(commentId: string): FlyCommentPage | undefined;
8626
+ replyDraft(commentId: string): string;
8627
+ isOwn(comment: FlyComment): boolean;
7976
8628
  /**
7977
- * Semantic alias of {@link settle} for explicit user-driven cancellation
7978
- * e.g. a future "Stop" button in the agent input tray, or a dispatcher
7979
- * teardown that wants to abandon its own in-flight action. Identical
7980
- * runtime behaviour (identity check + clear), but the two-method surface
7981
- * lets the UI distinguish "handler finished" from "user said no" in
7982
- * telemetry / logs without sniffing a "reason" parameter.
8629
+ * The name to render for a comment's author. Resolution order:
8630
+ * explicit `authorDisplayName` (a host that already knows the name, or a
8631
+ * backend that returns one, always wins) {@link resolveDisplayName}
8632
+ * the raw `authorUserId`.
7983
8633
  *
7984
- * Pass the same action reference returned from {@link inFlight} or held
7985
- * by the dispatcher; identity is the gate.
8634
+ * The raw id remains the last resort rather than a placeholder: it is stable,
8635
+ * unique, and lets a reader correlate two comments by the same author even
8636
+ * when the directory is unreachable.
7986
8637
  */
7987
- cancel(action: AgentAction): void;
7988
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentActionBus, never>;
7989
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentActionBus>;
8638
+ authorLabel(comment: FlyComment): string;
8639
+ /** First character of {@link authorLabel}, for the avatar-initials fallback. */
8640
+ authorInitial(comment: FlyComment): string;
8641
+ canReport(comment: FlyComment): boolean;
8642
+ canReply(): boolean;
8643
+ canDeleteComment(comment: FlyComment): boolean;
8644
+ canEditComment(comment: FlyComment): boolean;
8645
+ formatDate(iso: string): string;
8646
+ onRootDraftChange(value: string): void;
8647
+ submitRoot(): void;
8648
+ toggleReplies(comment: FlyComment): void;
8649
+ private _expand;
8650
+ loadMoreRoot(): void;
8651
+ loadMoreReplies(parentId: string): void;
8652
+ openReplyBox(commentId: string): void;
8653
+ closeReplyBox(commentId: string): void;
8654
+ onReplyDraftChange(commentId: string, value: string): void;
8655
+ submitReply(commentId: string): void;
8656
+ startEdit(comment: FlyComment): void;
8657
+ onEditDraftChange(value: string): void;
8658
+ saveEdit(comment: FlyComment): void;
8659
+ cancelEdit(): void;
8660
+ requestDelete(comment: FlyComment): void;
8661
+ confirmDelete(comment: FlyComment): void;
8662
+ cancelDelete(): void;
8663
+ openReport(comment: FlyComment): void;
8664
+ onReportReasonChange(reason: FlyCommentReportReason): void;
8665
+ onReportDetailChange(value: string): void;
8666
+ submitReport(): void;
8667
+ closeReport(): void;
8668
+ /** Space would otherwise scroll the page while the backdrop (role="button") is focused. */
8669
+ onOverlayBackdropSpace(event: Event): void;
8670
+ toggleLock(): void;
8671
+ onSortPick(next: 'newest' | 'oldest'): void;
8672
+ private _setDraft;
8673
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyCommentThreadComponent, never>;
8674
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyCommentThreadComponent, "fly-comment-thread", never, { "rootPage": { "alias": "rootPage"; "required": true; "isSignal": true; }; "repliesByParent": { "alias": "repliesByParent"; "required": false; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; "isLocked": { "alias": "isLocked"; "required": false; "isSignal": true; }; "currentUserId": { "alias": "currentUserId"; "required": false; "isSignal": true; }; "isReadonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "canModerateThread": { "alias": "canModerateThread"; "required": false; "isSignal": true; }; "resolveDisplayName": { "alias": "resolveDisplayName"; "required": false; "isSignal": true; }; }, { "loadReplies": "loadReplies"; "loadMore": "loadMore"; "submit": "submit"; "edit": "edit"; "delete": "delete"; "report": "report"; "lockToggle": "lockToggle"; "sortChange": "sortChange"; }, never, never, true, never>;
7990
8675
  }
7991
8676
 
7992
8677
  /**
7993
- * FLIP-style entry animation for payloads landing in the agent panel.
7994
- *
7995
- * Pure DOM + Web Animations API — no Chart.js, no Angular animations module,
7996
- * no CSS transitions racing layout. Honours `prefers-reduced-motion`: the
7997
- * ghost is appended then removed without animating when the user asked for
7998
- * less motion (so DOM side-effects stay consistent).
7999
- *
8000
- * Lifecycle:
8001
- * 1. The agent panel calls {@link registerTarget} in `ngAfterViewInit`
8002
- * with its header element.
8003
- * 2. A source app dispatches an `AgentAction` carrying an `originRect`
8004
- * from `getBoundingClientRect()` on the click target.
8005
- * 3. The bus subscriber calls {@link flyInto} with that rect.
8006
- * 4. The animator creates a fixed-position ghost at the origin, animates
8007
- * transform + opacity toward the registered target's rect, then
8008
- * removes itself on `onfinish` / `oncancel`.
8009
- *
8010
- * Uses `getBoundingClientRect()` (physical viewport coords) so the animation
8011
- * is RTL-correct without inset-inline math — the rect already encodes the
8012
- * physical position regardless of `dir`.
8013
- *
8014
- * The 900 ms duration and easing curve are deliberately hardcoded — making
8015
- * them configurable surfaces an API the host can't usefully tune without
8016
- * understanding motion design as a whole.
8678
+ * A proof-of-work challenge issued by `Fly.Sdk.Captcha` (`CaptchaChallenge`).
8679
+ * The solver must find `n` in `[0, maxNumber)` such that
8680
+ * `sha256HexLower(salt + n) === challenge`.
8017
8681
  */
8018
- declare class AgentFlightAnimator {
8019
- /** Hardcoded see class doc. */
8020
- private static readonly DURATION_MS;
8021
- private static readonly EASING;
8022
- /** Floor the target/source scale ratio so a tiny target rect doesn't
8023
- * collapse the ghost to invisibility before the animation finishes. */
8024
- private static readonly MIN_SCALE;
8025
- private targetEl;
8026
- /** Called by the panel host to publish where flights should land. Pass
8027
- * `null` on destroy so a re-mounted panel doesn't leave the animator
8028
- * pointing at a detached node. */
8029
- registerTarget(el: HTMLElement | null): void;
8030
- /**
8031
- * Animate a ghost element from {@link from} to the registered target's
8032
- * rect. No-ops when:
8033
- * - no target is registered (silent — panel may not be mounted yet)
8034
- * - running outside a browser (SSR safety)
8035
- * - the user has `prefers-reduced-motion: reduce` set (DOM is still
8036
- * touched so callers see consistent side-effects, but no animation
8037
- * runs)
8038
- *
8039
- * The ghost is appended to `document.body` (not the panel) so a parent
8040
- * `overflow: hidden` on the panel can't clip the flight path.
8041
- */
8042
- flyInto(from: DOMRect, opts?: {
8043
- previewHtml?: string;
8044
- }): void;
8045
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentFlightAnimator, never>;
8046
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentFlightAnimator>;
8682
+ interface FlyCaptchaChallenge {
8683
+ /** Always `"SHA-256"` today. */
8684
+ algorithm: string;
8685
+ /** Lowercase-hex SHA-256 of `salt + number` for the secret solution number. */
8686
+ challenge: string;
8687
+ /** Base64 random salt, unique per challenge. */
8688
+ salt: string;
8689
+ /** HMAC signature binding challenge + maxNumber + expiresAt (server-verified). */
8690
+ signature: string;
8691
+ /** Exclusive upper bound of the search space (difficulty). */
8692
+ maxNumber: number;
8693
+ /** ISO-8601 instant after which the solution is rejected. Echoed back verbatim. */
8694
+ expiresAt: string;
8047
8695
  }
8048
-
8049
8696
  /**
8050
- * Magic-bar contribution contract (UX v2 "Nova", D5 §4).
8051
- *
8052
- * The magic bar is the shell's top-bar toolbar pill. Unlike the fixed right
8053
- * cluster (Help / Tasks / Notifications / assistant orb — shell-owned, an app can
8054
- * never remove it), the pill's contents belong to **whatever view the user is
8055
- * looking at**, and the design's reference implementation re-publishes the whole
8056
- * set on every selection, tab and save (`pubTabActions`). Two consequences shape
8057
- * every type below:
8058
- *
8059
- * 1. **A contribution is a full replace, never a patch.** There is no "add one
8060
- * action" call, because the publisher never knows what the previous view left
8061
- * behind. That also collapses D5's `search: null` ("suppress") vs `search`
8062
- * absent distinction — under full replace both simply mean "this view offers
8063
- * no search".
8064
- * 2. **Render data and behaviour are separated in the type system.** Each family
8065
- * has a `*Spec` base carrying ONLY the fields that affect what is painted;
8066
- * the publisher-facing type extends it with callbacks, and the shell-facing
8067
- * `*View` type extends it with bound invokers. The base IS the equality
8068
- * surface {@link MagicBarRegistry} dedupes on — a re-publish whose specs are
8069
- * unchanged must not disturb the shell, even though its closures are brand
8070
- * new objects every time (an arrow function in a component method never
8071
- * compares equal to its predecessor). See `magic-bar-projection.ts`.
8072
- *
8073
- * **Strings are i18n keys, never resolved text** — same rule as
8074
- * {@link AgentCommandRegistration} (`labelKey` / `descriptionKey` / `hintKey`),
8075
- * for the same two reasons: a contribution outlives a locale switch (a resolved
8076
- * label would silently go stale until the publisher happened to re-publish), and
8077
- * keys are locale-invariant so the dedupe fingerprint doesn't churn when the user
8078
- * changes language. Keys resolve in the OWNING app's bundle.
8079
- *
8080
- * **Every `*Key` has an optional `*Params` sibling** ({@link MagicBarTextParams}),
8081
- * fed straight to `I18nService.t(key, params)`. Without it a count-carrying string
8082
- * ("Search 1,204 tasks…") forces the publisher to pass pre-formatted TEXT as the
8083
- * key: it renders correctly in `en`, renders identically untranslated in ar/fr/ur,
8084
- * and no locale tool can see it. Params participate in the dedupe fingerprint, so
8085
- * a changing count re-paints.
8086
- *
8087
- * Which of D5 §4's "dynamics the shell must honor" live where:
8088
- *
8089
- * | # | Dynamic | Owner |
8090
- * |---|---|---|
8091
- * | 1 | Live re-publication, idempotent replace | {@link MagicBarRegistry} (here) |
8092
- * | 2 | Disabled ≠ hidden, auto disabled-tooltip | contract keeps `disabled` rows; `fly-magic-actions` renders + suffixes |
8093
- * | 3 | `toolbarPopIn` 40 ms stagger | renderer — it tracks on {@link MagicBarActionSpec.id}, which is why ids are unique across the WHOLE contribution |
8094
- * | 4 | i18n on every label/tip | contract (keys + params, above) |
8095
- * | 5 | Fixed right cluster is not contributable | contract — no slot exists for it |
8096
- * | 6 | Mobile renders the same contribution | contract carries no form-factor branch; `openWidth` is a desktop hint mobile ignores |
8097
- * | 7 | `shortcut` reserved (design has no shortcut affordance yet) | contract — carried, unrendered |
8098
- *
8099
- * On dynamic 3 specifically: the registry's dedupe does NOT stop the stagger
8100
- * replaying, and believing it does would produce a renderer that re-animates on
8101
- * every click. Dedupe fires only when *nothing painted* changed, and the dominant
8102
- * case — `disabled: !hasSelection` — changes the fingerprint on every selection.
8103
- * What holds the DOM still is the renderer's `@for (…; track item.id)`.
8697
+ * The solved payload, matching `Fly.Sdk.Captcha.CaptchaSolution` field-for-field
8698
+ * (camelCase Web JSON). Serialized to the opaque base64 token the backend decodes:
8699
+ * `base64(utf8(JSON.stringify(solution)))` i.e. `Convert.ToBase64String` +
8700
+ * `JsonSerializerDefaults.Web`. Every field except `number` is echoed from the
8701
+ * challenge unchanged so the server's stateless signature check passes.
8104
8702
  */
8105
- /** How an action paints. `'text'` is the labelled-pill form (calendar's "Today"). */
8106
- type MagicBarActionKind = 'icon' | 'text' | 'toggle';
8703
+ interface FlyCaptchaSolution {
8704
+ algorithm: string;
8705
+ challenge: string;
8706
+ salt: string;
8707
+ number: number;
8708
+ signature: string;
8709
+ maxNumber: number;
8710
+ expiresAt: string;
8711
+ }
8712
+ /** The solver lifecycle surfaced by the component's `state()` signal. */
8713
+ type FlyCaptchaState = 'idle' | 'verifying' | 'verified' | 'expired' | 'error';
8714
+
8715
+ /** Lowercase-hex SHA-256 of `input` via Web Crypto — matches `Convert.ToHexStringLower(SHA256…)`. */
8716
+ declare function flyCaptchaSha256Hex(input: string): Promise<string>;
8717
+ /** Standard base64 of the UTF-8 bytes of `s` — matches .NET `Convert.ToBase64String`. */
8718
+ declare function flyCaptchaBase64Utf8(s: string): string;
8107
8719
  /**
8108
- * Semantic colour of an action. `'danger'` is the red Delete treatment (red glyph
8109
- * + red 18 % hover tint, D7); `'success'` is the green "Mark as complete" pressed
8110
- * state (D5 §1.4 + §4 prop table: "pressed bg tint-sel / green for complete").
8111
- *
8112
- * A union rather than the boolean `danger` this started as: the design already
8113
- * demands a second colour, and widening a shipped `danger?: boolean` into a union
8114
- * later would mean REMOVING an interface member — MAJOR under `tools/ds-compat`
8115
- * (rule 7), which forks the federation singleton for every remote.
8720
+ * Builds the opaque base64 token the backend's `CaptchaSolution.FromToken` expects.
8721
+ * Exported so a host (or a test) can construct/verify the token shape without the
8722
+ * component.
8116
8723
  */
8117
- type MagicBarTone = 'default' | 'danger' | 'success';
8724
+ declare function flyCaptchaBuildToken(solution: FlyCaptchaSolution): string;
8118
8725
  /**
8119
- * Interpolation params for one i18n key the `params` argument of
8120
- * `I18nService.t(key, params)`, substituting `{{name}}` placeholders.
8726
+ * **`fly-captcha`** the design-system client for the self-hosted proof-of-work
8727
+ * captcha (`Fly.Sdk.Captcha`).
8121
8728
  *
8122
- * Values are `string | number` only: the fingerprint has to encode them, and the
8123
- * renderer resolves them through `t()`, which stringifies. A `Date` or a nested
8124
- * object would fingerprint as `{}` and paint as `[object Object]`.
8125
- */
8126
- type MagicBarTextParams = Readonly<Record<string, string | number>>;
8127
- /**
8128
- * Identity of a contribution's publisher, and the arbitration key the shell uses
8129
- * to pick which contribution the bar renders.
8729
+ * Given a {@link FlyCaptchaChallenge}, it brute-forces `n` in `[0, maxNumber)`
8730
+ * until `sha256HexLower(salt + n) === challenge`, then emits the solution as the
8731
+ * base64 token the backend decodes ({@link flyCaptchaBuildToken}). The search
8732
+ * hashes candidates with a vendored **synchronous** SHA-256 ({@link sha256HexSync}
8733
+ * from `./sha256` — no `crypto.subtle`/promise overhead per candidate, which used
8734
+ * to dominate solve time), in chunks scheduled on `requestAnimationFrame` so a
8735
+ * chunk boundary still yields back to the browser between slices. It auto-starts
8736
+ * whenever the `[challenge]` input changes. Implements
8737
+ * {@link ControlValueAccessor} so it drops into a reactive form / `[(ngModel)]` —
8738
+ * the control value IS the token string.
8130
8739
  *
8131
- * `windowId` is optional but strongly preferred: two windows of the same app can
8132
- * be open on different selections, and keying on `appId` alone would let the
8133
- * background one paint the focused one's toolbar. It is optional only because a
8134
- * federated remote that predates per-window plumbing can still contribute —
8135
- * {@link MagicBarRegistry} resolves an `appId`-keyed slot as a fallback.
8740
+ * Expiry is the host's concern to refresh: the component checks `expiresAt` up
8741
+ * front and while solving, emits {@link expired}, and stops. i18n is
8742
+ * self-sufficient via the `captcha.*` baseline keys; RTL works via logical CSS.
8136
8743
  *
8137
- * It only has to be unique **within the publishing app** (the shell passes its own
8138
- * `win-<appId>-<timestamp>` window id; a tab index or a route-derived id is equally
8139
- * valid) {@link magicBarOwnerKey} scopes it by `appId`, so a value another app
8140
- * also uses can never cross over.
8744
+ * @example
8745
+ * ```html
8746
+ * <fly-captcha [challenge]="challenge()" (solved)="post($event)" (expired)="refresh()" />
8747
+ * ```
8141
8748
  */
8142
- interface MagicBarOwnerRef {
8143
- readonly appId: string;
8144
- readonly windowId?: string;
8749
+ declare class FlyCaptchaComponent implements ControlValueAccessor, OnDestroy {
8750
+ /** The challenge to solve. Setting (or replacing) it auto-starts a fresh solve. */
8751
+ readonly challenge: _angular_core.InputSignal<FlyCaptchaChallenge | null>;
8752
+ /** Candidates hashed synchronously in one slice before yielding back to the browser
8753
+ * on the next animation frame. Higher = fewer `requestAnimationFrame` hops (each
8754
+ * carries ~16ms of frame-boundary overhead), more synchronous work per slice.
8755
+ * 20,000 keeps a slice's own hashing time well under a long-task budget on modern
8756
+ * hardware (the vendored sync SHA-256 does several hundred thousand hashes/sec)
8757
+ * while needing only a handful of hops to clear the library's default difficulty. */
8758
+ readonly chunkSize: _angular_core.InputSignal<number>;
8759
+ /** The solution token once solved — the exact string to POST back to the backend. */
8760
+ readonly solved: _angular_core.OutputEmitterRef<string>;
8761
+ /** The challenge expired (up-front or mid-solve). The host should request a new one. */
8762
+ readonly expired: _angular_core.OutputEmitterRef<void>;
8763
+ readonly state: _angular_core.WritableSignal<FlyCaptchaState>;
8764
+ readonly token: _angular_core.WritableSignal<string | null>;
8765
+ /** Monotonic generation — bumped on every restart/destroy so a stale async chunk
8766
+ * or timer that captured an older generation no-ops instead of racing. */
8767
+ private _gen;
8768
+ private _raf;
8769
+ private _expiryTimer;
8770
+ private _onChange;
8771
+ private _onTouched;
8772
+ constructor();
8773
+ ngOnDestroy(): void;
8774
+ private _restart;
8775
+ /**
8776
+ * Hashes `[start, end)` synchronously with {@link sha256HexSync} — no per-candidate
8777
+ * `await`, so there is no promise/microtask overhead between candidates — then
8778
+ * yields to the next animation frame for the remainder. Bails to `error` state if
8779
+ * hashing itself throws (defensive; a pure function over a short string should not)
8780
+ * or if the whole `[0, maxNumber)` space is exhausted with no match, which means the
8781
+ * challenge is corrupt or was tampered with.
8782
+ */
8783
+ private _solveChunk;
8784
+ private _onSolved;
8785
+ private _cancel;
8786
+ private _schedule;
8787
+ private _cancelSchedule;
8788
+ writeValue(value: string | null): void;
8789
+ registerOnChange(fn: (v: string | null) => void): void;
8790
+ registerOnTouched(fn: () => void): void;
8791
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyCaptchaComponent, never>;
8792
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyCaptchaComponent, "fly-captcha", never, { "challenge": { "alias": "challenge"; "required": false; "isSignal": true; }; "chunkSize": { "alias": "chunkSize"; "required": false; "isSignal": true; }; }, { "solved": "solved"; "expired": "expired"; }, never, never, true, never>;
8145
8793
  }
8794
+
8146
8795
  /**
8147
- * Storage/arbitration key for an owner. Exported because the shell computes it
8148
- * on the janitor path (`clear` after a window closes) without holding the
8149
- * publisher.
8796
+ * Singleton registry of slash commands offered by the agent input palette.
8150
8797
  *
8151
- * **Both fields key the slot**, even though the shell's own ids
8152
- * (`win-<appId>-<timestamp>`) are already unique. `windowId` is publisher-supplied
8153
- * DATA that crosses the federation boundary, and the field is documented only as
8154
- * "two windows of the same app" — nothing stops a remote passing a tab index or a
8155
- * route-derived id. Keyed on `windowId` alone, two apps that both call their first
8156
- * window `"1"` would share a slot: one silently paints the other's chrome, and the
8157
- * shell's janitor `clear()` wipes the wrong one.
8798
+ * Lives in the DS so it crosses the federation boundary as a single instance via
8799
+ * `sharedMappings: ['@flyos/design-system']`. Federated remotes register
8800
+ * their commands at boot (and dispose on window close) without forking the shell.
8801
+ *
8802
+ * Storage is a signal store. `register` is O(1) for the unique-id case and O(n) when
8803
+ * replacing an existing id (filter then push). The append-then-replace strategy is
8804
+ * deliberate: registrations are rare (each one corresponds to a remote's app-init
8805
+ * effect), and the tradeoff buys us a stable id-collision contract — the *latest*
8806
+ * registration wins, and the previous handle's `dispose()` becomes a no-op rather
8807
+ * than removing the new entry.
8158
8808
  */
8159
- declare function magicBarOwnerKey(owner: MagicBarOwnerRef): string;
8160
- /** One `menuitemradio` row. `value` is the publisher's own vocabulary. */
8161
- interface MagicBarRadioOption {
8162
- readonly value: string;
8809
+ declare class AgentCommandRegistry {
8810
+ private readonly _commands;
8811
+ /** All currently-registered commands, in insertion order. */
8812
+ readonly all: Signal<readonly AgentCommandRegistration[]>;
8163
8813
  /**
8164
- * i18n key for this row. Required, and it stays required the overwhelming case is a fixed
8165
- * vocabulary (statuses, sort orders) that MUST translate. When {@link label} is supplied this
8166
- * is the accessible fallback used if the row's data-derived text is ever empty, so give it a
8167
- * generic key ("Untitled") rather than inventing a per-value one.
8814
+ * Returns a signal of commands whose scope is `'global'` OR whose `scope.appId` is in
8815
+ * the live app set. The signal recomputes when either the registry or `liveAppIds`
8816
+ * changes pass a `Signal<ReadonlySet<string>>` from the host's app-registry service
8817
+ * for reactive filtering.
8168
8818
  */
8169
- readonly labelKey: string;
8170
- /** Params for {@link labelKey} — the count-badged option ("All statuses ({{count}})"). */
8171
- readonly labelParams?: MagicBarTextParams;
8819
+ visible(liveAppIds: ReadonlySet<string> | Signal<ReadonlySet<string>>): Signal<readonly AgentCommandRegistration[]>;
8172
8820
  /**
8173
- * ALREADY-RESOLVED display text, for a row whose label is TENANT DATA rather than a caption —
8174
- * a campaign name, an area of focus, an owner. When present it wins over {@link labelKey}.
8175
- *
8176
- * ## Why this had to exist
8177
- * Everything else in this contract is an i18n key, deliberately (see the module header). But a
8178
- * key can only name a string the APP ships in its locale bundles, and a tenant's campaign names
8179
- * are rows in their database. Without this field a publisher had exactly two bad options: put
8180
- * the name in `labelKey`, which renders correctly in `en`, renders identically untranslated in
8181
- * ar/fr/ur, and is invisible to every locale tool; or leave the filter out of the bar entirely.
8182
- * Thoughts' `top-trending` chose the second and kept its campaign/area filters in-page — that
8183
- * is the gap this closes.
8184
- *
8185
- * ## What it does NOT license
8186
- * Do not use it for captions. If the string is something your app authored — "All statuses",
8187
- * "Newest first", an action name — it belongs in a locale bundle and `labelKey` is the field.
8188
- * The test is where the string comes from: shipped with the app ⇒ `labelKey`; read from the
8189
- * tenant's data ⇒ `label`. A resolved caption here is untranslatable text smuggled past the
8190
- * locale tooling, which is precisely what the key-only rule exists to prevent.
8821
+ * Register a single command. Returns a handle whose `dispose()` removes the row by
8822
+ * id. If the same id is later re-registered, the original handle's `dispose()`
8823
+ * becomes a no-op (the newer registration owns the row). Idempotent disposal.
8824
+ */
8825
+ register(cmd: AgentCommandRegistration): AgentCommandHandle;
8826
+ /**
8827
+ * Bulk register. Rolls back on duplicate id within the input batch (throws before any
8828
+ * row lands). Cross-batch duplicates against existing rows follow the standard
8829
+ * "latest wins" rule and do NOT trigger rollback.
8191
8830
  *
8192
- * It participates in the dedupe fingerprint, so a renamed campaign repaints.
8831
+ * Returns a handle whose `dispose()` tears down every row registered by this call.
8193
8832
  */
8194
- readonly label?: string;
8195
- }
8196
- /** Render surface of a radio menu — the dedupe key for {@link MagicBarRadioMenu}. */
8197
- interface MagicBarRadioMenuSpec {
8198
- /** Eyebrow above the options ("Filter by status" / "Sort by"). */
8199
- readonly titleKey: string;
8200
- /** Params for {@link titleKey}. */
8201
- readonly titleParams?: MagicBarTextParams;
8202
- readonly options: readonly MagicBarRadioOption[];
8203
- /** Current selection — check glyph + 600 weight on the matching row. */
8204
- readonly value: string;
8205
- /** The unfiltered/unsorted baseline; the trigger shows its accent dot iff `value !== defaultValue`. */
8206
- readonly defaultValue: string;
8207
- }
8208
- /** What a publisher supplies for a radio menu. */
8209
- interface MagicBarRadioMenu extends MagicBarRadioMenuSpec {
8210
- readonly onSelect?: (value: string) => void;
8211
- }
8212
- /** What the shell renders. `select` always exists; it no-ops when the publisher supplied no handler. */
8213
- interface MagicBarRadioMenuView extends MagicBarRadioMenuSpec {
8214
- select(value: string): void;
8833
+ registerAll(cmds: readonly AgentCommandRegistration[]): AgentCommandHandle;
8834
+ /** Tear down by id. Idempotent. */
8835
+ unregister(id: string): void;
8836
+ /** Monotonic counter; identifies which registration call currently owns each id. */
8837
+ private _generation;
8838
+ /** id → generation. Used so a stale handle's `dispose()` is a no-op after replacement. */
8839
+ private readonly _owners;
8840
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentCommandRegistry, never>;
8841
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentCommandRegistry>;
8215
8842
  }
8843
+
8216
8844
  /**
8217
- * Render surface of an action the dedupe key for {@link MagicBarAction}.
8845
+ * Singleton registry of entity lookups offered by the `/lookup` typeahead.
8218
8846
  *
8219
- * `id` must be unique across the WHOLE contribution (every group, plus `primary`
8220
- * and `overflow`), not merely within its group: it is the address the shell's
8221
- * invoker resolves against the publisher's latest handlers.
8847
+ * Mirrors {@link AgentCommandRegistry}'s federation-singleton story
8848
+ * (`sharedMappings: ['@flyos/design-system']`), id-collision
8849
+ * "latest wins" contract, and disposable-handle ergonomics. OS-core entities
8850
+ * (note / calendar event / file) register once at shell bootstrap via
8851
+ * `CORE_APP_LOOKUPS`; federated remotes (Circles: scenario / trend / signal)
8852
+ * register at remote-component boot and dispose on window close.
8853
+ *
8854
+ * **Scope semantics diverge from commands.** Commands HIDE when their `appId`
8855
+ * isn't in `liveAppIds`. Lookups DO NOT — they're always offered, and
8856
+ * `{appId}` is just a *priority hint* that bumps that lookup to the top of
8857
+ * the entity picker when the app is live. See {@link LookupRegistration.scope}
8858
+ * for the rationale.
8859
+ *
8860
+ * Storage is a signal store keyed on {@link LookupRegistration.entity}. Because
8861
+ * `entity` is the collision key, an app re-registering the same entity replaces
8862
+ * the prior descriptor; a stale handle's `dispose()` then no-ops.
8222
8863
  */
8223
- interface MagicBarActionSpec {
8224
- readonly id: string;
8225
- /** i18n key — the `aria-label` and the default tooltip. */
8226
- readonly labelKey: string;
8227
- /** Params for {@link labelKey}, e.g. `{ count: 3 }` for "Delete {{count}} items". */
8228
- readonly labelParams?: MagicBarTextParams;
8864
+ declare class AgentLookupRegistry {
8865
+ private readonly _lookups;
8866
+ /** All currently-registered lookups, in insertion order. */
8867
+ readonly all: Signal<readonly LookupRegistration[]>;
8229
8868
  /**
8230
- * Inner-SVG geometry for a 24×24 glyph (`stroke-width: 1.8`, round caps) —
8231
- * the design's icon transport. Reference {@link FLY_MAGIC_BAR_ICONS} by name
8232
- * rather than hand-authoring; a publisher MAY author its own, subject to the
8233
- * contract below.
8869
+ * All registered lookups, sorted by affinity to `liveAppIds`:
8234
8870
  *
8235
- * **This value is not trusted.** The renderer parses it against a closed
8236
- * allowlist self-closing `path`/`circle`/`rect`/`ellipse`/`line`/`polyline`/
8237
- * `polygon` elements carrying geometry attributes only (`d`, `cx`, `points`,
8238
- * …) and binds the result as real attributes. It is never assigned as HTML.
8239
- * Anything else (a container element, an `on*` handler, `style`, `href`,
8240
- * `fill="url(…)"`, a stray text node) rejects the WHOLE fragment: the button
8241
- * renders with no glyph and logs. See `magic-actions-icon.ts`.
8871
+ * 1. Lookups whose `scope.appId` is in the live app set (in registration
8872
+ * order within that bucket).
8873
+ * 2. Then everything else `'global'` lookups AND scoped lookups whose
8874
+ * app isn't currently live in registration order.
8242
8875
  *
8243
- * The type stays `string` deliberately. Narrowing it to a branded/opaque type
8244
- * would be a ds-compat MAJOR, and it would not buy anything the renderer's own
8245
- * validation does not already guaranteea nominal type describes where a
8246
- * value came from, not what it contains.
8247
- */
8248
- readonly iconPath?: string;
8249
- /** Default `'icon'`. */
8250
- readonly kind?: MagicBarActionKind;
8251
- /** Dimmed (`opacity .38`), NEVER hidden — a vanishing action teaches the user nothing. */
8252
- readonly disabled?: boolean;
8253
- /**
8254
- * Tooltip override while disabled. Absent is the normal case: the renderer
8255
- * auto-suffixes the resolved label ("… — select an item first"). Resolution
8256
- * and the connector phrasing are the renderer's job precisely because they are
8257
- * locale work, and this store holds keys rather than text.
8258
- */
8259
- readonly disabledTipKey?: string;
8260
- /** Params for {@link disabledTipKey}. */
8261
- readonly disabledTipParams?: MagicBarTextParams;
8262
- /** Tooltip override while enabled. Absent → the renderer uses `labelKey`. */
8263
- readonly tipKey?: string;
8264
- /** Params for {@link tipKey}. */
8265
- readonly tipParams?: MagicBarTextParams;
8266
- /**
8267
- * Semantic colour. Default `'default'`; `'danger'` is the ONLY thing that makes
8268
- * an icon red (D7), `'success'` the green completed-toggle treatment.
8876
+ * Recomputes when either the registry or `liveAppIds` changes. Pass a
8877
+ * `Signal<ReadonlySet<string>>` from the host's app-registry for reactive
8878
+ * re-sorting. **Always returns the full registry** see the type doc on
8879
+ * {@link LookupRegistration.scope} for why this differs from
8880
+ * {@link AgentCommandRegistry.visible}.
8269
8881
  */
8270
- readonly tone?: MagicBarTone;
8271
- /** `aria-pressed` for `kind: 'toggle'`. */
8272
- readonly pressed?: boolean;
8273
- /** Small accent dot — a non-default filter/sort is active. */
8274
- readonly badgeDot?: boolean;
8882
+ visible(liveAppIds: ReadonlySet<string> | Signal<ReadonlySet<string>>): Signal<readonly LookupRegistration[]>;
8275
8883
  /**
8276
- * Reserved (D5 §4 item 7). The designs carry no keyboard-shortcut affordance
8277
- * anywhere; the field exists so adding one later is a renderer change rather
8278
- * than a contract break. Nothing renders it today.
8884
+ * Register one lookup. Returns a handle whose `dispose()` removes the row by
8885
+ * `entity`. A later re-registration of the same entity makes the original
8886
+ * handle's `dispose()` a no-op (the newer registration owns the row).
8279
8887
  */
8280
- readonly shortcut?: string;
8281
- }
8282
- /** What a publisher supplies for one action. */
8283
- interface MagicBarAction extends MagicBarActionSpec {
8284
- readonly menu?: MagicBarRadioMenu;
8888
+ register(lookup: LookupRegistration): LookupHandle;
8285
8889
  /**
8286
- * Invoked on activation. The argument is the DOM element the renderer painted
8287
- * for this action see {@link MagicBarActionView.run} for why it is passed and
8288
- * what a publisher may do with it. Ignore it and this is the zero-argument
8289
- * handler it has always been.
8890
+ * Bulk register. Rolls back on a duplicate entity WITHIN the input batch
8891
+ * (throws before any row lands). Cross-batch duplicates against existing rows
8892
+ * follow the standard "latest wins" rule and do NOT trigger rollback.
8290
8893
  */
8291
- readonly onSelect?: (trigger?: HTMLElement) => void;
8292
- }
8293
- /**
8294
- * What the shell renders. `run()` always exists and always dispatches to the
8295
- * publisher's LATEST handler for this id — including after a re-publish the
8296
- * registry deliberately did not propagate (see `magic-bar-projection.ts`).
8297
- */
8298
- interface MagicBarActionView extends MagicBarActionSpec {
8299
- readonly menu: MagicBarRadioMenuView | null;
8894
+ registerAll(lookups: readonly LookupRegistration[]): LookupHandle;
8300
8895
  /**
8301
- * Fire the publisher's newest handler for this id.
8302
- *
8303
- * ## Why it carries the trigger element
8304
- * An action that opens a POPOVER the publisher owns — the canonical case is a
8305
- * listing screen's advanced-filter panel — has to position that popover under
8306
- * the button the user just pressed. The publisher cannot find that button:
8307
- * embedded, it is painted by `fly-magic-actions` inside the shell's top-bar
8308
- * pill, in the shell's DOM, several stacking contexts and one federation
8309
- * boundary away from the remote that published the action. Before this argument
8310
- * existed, PPM's projects register had to anchor its panel to its own page
8311
- * header instead, which put the panel in the middle of the window while the
8312
- * button that opened it sat in the chrome above — an affordance with no visible
8313
- * relationship to its own trigger.
8896
+ * Resolve a deep-link anchor to a concrete launch target.
8314
8897
  *
8315
- * Passing the element rather than modelling the popover is deliberate. The
8316
- * panel's CONTENT is app schema (see {@link MagicBarSearch.onOpenFilters} for
8317
- * the same ruling), so it must stay in the publisher's own DOM and injection
8318
- * context; only its POSITION is chrome-relative. An element reference crosses
8319
- * the federation boundary without any of the coupling a descriptor would need
8320
- * a DOM node is a DOM node in every bundle. `fly-filter-panel`'s `[anchorEl]`
8321
- * is the intended consumer.
8898
+ * `kind` is the dotted `<appId>.<entity>` token the agents backend emits
8899
+ * inside `flyos:<kind>/<id>` chat-answer anchors the same entity-kind
8900
+ * vocabulary as drag-payload kinds and `ref` parts. Returns
8901
+ * `{ appId, route }` when a registered lookup for that `(appId, entity)`
8902
+ * pair carries a {@link LookupDescriptor.deepLinkRoute} template; `null`
8903
+ * otherwise (unknown entity, app mismatch, or no template e.g. the
8904
+ * owning app isn't installed) so the caller renders plain text rather than
8905
+ * a dead link.
8322
8906
  *
8323
- * ## What a publisher must not assume
8324
- * The element is the trigger AS PAINTED RIGHT NOW. It is valid for the duration
8325
- * of the popover, not beyond: a re-publish that drops this action id destroys it
8326
- * (`@for (…; track action.id)`), and a mode switch repaints it in a different
8327
- * chrome entirely. Hold it in a signal the popover's open-state clears, never in
8328
- * long-lived state, and never mutate it.
8907
+ * `appId` and `entity` are both dot-free by their own grammars, so the
8908
+ * FIRST dot is the unambiguous split point; a dotless `kind` can't carry an
8909
+ * app and never resolves. The template's single `{id}` placeholder is
8910
+ * substituted URL-encoded.
8911
+ */
8912
+ resolveDeepLink(kind: string, id: string): {
8913
+ readonly appId: string;
8914
+ readonly route: string;
8915
+ } | null;
8916
+ /**
8917
+ * Resolve a deep-link target from a bare `(entity, id)` pair — the shape a
8918
+ * `/lookup` ref carries (it has no `<appId>.<entity>` kind token; the owning
8919
+ * app is implicit in the registered descriptor). `entity` is the registry's
8920
+ * unique storage key, so it identifies the descriptor unambiguously without
8921
+ * an app prefix.
8329
8922
  *
8330
- * It is OPTIONAL because not every renderer has one to give — a keyboard-driven
8331
- * invocation, a synthetic call in a test, an inline composer that paints no
8332
- * button. A publisher that receives `undefined` must still work: for a popover
8333
- * that means falling back to its own in-page anchoring, which is exactly what
8334
- * `fly-filter-panel` does when `[anchorEl]` is null.
8923
+ * Returns `{ appId, route }` (the descriptor's {@link LookupDescriptor.appId}
8924
+ * / affinity `scope.appId` as the owner, `{id}` substituted URL-encoded) when
8925
+ * a matching descriptor carries a {@link LookupDescriptor.deepLinkRoute};
8926
+ * `null` otherwise (unknown entity, no template, or the owning app has since
8927
+ * unregistered) so callers render plain text rather than a dead link — the
8928
+ * same graceful-degrade contract as {@link resolveDeepLink}.
8335
8929
  */
8336
- run(trigger?: HTMLElement): void;
8337
- }
8338
- /** Render surface of a group — the dedupe key for {@link MagicBarGroup}. */
8339
- interface MagicBarGroupSpec {
8340
- readonly id: string;
8341
- /** i18n key for the `role="group"` aria-label ("Signal actions"). */
8342
- readonly labelKey: string;
8343
- /** Framed variant: 2 px gaps + a trailing separator. Plain (default) uses 8 px gaps. */
8344
- readonly framed?: boolean;
8345
- }
8346
- interface MagicBarGroup extends MagicBarGroupSpec {
8347
- readonly items: readonly MagicBarAction[];
8348
- }
8349
- interface MagicBarGroupView extends MagicBarGroupSpec {
8350
- readonly items: readonly MagicBarActionView[];
8930
+ resolveDeepLinkForEntity(entity: string, id: string): {
8931
+ readonly appId: string;
8932
+ readonly route: string;
8933
+ } | null;
8934
+ /** Tear down by entity. Idempotent. */
8935
+ unregister(entity: string): void;
8936
+ /** Monotonic counter; identifies which registration call currently owns each entity. */
8937
+ private _generation;
8938
+ /** entity → generation. Lets a stale handle's `dispose()` no-op after replacement. */
8939
+ private readonly _owners;
8940
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentLookupRegistry, never>;
8941
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentLookupRegistry>;
8351
8942
  }
8943
+
8352
8944
  /**
8353
- * Render surface of the expanding search field — the dedupe key for
8354
- * {@link MagicBarSearch}.
8945
+ * Singleton registry of chip-renderer components and keyboard-alternative draggable
8946
+ * items, keyed by `kind` and `appId`.
8355
8947
  *
8356
- * This is a per-page typeahead, NOT a command palette: neither design package
8357
- * has a global ⌘K surface, and the only "command" affordance is the Ask-AI menu.
8948
+ * Like {@link AgentCommandRegistry}, this lives in the DS so it crosses the federation
8949
+ * boundary as a single instance. Hosts (the shell `<fly-agent-input>`) lookup
8950
+ * renderers; remotes register them.
8951
+ *
8952
+ * Renderer lookup is `O(n)` over the registered list — the registry is small (one or
8953
+ * two entries per app) and lookups happen on drop, not per frame.
8954
+ *
8955
+ * Draggable storage is per-`appId` writable signal cached in a Map, so each `appId`
8956
+ * gets a stable {@link Signal} reference across reads (callers can `===`-compare).
8358
8957
  */
8359
- interface MagicBarSearchSpec {
8360
- /** Context-aware placeholder key ("Search tasks…", "Search events…"). */
8361
- readonly placeholderKey: string;
8362
- /** Params for {@link placeholderKey}, e.g. `{ count: 1204 }` for "Search {{count}} tasks…". */
8363
- readonly placeholderParams?: MagicBarTextParams;
8958
+ declare class AgentDropRegistry {
8959
+ private readonly _renderers;
8960
+ /** Per-appId writable store for draggables. Read-only mirror returned to callers. */
8961
+ private readonly _draggablesByApp;
8364
8962
  /**
8365
- * The text the search field should SHOW literal text, not an i18n key, because
8366
- * it is the user's own query rather than a caption.
8367
- *
8368
- * Exists because a view can arrive **already filtered**: a deep link carrying
8369
- * `?q=…`, a restored session, a filter applied from a detail pane. Without it the
8370
- * box renders empty over a visibly filtered list, and the user is looking at a
8371
- * contradiction the chrome cannot explain.
8372
- *
8373
- * ## Why it is not called `query`
8374
- * {@link MagicBarSearchView} — which extends this interface — already declares
8375
- * `query(text: string): void`, the late-bound invoker. A `query?: string` here
8376
- * would make that view an illegal extension (TS2430: `(text: string) => void` is
8377
- * not assignable to `string | undefined`), and the only other way out — renaming
8378
- * the invoker — removes a member of a shipped exported interface, which is a
8379
- * ds-compat MAJOR (rule 7) and forks the federation singleton for every remote.
8380
- * So the field takes the name that pairs with the invoker instead of colliding
8381
- * with it: `queryText` is the text, `query()` submits it.
8382
- *
8383
- * ## Precedence — this is a SEED, not a controlled value
8384
- * The field's live text is owned by the **shell**, because the user types into
8385
- * it. This field is the publisher's chance to move it, and the shell adopts the
8386
- * published value exactly three times:
8387
- *
8388
- * 1. the first time it binds this view,
8389
- * 2. when {@link MagicBarContribution.viewKey} changes (reset-on-navigate — the
8390
- * previous view's query must not survive into the next one), and
8391
- * 3. when the published value **itself changes** from the one previously
8392
- * published.
8393
- *
8394
- * A re-publish carrying an UNCHANGED `queryText` never touches what the user has
8395
- * typed. That third clause is the whole rule: the dominant re-publish is
8396
- * `disabled: !hasSelection` flipping on a selection change, and a shell that
8397
- * re-read `queryText` on every emitted view would revert a keystroke every time
8398
- * the user selected a row mid-search — the classic controlled-input bug. The rule
8399
- * is authored once, as {@link magicBarSearchText}, so no renderer re-derives it.
8400
- *
8401
- * `undefined` and `''` are **different** and the difference is load-bearing:
8402
- * absent means "this view does not manage the field" (nothing is ever forced),
8403
- * `''` means "clear it". Both the fingerprint and the seed comparison distinguish
8404
- * them.
8963
+ * Look up the chip-renderer component class for a `kind`. The newest registration for
8964
+ * a given `kind` wins, regardless of `appId` — we scan the list in reverse so a later
8965
+ * `register` call shadows an earlier one for the same `kind`.
8405
8966
  *
8406
- * ## Do not echo the user's keystrokes back through this field
8407
- * A publisher that mirrors its `onQuery` argument straight back into `queryText`
8408
- * is harmless (the seed changes to text the field already shows). A publisher
8409
- * that mirrors it back **debounced** is not: typing "abc" then publishing
8410
- * `queryText: 'a'` 300 ms later is, by rule 3, a genuine publisher-initiated
8411
- * change, and the field snaps back to "a". Publish here only for state the view
8412
- * owns independently of the field.
8967
+ * Returns `null` when no renderer is registered; the host falls back to a generic
8968
+ * `plainTextFallback` chip.
8413
8969
  */
8414
- readonly queryText?: string;
8970
+ rendererFor(kind: string): Type<AgentChipHostInputs> | null;
8415
8971
  /**
8416
- * Expanded width in px a **desktop layout hint**, carried verbatim.
8972
+ * Register a renderer for a `(kind, appId)` pair. Re-registering the same pair
8973
+ * replaces the prior entry; the disposal handle for the prior registration becomes
8974
+ * a no-op.
8417
8975
  *
8418
- * The design's range is 140–420 (default 230), but this store deliberately does
8419
- * NOT validate or clamp it: enforcing a paint range here would reject a whole
8420
- * contribution over a cosmetic hint (leaving the previous view's actions in the
8421
- * chrome), and mobile ignores the field entirely, so there is no single range to
8422
- * enforce. `fly-magic-actions` clamps to the design range at paint time and is
8423
- * the only place that decides what an out-of-range or non-finite value means.
8976
+ * Returns a {@link AgentCommandHandle} (re-used to keep the disposable shape uniform
8977
+ * across registries) whose `dispose()` removes this exact registration.
8424
8978
  */
8425
- readonly openWidth?: number;
8426
- /** Accent dot on the Filters button — a non-default filter is applied. */
8427
- readonly filtersActive?: boolean;
8979
+ register<T = unknown>(reg: AgentDropRendererRegistration<T>): AgentCommandHandle;
8980
+ /**
8981
+ * Apps publish their live "draggable from focused window" set so the keyboard
8982
+ * "Attach from app…" menu can offer them. Hosts call this each time the user-visible
8983
+ * draggable list changes; passing an empty array clears the entry for this `appId`.
8984
+ */
8985
+ publishDraggables(appId: string, items: readonly AgentDraggableItem[]): void;
8986
+ /**
8987
+ * Reactive read of the draggable set published for `appId`. Empty when none. The
8988
+ * returned signal is stable across calls (cached by `appId`), so consumers can use
8989
+ * it as a stable input to `computed()`.
8990
+ */
8991
+ draggablesFor(appId: string): Signal<readonly AgentDraggableItem[]>;
8992
+ /** Lazy-init the per-appId writable bucket. Returns the writable handle for internal use. */
8993
+ private bucketFor;
8994
+ private _generation;
8995
+ private readonly _owners;
8996
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentDropRegistry, never>;
8997
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentDropRegistry>;
8998
+ }
8999
+
9000
+ /**
9001
+ * Imperative action published by an app, consumed by the agent panel.
9002
+ *
9003
+ * Re-uses {@link AgentDragPayload} as the wire envelope so a dragged item
9004
+ * and a programmatic "Explain" click are byte-for-byte the same shape —
9005
+ * renderers, validators, and serialisation paths never fork on transport.
9006
+ *
9007
+ * Adding a verb is a minor DS bump (consumers ignore unknown verbs in
9008
+ * their `switch`). Removing one is a major DS bump.
9009
+ */
9010
+ type AgentActionVerb = 'explain' | 'why-empty' | 'compose-query' | 'compare' | 'summarize';
9011
+ /**
9012
+ * Whether the agent panel sends the staged payload immediately or stages
9013
+ * the chip for the user to edit and send manually.
9014
+ *
9015
+ * Phase 1 (DS v2.6.0) supports `'stage'` only. Dispatching with `'auto'`
9016
+ * throws {@link AgentActionUnsupportedDispatchError} so callers don't
9017
+ * silently fail. `'auto'` lands once `AgentInputComponent.programmaticSubmit`
9018
+ * is exposed and reviewed against the input's state machine.
9019
+ */
9020
+ type AgentActionDispatch = 'auto' | 'stage';
9021
+ interface AgentAction<T = unknown> {
9022
+ /** Intent the agent should apply to {@link payload}. */
9023
+ readonly verb: AgentActionVerb;
9024
+ /** The wire envelope. Validated against {@link validateAgentPayload}'s
9025
+ * size caps before the bus fans it out. */
9026
+ readonly payload: AgentDragPayload<T>;
9027
+ /** Optional slash command id to bind before send (e.g. `'explain-report'`).
9028
+ * Phase 1 captures this for telemetry only — actual binding lands when
9029
+ * the input's programmatic-send API ships. An unknown id is dropped
9030
+ * silently with a console warning, the chip still arrives. */
9031
+ readonly autoCommandId?: string;
9032
+ /** Phase 1 supports `'stage'` only. See {@link AgentActionDispatch}. */
9033
+ readonly dispatch: AgentActionDispatch;
9034
+ /** Source DOM rect for the FLIP entry animation. Omit to skip the
9035
+ * animation (e.g. dispatching from a keyboard shortcut with no anchor). */
9036
+ readonly originRect?: DOMRect;
9037
+ /** Optional HTML snippet rendered inside the flight ghost. Callers are
9038
+ * responsible for escaping untrusted text — the bus does not sanitise.
9039
+ * Defaults (when omitted) to a strong-wrapped escape of the payload's
9040
+ * `plainTextFallback` rendered by the panel host. */
9041
+ readonly originPreviewHtml?: string;
8428
9042
  }
8429
- interface MagicBarSearch extends MagicBarSearchSpec {
8430
- readonly onQuery?: (query: string) => void;
8431
- /**
8432
- * Opens the view's own Advanced-search overlay. Presence of this handler is
8433
- * what makes the Filters button exist — the DS deliberately does not model the
8434
- * dialog itself. Its fields are app schema (Circles' PESTLE/horizon grid), and
8435
- * a descriptor for them would be business logic the design system cannot
8436
- * validate or render honestly.
8437
- */
8438
- readonly onOpenFilters?: (trigger?: HTMLElement) => void;
9043
+ /**
9044
+ * Thrown synchronously by {@link AgentActionBus.dispatch} when a caller
9045
+ * supplies a dispatch mode this DS version doesn't implement yet. Catching
9046
+ * by class name lets a forward-compatible caller fall back to `'stage'`
9047
+ * without depending on instanceof across federation boundaries.
9048
+ */
9049
+ declare class AgentActionUnsupportedDispatchError extends Error {
9050
+ readonly dispatch: AgentActionDispatch;
9051
+ constructor(dispatch: AgentActionDispatch);
8439
9052
  }
8440
- interface MagicBarSearchView extends MagicBarSearchSpec {
8441
- /** True iff the publisher can open an advanced-search overlay. */
8442
- readonly hasFilters: boolean;
9053
+
9054
+ /**
9055
+ * Imperative sibling to {@link AgentCommandRegistry} / {@link AgentDropRegistry}.
9056
+ *
9057
+ * Apps call {@link dispatch} to push a typed {@link AgentAction} onto the bus;
9058
+ * the agent panel subscribes once at construct and routes by verb. The bus
9059
+ * itself is a thin pass-through — it does NOT decide UI behaviour. The
9060
+ * subscriber (agent-panel) owns: showing the panel, staging the chip,
9061
+ * triggering the flight animation, and binding the command. This keeps the
9062
+ * DS free of host policy.
9063
+ *
9064
+ * Federation-safe: `providedIn: 'root'` + `sharedMappings: ['@flyos/design-system']`
9065
+ * give every federated remote the same singleton, so a remote's "Explain"
9066
+ * button reaches the host's panel without any cross-bundle wiring.
9067
+ *
9068
+ * Validation runs synchronously inside `dispatch` so a caller that sends an
9069
+ * oversize payload sees the throw at their site, not on the subscriber. The
9070
+ * subscriber therefore never has to defend against malformed envelopes.
9071
+ */
9072
+ declare class AgentActionBus {
9073
+ private readonly _actions$;
9074
+ /** Hot stream of actions in dispatch order. Subscribers receive only
9075
+ * actions dispatched AFTER they subscribe — late subscribers see nothing
9076
+ * retroactively. Use {@link lastAction} for the latest snapshot. */
9077
+ readonly actions$: Observable<AgentAction>;
9078
+ /** Most recent action — for DevTools, smoke tests, and late-subscriber
9079
+ * catch-up. Null until the first successful dispatch. */
9080
+ readonly lastAction: _angular_core.WritableSignal<AgentAction<unknown> | null>;
8443
9081
  /**
8444
- * Submit the field's current text to the publisher's newest `onQuery`. The
8445
- * inbound half what the field should SHOW — is
8446
- * {@link MagicBarSearchSpec.queryText}, and that asymmetry in naming is forced:
8447
- * this member shipped first, and renaming it would be a ds-compat MAJOR.
9082
+ * The action currently being processed by the subscriber, or null when
9083
+ * none. Set by {@link dispatch} immediately before emitting on
9084
+ * {@link actions$}; cleared by the subscriber via {@link settle} once
9085
+ * it finishes its handler (success or fail). Lets the dispatcher render
9086
+ * a busy state on the originating control — e.g. a card swapping its
9087
+ * sparkle icon for a spinner while the agent panel mints the optimistic
9088
+ * thread and starts the request. Identity check (`bus.inFlight() === act`)
9089
+ * is the panel-side contract; dispatchers usually project to a stable id
9090
+ * inside the payload (e.g. <c>reportId</c>) to scope busy-state visually.
9091
+ *
9092
+ * If multiple dispatches race, the latest wins — the prior in-flight
9093
+ * action is dropped on the floor here (the panel may still handle it,
9094
+ * but the dispatcher's busy indicator follows the newer action). Apps
9095
+ * that need stricter single-flight semantics should guard at the call
9096
+ * site (the agent-panel's <c>_pendingTempThreadId</c> already does so
9097
+ * for the explain verb).
8448
9098
  */
8449
- query(text: string): void;
9099
+ readonly inFlight: _angular_core.WritableSignal<AgentAction<unknown> | null>;
8450
9100
  /**
8451
- * Open the publisher's advanced-search overlay. Carries the Filters button the
8452
- * shell painted, for the same reason and under the same caveats as
8453
- * {@link MagicBarActionView.run} — a publisher whose overlay is a popover
8454
- * anchors it there instead of guessing at a position inside its own page.
9101
+ * Push an action onto the bus.
9102
+ *
9103
+ * Throws synchronously when:
9104
+ * - `dispatch === 'auto'` (not implemented in this DS version) see
9105
+ * {@link AgentActionUnsupportedDispatchError}.
9106
+ * - the payload fails {@link validateAgentPayload} (oversize, invalid
9107
+ * version, invalid kind). The error message carries the field path
9108
+ * so the caller can fix the offending field.
9109
+ *
9110
+ * Subscribers see the action via {@link actions$} on the next tick of
9111
+ * the Subject; the {@link lastAction} signal updates synchronously
9112
+ * before the Subject emits so an effect reading both stays consistent.
8455
9113
  */
8456
- openFilters(trigger?: HTMLElement): void;
8457
- }
8458
- /**
8459
- * One view's complete claim on the magic bar.
8460
- *
8461
- * Identity (`appId` / `windowId`) is deliberately absent: it belongs to the
8462
- * {@link MagicBarPublisher} that was minted for this owner, so a later publish
8463
- * cannot re-target another app's slot.
8464
- */
8465
- interface MagicBarContribution {
9114
+ dispatch<T>(action: AgentAction<T>): void;
8466
9115
  /**
8467
- * What the publisher is showing, e.g. `'tasks:detail'`. A change here is
8468
- * reset-on-navigate: the registry replaces the slot wholesale rather than
8469
- * reconciling, so nothing from the previous view can survive.
9116
+ * Subscriber contract: call after the handler for {@link inFlight}
9117
+ * completes (success or fail). Only clears {@link inFlight} if it still
9118
+ * points at the passed action a no-op when a later dispatch already
9119
+ * superseded it. Pass the same action reference the subscriber received
9120
+ * from {@link actions$}; identity is the gate.
8470
9121
  */
8471
- readonly viewKey: string;
8472
- readonly search?: MagicBarSearch | null;
8473
- /** Ordered clusters, separator-joined. */
8474
- readonly groups?: readonly MagicBarGroup[];
8475
- /** The accent-filled CTA ("New task", "Add signal"). */
8476
- readonly primary?: MagicBarAction | null;
9122
+ settle(action: AgentAction): void;
8477
9123
  /**
8478
- * Ids of {@link AgentCommand}s this view considers most relevant, most-relevant
8479
- * first the Ask-AI menu's per-view sets.
9124
+ * Semantic alias of {@link settle} for explicit user-driven cancellation
9125
+ * e.g. a future "Stop" button in the agent input tray, or a dispatcher
9126
+ * teardown that wants to abandon its own in-flight action. Identical
9127
+ * runtime behaviour (identity check + clear), but the two-method surface
9128
+ * lets the UI distinguish "handler finished" from "user said no" in
9129
+ * telemetry / logs without sniffing a "reason" parameter.
8480
9130
  *
8481
- * Ids, not command objects: the platform already models commands (manifest
8482
- * `commands[]` `AgentCommandRegistry`), and D5 §6 item 9 is explicit that
8483
- * production must source these from manifests instead of the mock's hardcoded
8484
- * `{ title, note, prompt }` triples. Duplicating that model here would fork the
8485
- * palette and reintroduce hardcoded prompt text. Unknown ids are dropped by the
8486
- * resolver; an empty list leaves the shell's affinity ranking in charge.
9131
+ * Pass the same action reference returned from {@link inFlight} or held
9132
+ * by the dispatcher; identity is the gate.
8487
9133
  */
8488
- readonly quickCommandIds?: readonly string[];
8489
- /** The "…" overflow set. Reserved design intent (D5 §6 item 8) — Print / Export / Settings. */
8490
- readonly overflow?: readonly MagicBarAction[];
8491
- }
8492
- /** The shell-facing projection of a contribution. See `magic-bar-projection.ts`. */
8493
- interface MagicBarView {
8494
- readonly appId: string;
8495
- readonly windowId?: string;
8496
- /** {@link magicBarOwnerKey} of the publisher. */
8497
- readonly ownerKey: string;
8498
- readonly viewKey: string;
8499
- readonly search: MagicBarSearchView | null;
8500
- readonly groups: readonly MagicBarGroupView[];
8501
- readonly primary: MagicBarActionView | null;
8502
- readonly quickCommandIds: readonly string[];
8503
- readonly overflow: readonly MagicBarActionView[];
9134
+ cancel(action: AgentAction): void;
9135
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentActionBus, never>;
9136
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentActionBus>;
8504
9137
  }
9138
+
8505
9139
  /**
8506
- * A view's handle on its magic-bar slot. Minted once per publisher (not per
8507
- * publish) because the publish rate is the whole point of this surface: the
8508
- * sibling registries hand back a handle per `register()` and expect a handful of
8509
- * calls per app lifetime, whereas a magic-bar publisher re-emits on every
8510
- * selection change. Handing out a fresh handle each time would leave the caller
8511
- * holding a dead one and force it to reassign on every keystroke.
9140
+ * FLIP-style entry animation for payloads landing in the agent panel.
9141
+ *
9142
+ * Pure DOM + Web Animations API no Chart.js, no Angular animations module,
9143
+ * no CSS transitions racing layout. Honours `prefers-reduced-motion`: the
9144
+ * ghost is appended then removed without animating when the user asked for
9145
+ * less motion (so DOM side-effects stay consistent).
9146
+ *
9147
+ * Lifecycle:
9148
+ * 1. The agent panel calls {@link registerTarget} in `ngAfterViewInit`
9149
+ * with its header element.
9150
+ * 2. A source app dispatches an `AgentAction` carrying an `originRect`
9151
+ * from `getBoundingClientRect()` on the click target.
9152
+ * 3. The bus subscriber calls {@link flyInto} with that rect.
9153
+ * 4. The animator creates a fixed-position ghost at the origin, animates
9154
+ * transform + opacity toward the registered target's rect, then
9155
+ * removes itself on `onfinish` / `oncancel`.
9156
+ *
9157
+ * Uses `getBoundingClientRect()` (physical viewport coords) so the animation
9158
+ * is RTL-correct without inset-inline math — the rect already encodes the
9159
+ * physical position regardless of `dir`.
9160
+ *
9161
+ * The 900 ms duration and easing curve are deliberately hardcoded — making
9162
+ * them configurable surfaces an API the host can't usefully tune without
9163
+ * understanding motion design as a whole.
8512
9164
  */
8513
- interface MagicBarPublisher {
8514
- readonly owner: MagicBarOwnerRef;
9165
+ declare class AgentFlightAnimator {
9166
+ /** Hardcoded — see class doc. */
9167
+ private static readonly DURATION_MS;
9168
+ private static readonly EASING;
9169
+ /** Floor the target/source scale ratio so a tiny target rect doesn't
9170
+ * collapse the ghost to invisibility before the animation finishes. */
9171
+ private static readonly MIN_SCALE;
9172
+ private targetEl;
9173
+ /** Called by the panel host to publish where flights should land. Pass
9174
+ * `null` on destroy so a re-mounted panel doesn't leave the animator
9175
+ * pointing at a detached node. */
9176
+ registerTarget(el: HTMLElement | null): void;
8515
9177
  /**
8516
- * Replace this owner's contribution. No-op after {@link dispose}, and no-op
8517
- * after the shell has {@link MagicBarRegistry.clear}ed this owner key.
9178
+ * Animate a ghost element from {@link from} to the registered target's
9179
+ * rect. No-ops when:
9180
+ * - no target is registered (silent — panel may not be mounted yet)
9181
+ * - running outside a browser (SSR safety)
9182
+ * - the user has `prefers-reduced-motion: reduce` set (DOM is still
9183
+ * touched so callers see consistent side-effects, but no animation
9184
+ * runs)
8518
9185
  *
8519
- * **Publish is last-writer-wins and eviction is not** a deliberate asymmetry.
8520
- * A stale publisher (one that has been replaced on this key by a newer one)
8521
- * cannot `dispose()` the newer one out of the slot, but it *can* still take the
8522
- * slot back by publishing. Symmetry would need a "newest publisher wins" rule
8523
- * enforced on write, which would make the first publish of a replacement
8524
- * publisher silently drop whenever two live views briefly share a key. The
8525
- * exposure is bounded: two live publishers on one owner key only happen while a
8526
- * view is being replaced in place, and the loser re-publishes on its next
8527
- * selection change. The unbounded case — a publisher that outlives its window —
8528
- * is closed by `clear()`'s tombstone, not by write ordering.
8529
- */
8530
- publish(contribution: MagicBarContribution): void;
8531
- /**
8532
- * Release the slot. Idempotent, and a no-op if another publisher has since
8533
- * taken this owner key — the same stale-handle rule the sibling registries use.
9186
+ * The ghost is appended to `document.body` (not the panel) so a parent
9187
+ * `overflow: hidden` on the panel can't clip the flight path.
8534
9188
  */
8535
- dispose(): void;
9189
+ flyInto(from: DOMRect, opts?: {
9190
+ previewHtml?: string;
9191
+ }): void;
9192
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentFlightAnimator, never>;
9193
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentFlightAnimator>;
8536
9194
  }
8537
9195
 
8538
9196
  /**
@@ -8813,6 +9471,36 @@ interface MagicBarSearchSeed {
8813
9471
  */
8814
9472
  declare function magicBarSearchText(previous: MagicBarSearchSeed | null, next: MagicBarSearchSeed): string | null;
8815
9473
 
9474
+ /**
9475
+ * Order one group's items the way every FlyOS bar orders them.
9476
+ *
9477
+ * **Icon-only actions occupy the outer edge of the flow — the right in LTR — in the precedence
9478
+ * Add, Delete, Edit reading inward; labelled (icon+label) actions sit inside them.**
9479
+ *
9480
+ * The outer edge is where the hand and the eye finish, so it belongs to the actions that need no
9481
+ * reading: a bare glyph is fastest to hit and slowest to identify, and putting the glyphs where
9482
+ * they are found by position rather than by reading is what makes that trade pay. A labelled
9483
+ * action is identified by its text wherever it sits, so it gives the edge up.
9484
+ *
9485
+ * Order is expressed as FLOW order, never as physical left/right: the bar mirrors under RTL, so
9486
+ * "outermost" becomes the left edge in `ar`/`ur` automatically. Encoding it as a physical side
9487
+ * would be a bug in three of this platform's four locales.
9488
+ *
9489
+ * Stable within each band — callers keep authoring items in whatever order reads best in source,
9490
+ * and two labelled actions stay in the order they were written (PPM's "Promote to schedule" stays
9491
+ * after "Add backlog task").
9492
+ *
9493
+ * ## Why this lives in the design system
9494
+ * It shipped first inside PPM (`ppm/shared/magic-bar-order.util.ts`) and
9495
+ * `skills/shell-magic-bar-views.md` §7 named that file as the reference implementation —
9496
+ * which is exactly the shape of a rule that is about to be copy-pasted into the next External App.
9497
+ * The ordering is a property of the BAR, not of any one app: the same renderer paints every
9498
+ * contribution, so two apps disagreeing about it is a platform-visible inconsistency, not a local
9499
+ * style choice. Same reasoning as {@link magicBarSearchText} — a documented-but-unimplemented rule
9500
+ * gets re-derived per consumer, and the copies disagree on precisely the case that is hard to see.
9501
+ */
9502
+ declare function orderMagicBarItems(items: readonly MagicBarAction[]): MagicBarAction[];
9503
+
8816
9504
  /**
8817
9505
  * Allowlist parser for {@link MagicBarActionSpec.iconPath} — the geometry-only
8818
9506
  * gate between a publisher-supplied glyph fragment and the DOM.
@@ -9034,6 +9722,15 @@ declare class FlyMagicActionsComponent {
9034
9722
  * what stops `@for (…; track $index)` rebuilding the glyph each time.
9035
9723
  */
9036
9724
  protected iconNodes(action: MagicBarActionView): readonly FlyMagicBarIconNode[];
9725
+ /**
9726
+ * Whether this action has a glyph to paint at all.
9727
+ *
9728
+ * Derived from the PARSED nodes, never from `iconPath` being a non-empty string: a fragment the
9729
+ * allowlist rejected parses to zero nodes, and treating that as "has an icon" would paint an
9730
+ * empty `<svg>` box — on a labelled action, a permanent gap in front of the text. The parse
9731
+ * memoizes, so asking twice costs nothing.
9732
+ */
9733
+ protected hasGlyph(action: MagicBarActionView): boolean;
9037
9734
  /** `null` (not `'default'`) so `[attr.data-tone]` omits the attribute entirely for the common case. */
9038
9735
  protected toneAttr(action: MagicBarActionView): string | null;
9039
9736
  /**
@@ -9164,6 +9861,17 @@ declare const FLY_MAGIC_BAR_ICONS: {
9164
9861
  * the settings gear, not the mode switcher).
9165
9862
  */
9166
9863
  readonly calendarView: "<rect x=\"3.5\" y=\"3.5\" width=\"17\" height=\"17\" rx=\"2.5\"/><path d=\"M3.5 12h17M12 3.5v17\"/>";
9864
+ /**
9865
+ * Gantt / outline "Indent" — the block pushed in, with the arrow pointing INTO the indent.
9866
+ * Pairs with {@link outdent}: the two differ only in the arrow, which is the convention every
9867
+ * word processor and desktop planner has shipped for decades and the reason both stay icon-only.
9868
+ *
9869
+ * Directional, so publishers pass `mirrorInRtl: true` alongside it — the block sits on the
9870
+ * reading-start side, which is the right edge in `ar`/`ur`.
9871
+ */
9872
+ readonly indent: "<path d=\"M4 5h16M4 19h16\"/><path d=\"M11 9.7h9M11 14.3h9\"/><path d=\"M4.5 9.7 7.5 12l-3 2.3\"/>";
9873
+ /** Gantt / outline "Outdent" — {@link indent}'s block with the arrow pulling back out. */
9874
+ readonly outdent: "<path d=\"M4 5h16M4 19h16\"/><path d=\"M11 9.7h9M11 14.3h9\"/><path d=\"M7.5 9.7 4.5 12l3 2.3\"/>";
9167
9875
  /** `UX/FlyOS Desktop-app/MainContent.dc.html:927` (`pubTabActions`'s local `P` table). */
9168
9876
  readonly addEvidence: "<path d=\"M10.5 13.5a4 4 0 0 0 5.7 0l2-2a4 4 0 0 0-5.7-5.7l-.6.6\"/><path d=\"M13.5 10.5a4 4 0 0 0-5.7 0l-2 2a4 4 0 0 0 4.2 6.5\"/><path d=\"M17.5 15.5v5M15 18h5\"/>";
9169
9877
  /** `MainContent.dc.html:928`. Reused for every "Edit …" action (evidence, link, task). */
@@ -9212,7 +9920,7 @@ type FlyMagicBarIconName = keyof typeof FLY_MAGIC_BAR_ICONS;
9212
9920
  * App running standalone.
9213
9921
  *
9214
9922
  * ## The gap this closes
9215
- * A view publishes one `MagicBarContribution` (skill: `magic-bar-actions.md`)
9923
+ * A view publishes one `MagicBarContribution` (skill: `shell-magic-bar.md`)
9216
9924
  * and, embedded in the desktop shell, the top-bar pill renders it. Standalone,
9217
9925
  * nothing did — so every screen also kept a bespoke header-actions row
9218
9926
  * (`[page-header-actions]` buttons) that duplicated the published actions in
@@ -10490,7 +11198,7 @@ type FlyCurrencySelectorValue = string | readonly string[] | null;
10490
11198
  * `locked` is a different claim entirely: **the value is fixed on purpose**, because
10491
11199
  * something downstream now depends on it (PPM freezes a project's currency the moment any
10492
11200
  * financial row exists — changing it would silently re-denominate every stored amount).
10493
- * A dimmed control with no explanation is exactly the failure `skills/magic-bar-actions.md`
11201
+ * A dimmed control with no explanation is exactly the failure `skills/shell-magic-bar.md`
10494
11202
  * §3.6 names for actions ("a vanishing action teaches the user nothing"); the same argument
10495
11203
  * applies to a frozen field. So `locked` renders differently on purpose: full-opacity (never
10496
11204
  * dimmed — the value is not "unavailable", it is authoritative), no dropdown affordance at
@@ -10573,7 +11281,7 @@ declare class FlyCurrencySelectorComponent implements ControlValueAccessor {
10573
11281
  */
10574
11282
  readonly locked: _angular_core.InputSignal<boolean>;
10575
11283
  /**
10576
- * i18n KEY (never resolved text — see `skills/magic-bar-actions.md` §3.4) explaining
11284
+ * i18n KEY (never resolved text — see `skills/shell-magic-bar.md` §3.4) explaining
10577
11285
  * WHY the control is locked. Omit to use the localized `currency_selector.locked_default_reason`
10578
11286
  * baseline key; supply your own only when that default reason is wrong for your case
10579
11287
  * (mirrors `MagicBarActionSpec.disabledTipKey`'s "supply only when the default is wrong"
@@ -12902,6 +13610,13 @@ declare class FlyDataTableComponent {
12902
13610
  /** `fly-state-message` key when `rows` is empty (omit to render nothing). */
12903
13611
  readonly emptyKey: _angular_core.InputSignal<string | undefined>;
12904
13612
  readonly emptyIconClass: _angular_core.InputSignal<string | undefined>;
13613
+ /**
13614
+ * The selected row's `id`, or `null`/omitted for no selection — the select-then-act model
13615
+ * (`skills/shell-ux-rulebook.md` §1) paints the matching `<tr>` with `--tint-sel` and
13616
+ * `aria-selected`. A page that still opens rows on activation (rather than selecting them) simply
13617
+ * never sets this; the table renders exactly as before.
13618
+ */
13619
+ readonly selectedId: _angular_core.InputSignal<string | null>;
12905
13620
  protected readonly skeletonRows: number[];
12906
13621
  private readonly cellTemplates;
12907
13622
  private readonly templateMap;
@@ -12913,13 +13628,14 @@ declare class FlyDataTableComponent {
12913
13628
  */
12914
13629
  protected onRowActivate(event: Event, row: unknown): void;
12915
13630
  protected cellValue(row: unknown, col: FlyColumn): unknown;
13631
+ protected isSelected(row: unknown): boolean;
12916
13632
  /** Default text rendering — em-dash for absent values, verbatim otherwise. */
12917
13633
  protected displayValue(row: unknown, col: FlyColumn): string;
12918
13634
  protected headerAriaSort(col: FlyColumn): string | null;
12919
13635
  protected sortDirFor(col: FlyColumn): string | null;
12920
13636
  protected onHeaderClick(col: FlyColumn): void;
12921
13637
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyDataTableComponent, never>;
12922
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyDataTableComponent, "fly-data-table", never, { "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "rows": { "alias": "rows"; "required": true; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "ariaLabelKey": { "alias": "ariaLabelKey"; "required": false; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; "emptyKey": { "alias": "emptyKey"; "required": false; "isSignal": true; }; "emptyIconClass": { "alias": "emptyIconClass"; "required": false; "isSignal": true; }; }, { "sortChange": "sortChange"; "rowActivated": "rowActivated"; }, ["cellTemplates"], never, true, never>;
13638
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyDataTableComponent, "fly-data-table", never, { "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "rows": { "alias": "rows"; "required": true; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "ariaLabelKey": { "alias": "ariaLabelKey"; "required": false; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; "emptyKey": { "alias": "emptyKey"; "required": false; "isSignal": true; }; "emptyIconClass": { "alias": "emptyIconClass"; "required": false; "isSignal": true; }; "selectedId": { "alias": "selectedId"; "required": false; "isSignal": true; }; }, { "sortChange": "sortChange"; "rowActivated": "rowActivated"; }, ["cellTemplates"], never, true, never>;
12923
13639
  }
12924
13640
 
12925
13641
  /**
@@ -14314,6 +15030,6 @@ declare const AUDIENCE_ERROR_CODES: {
14314
15030
  };
14315
15031
  type AudienceErrorCode = (typeof AUDIENCE_ERROR_CODES)[keyof typeof AUDIENCE_ERROR_CODES];
14316
15032
 
14317
- 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, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_CURRENCY_DEFAULT_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_OUTLET_DEPTH, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_STRATEGY_OBJECTIVES_LOOKUP_ENDPOINT, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_BREADCRUMBS_EVENT, FLY_WINDOW_BREADCRUMBS_STORE_KEY, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyLifecyclePipelineComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyOverviewKpiRowComponent, FlyOverviewRowsComponent, FlyOverviewSectionComponent, FlyOverviewSurfaceComponent, FlyPagerComponent, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyStrategySelectorComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowBreadcrumbsRegistry, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext$1 as canGoNext, canGoPrev$1 as canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage$1 as clampPage, clampSliderValue, connectRemoteLaunch, deepestFlyMatch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapCurrency, flyUnwrapLenient, flyUnwrapObjectiveLookup, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, matchFlyRoutePrefix, matchFlyRouteTable, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, canGoNext as pagerCanGoNext, canGoPrev as pagerCanGoPrev, clampPage as pagerClampPage, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyOfflineAuth, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
14318
- export type { ActionMenuItem, AgentAction, AgentActionDispatch, AgentActionVerb, AgentChipHostInputs, AgentCommand, AgentCommandContextBinding, AgentCommandHandle, AgentCommandRegistration, AgentCommandScope, AgentCommandSlashSpec, AgentDragPayload, AgentDraggableItem, AgentDropChipMode, AgentDropRendererRegistration, AgentEnvelopeAttachment, AgentMcpScope, AgentMessageEnvelope, AgentPayloadLimits, AgentPayloadValidationResult, AppEveryonePrincipal, AppEveryoneTerm, AppLookup, AppLookupEntry, AudienceEditTarget, AudienceErrorCode, AudienceFilter, AudienceOptions, AudiencePresetKind, AudienceTerm, AudienceTermKind, BreadcrumbItem, ButtonSize, ButtonVariant, CardDensity, CardLayout, ChartTerm, ChildWindowData, ChipTone, ConfirmKind, ConnectRemoteLaunchOptions, ContextMenuAlign, ContextMenuItem, ContextMenuSection, CronAdvancedFieldDef, CronMode, CronParts, CronWeekday, DesktopApp, DesktopAppCategory, DesktopAppKind, DetailSection, DialogResultWithAcknowledgement, EmbeddedSessionProbe, EntityLinkLauncher, EntityLinkSelection, FilterGroup, FlyAchievementRow, FlyApiError, FlyApiResponse, FlyAppHomeLayout, FlyAppModule, FlyAppModuleSection, FlyAppUnavailableState, FlyAuthRefreshPayload, FlyAuthStoreReadable, FlyBadgeTile, FlyBreadcrumbItem, FlyCaptchaChallenge, FlyCaptchaSolution, FlyCaptchaState, FlyCellContext, FlyColumn, FlyColumnKind, FlyComment, FlyCommentDeleteRequest, FlyCommentEditRequest, FlyCommentLoadMoreRequest, FlyCommentLoadRepliesRequest, FlyCommentLockToggleRequest, FlyCommentPage, FlyCommentReportReason, FlyCommentReportRequest, FlyCommentReportStatus, FlyCommentSubmitRequest, FlyCountry, FlyCurrency, FlyCurrencyFetchFn, FlyCurrencySelectorMode, FlyCurrencySelectorValue, FlyDeepLinkPrefetchRoute, FlyDrawerBodyPadding, FlyDrawerPosition, FlyDrawerSide, FlyDrawerSize, FlyDrawerVariant, FlyEmojiCategory, FlyEmojiCell, FlyEmojiEntry, FlyEmojiPack, FlyEmojiPackEntry, FlyEmojiPackManifest, FlyEmojiPickerSize, FlyEmojiPickerTab, FlyEmojiSection, FlyFileInfo, FlyFileSelection, FlyFormAnswer, FlyFormAnswerValue, FlyFormDefinition, FlyFormFieldOptions, FlyFormOption, FlyFormQuestion, FlyFormQuestionType, FlyFormScoreDisplay, FlyFormatMoneyOptions, FlyLaunchEventDetail, FlyLaunchRequestDetail, FlyLeaderboardRow, FlyLiveRefreshOptions, FlyLocaleEntry, FlyMagicBarIconName, FlyMetaItem, FlyModerationLoadPageRequest, FlyModerationLockThreadRequest, FlyModerationOpenSubjectRequest, FlyModerationReport, FlyModerationReportPage, FlyModerationResolutionStatus, FlyModerationResolveRequest, FlyMoneyDisplay, FlyNavigableItem, FlyOfflineAuthConfig, FlyPageMeta, FlyPageResult, FlyPaged, FlyPeoplePickerMode, FlyPeoplePickerOption, FlyPeopleSearchFn, FlyPointsSummary, FlyRemoteContext, FlyRemoteEagerRoute, FlyRemoteLazyRoute, FlyRemoteLoadedComponent, FlyRemoteMatch, FlyRemoteRoute, FlyRemoteRouteEventDetail, FlySearchExpandTrigger, FlySearchInputSize, FlySecureSrcState, FlySelectOption, FlySelectValue, FlySkeletonAnimation, FlySkeletonLayout, FlySkeletonShape, FlySkinTone, FlySort, FlyStandaloneAuthConfig, FlyStrategyObjectiveRef, FlyStrategyObjectiveSearchFn, FlyFileSelection as FlySurveyFileSelection, FlyFormAnswer as FlySurveyFormAnswer, FlyFormAnswerValue as FlySurveyFormAnswerValue, FlyFormDefinition as FlySurveyFormDefinition, FlyFormFieldOptions as FlySurveyFormFieldOptions, FlyFormOption as FlySurveyFormOption, FlyFormQuestion as FlySurveyFormQuestion, FlyFormQuestionType as FlySurveyQuestionType, FlyTenantDefaultCurrencyFetchFn, FlyThemeMode, FlyTooltipPlacement, FlyTreeNavNode, FlyTypeaheadOption, FlyWindowHelpHintEventDetail, FlyWindowHelpPublisher, FlyWindowPageTitleEventDetail, FlyWindowTitlePublisher, FlyosPendingLaunches, FocusRestore, FormBannerKind, GanttDependency, GanttDependencyCreate, GanttDependencyDelete, GanttDependencyType, GanttRow, GanttRowDatesChange, GanttRowKind, GanttZoom, IconButtonVariant, LaunchContext, LifecycleRollupItem, LifecycleStep, LoadBundleOptions, LoadRemoteStylesOptions, LookupDescriptor, LookupHandle, LookupRegistration, LookupResult, LookupSearch, MagicBarAction, MagicBarActionKind, MagicBarActionSpec, MagicBarActionView, MagicBarContribution, MagicBarGroup, MagicBarGroupSpec, MagicBarGroupView, MagicBarOwnerRef, MagicBarPublisher, MagicBarRadioMenu, MagicBarRadioMenuSpec, MagicBarRadioMenuView, MagicBarRadioOption, MagicBarSearch, MagicBarSearchSeed, MagicBarSearchSpec, MagicBarSearchView, MagicBarTextParams, MagicBarTone, MagicBarView, MessageBoxButton, MessageBoxDontAskAgainConfig, MessageBoxOptions, MessageBoxOptionsWithAcknowledgement, MockAuthConfig, OpenWindowOptions, OuPrincipal, OuTerm, OverlayHandle, OverviewKpiItem, OverviewKpiTone, OverviewRow, PageHeaderVariant, PresetTerm, ProgressTone, RemoteAppDef, RequestAppLaunchOptions, RoleOuLookupRow, RolePrincipal, RolesTerm, SegmentedOption, SegmentedVariant, ShareOrgChartOption, ShareOuNode, SharePanelLevelOption, SharePermissionEntry, SharePrincipal, SharePrincipalKind, ShareUserResult, StateMessageKind, StepUpChallenge, StepUpReauthHandler, StepUpReauthRequest, TabsVariant, ToastAction, ToastEntry, ToastOptions, ToastVariant, User, UserPrincipal, UsersTerm, WindowBreadcrumb, WindowHelpHint, WindowInstance, WindowState };
15033
+ 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, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_CURRENCY_DEFAULT_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_GANTT_DEFAULTS, FLY_LAUNCH_CONTEXT_EVENT, FLY_LAUNCH_CONTEXT_STORE_KEY, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_OUTLET_DEPTH, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_STRATEGY_OBJECTIVES_LOOKUP_ENDPOINT, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_BREADCRUMBS_EVENT, FLY_WINDOW_BREADCRUMBS_STORE_KEY, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLaunchContextService, FlyLeaderboardComponent, FlyLifecyclePipelineComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyOverviewKpiRowComponent, FlyOverviewRowsComponent, FlyOverviewSectionComponent, FlyOverviewSurfaceComponent, FlyPagerComponent, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyStrategySelectorComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_LABEL_W_DEFAULT, GANTT_LABEL_W_MAX, GANTT_LABEL_W_MIN, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowBreadcrumbsRegistry, WindowManagerService, applyGanttRowReorder, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext$1 as canGoNext, canGoPrev$1 as canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage$1 as clampPage, clampSliderValue, connectRemoteLaunch, deepestFlyMatch, enterActivatesNatively, filterGanttRows, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapCurrency, flyUnwrapLenient, flyUnwrapObjectiveLookup, ganttOutlineBarItems, ganttPeriodBarItem, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, linkAnchorsFor, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, matchFlyRoutePrefix, matchFlyRouteTable, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeGanttSearchText, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, orderMagicBarItems, overlayStack, canGoNext as pagerCanGoNext, canGoPrev as pagerCanGoPrev, clampPage as pagerClampPage, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyGanttDefaults, provideFlyOfflineAuth, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
15034
+ export type { ActionMenuItem, AgentAction, AgentActionDispatch, AgentActionVerb, AgentChipHostInputs, AgentCommand, AgentCommandContextBinding, AgentCommandHandle, AgentCommandRegistration, AgentCommandScope, AgentCommandSlashSpec, AgentDragPayload, AgentDraggableItem, AgentDropChipMode, AgentDropRendererRegistration, AgentEnvelopeAttachment, AgentMcpScope, AgentMessageEnvelope, AgentPayloadLimits, AgentPayloadValidationResult, AppEveryonePrincipal, AppEveryoneTerm, AppLookup, AppLookupEntry, AudienceEditTarget, AudienceErrorCode, AudienceFilter, AudienceOptions, AudiencePresetKind, AudienceTerm, AudienceTermKind, BreadcrumbItem, ButtonSize, ButtonVariant, CardDensity, CardLayout, ChartTerm, ChildWindowData, ChipTone, ConfirmKind, ConnectRemoteLaunchOptions, ContextMenuAlign, ContextMenuItem, ContextMenuSection, CronAdvancedFieldDef, CronMode, CronParts, CronWeekday, DesktopApp, DesktopAppCategory, DesktopAppKind, DetailSection, DialogResultWithAcknowledgement, EmbeddedSessionProbe, EntityLinkLauncher, EntityLinkSelection, FilterGroup, FlyAchievementRow, FlyApiError, FlyApiResponse, FlyAppHomeLayout, FlyAppModule, FlyAppModuleSection, FlyAppUnavailableState, FlyAuthRefreshPayload, FlyAuthStoreReadable, FlyBadgeTile, FlyBreadcrumbItem, FlyCaptchaChallenge, FlyCaptchaSolution, FlyCaptchaState, FlyCellContext, FlyColumn, FlyColumnKind, FlyComment, FlyCommentDeleteRequest, FlyCommentEditRequest, FlyCommentLoadMoreRequest, FlyCommentLoadRepliesRequest, FlyCommentLockToggleRequest, FlyCommentPage, FlyCommentReportReason, FlyCommentReportRequest, FlyCommentReportStatus, FlyCommentSubmitRequest, FlyCountry, FlyCurrency, FlyCurrencyFetchFn, FlyCurrencySelectorMode, FlyCurrencySelectorValue, FlyDeepLinkPrefetchRoute, FlyDrawerBodyPadding, FlyDrawerPosition, FlyDrawerSide, FlyDrawerSize, FlyDrawerVariant, FlyEmojiCategory, FlyEmojiCell, FlyEmojiEntry, FlyEmojiPack, FlyEmojiPackEntry, FlyEmojiPackManifest, FlyEmojiPickerSize, FlyEmojiPickerTab, FlyEmojiSection, FlyFileInfo, FlyFileSelection, FlyFormAnswer, FlyFormAnswerValue, FlyFormDefinition, FlyFormFieldOptions, FlyFormOption, FlyFormQuestion, FlyFormQuestionType, FlyFormScoreDisplay, FlyFormatMoneyOptions, FlyGanttDefaults, FlyLaunchEventDetail, FlyLaunchRequestDetail, FlyLeaderboardRow, FlyLiveRefreshOptions, FlyLocaleEntry, FlyMagicBarIconName, FlyMetaItem, FlyModerationLoadPageRequest, FlyModerationLockThreadRequest, FlyModerationOpenSubjectRequest, FlyModerationReport, FlyModerationReportPage, FlyModerationResolutionStatus, FlyModerationResolveRequest, FlyMoneyDisplay, FlyNavigableItem, FlyOfflineAuthConfig, FlyPageMeta, FlyPageResult, FlyPaged, FlyPeoplePickerMode, FlyPeoplePickerOption, FlyPeopleSearchFn, FlyPointsSummary, FlyRemoteContext, FlyRemoteEagerRoute, FlyRemoteLazyRoute, FlyRemoteLoadedComponent, FlyRemoteMatch, FlyRemoteRoute, FlyRemoteRouteEventDetail, FlySearchExpandTrigger, FlySearchInputSize, FlySecureSrcState, FlySelectOption, FlySelectValue, FlySkeletonAnimation, FlySkeletonLayout, FlySkeletonShape, FlySkinTone, FlySort, FlyStandaloneAuthConfig, FlyStrategyObjectiveRef, FlyStrategyObjectiveSearchFn, FlyFileSelection as FlySurveyFileSelection, FlyFormAnswer as FlySurveyFormAnswer, FlyFormAnswerValue as FlySurveyFormAnswerValue, FlyFormDefinition as FlySurveyFormDefinition, FlyFormFieldOptions as FlySurveyFormFieldOptions, FlyFormOption as FlySurveyFormOption, FlyFormQuestion as FlySurveyFormQuestion, FlyFormQuestionType as FlySurveyQuestionType, FlyTenantDefaultCurrencyFetchFn, FlyThemeMode, FlyTooltipPlacement, FlyTreeNavNode, FlyTypeaheadOption, FlyWindowHelpHintEventDetail, FlyWindowHelpPublisher, FlyWindowPageTitleEventDetail, FlyWindowTitlePublisher, FlyosPendingLaunches, FocusRestore, FormBannerKind, GanttDependency, GanttDependencyCreate, GanttDependencyDelete, GanttDependencyType, GanttLinkVm, GanttRow, GanttRowDatesChange, GanttRowIndentChange, GanttRowKind, GanttRowReorder, GanttZoom, IconButtonVariant, LaunchContext, LifecycleRollupItem, LifecycleStep, LoadBundleOptions, LoadRemoteStylesOptions, LookupDescriptor, LookupHandle, LookupRegistration, LookupResult, LookupSearch, MagicBarAction, MagicBarActionKind, MagicBarActionSpec, MagicBarActionView, MagicBarContribution, MagicBarGroup, MagicBarGroupSpec, MagicBarGroupView, MagicBarOwnerRef, MagicBarPublisher, MagicBarRadioMenu, MagicBarRadioMenuSpec, MagicBarRadioMenuView, MagicBarRadioOption, MagicBarSearch, MagicBarSearchSeed, MagicBarSearchSpec, MagicBarSearchView, MagicBarTextParams, MagicBarTone, MagicBarView, MessageBoxButton, MessageBoxDontAskAgainConfig, MessageBoxOptions, MessageBoxOptionsWithAcknowledgement, MockAuthConfig, OpenWindowOptions, OuPrincipal, OuTerm, OverlayHandle, OverviewKpiItem, OverviewKpiTone, OverviewRow, PageHeaderVariant, PresetTerm, ProgressTone, RemoteAppDef, RequestAppLaunchOptions, RoleOuLookupRow, RolePrincipal, RolesTerm, SegmentedOption, SegmentedVariant, ShareOrgChartOption, ShareOuNode, SharePanelLevelOption, SharePermissionEntry, SharePrincipal, SharePrincipalKind, ShareUserResult, StateMessageKind, StepUpChallenge, StepUpReauthHandler, StepUpReauthRequest, TabsVariant, ToastAction, ToastEntry, ToastOptions, ToastVariant, User, UserPrincipal, UsersTerm, WindowBreadcrumb, WindowHelpHint, WindowInstance, WindowState };
14319
15035
  //# sourceMappingURL=flyos-design-system.d.ts.map