@flyos/design-system 3.13.0 → 3.14.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';
@@ -6569,6 +6569,43 @@ interface GanttDependencyCreate {
6569
6569
  toId: string;
6570
6570
  type: GanttDependencyType;
6571
6571
  }
6572
+ /**
6573
+ * Payload of {@link FlyGanttComponent.rowReorder} — a label row dragged to a new place in the tree.
6574
+ *
6575
+ * Carries the FULL new sibling order rather than a `before`/`after` hint, because that is the shape
6576
+ * every ordering endpoint actually takes (an ordered, complete id set) and because re-deriving it
6577
+ * consumer-side is where the chart's order and the consumer's start to disagree. `parentId` is the
6578
+ * parent AFTER the move — `null` for a root row — so a drop that also re-homes the row (dragging a
6579
+ * task from one summary group into another) is one event, not two.
6580
+ *
6581
+ * The chart does NOT judge whether a level change is legal: only the consumer knows whether its
6582
+ * two outline levels are the same kind of thing. Reject the move by simply not persisting it; the
6583
+ * rows are re-rendered from the consumer's own input either way, so nothing is left inconsistent.
6584
+ */
6585
+ interface GanttRowReorder {
6586
+ /** The row that moved. */
6587
+ readonly id: string;
6588
+ /** Its parent after the move; `null` = a root row. */
6589
+ readonly parentId: string | null;
6590
+ /** Zero-based slot among `parentId`'s children after the move. */
6591
+ readonly index: number;
6592
+ /** Every child of `parentId` after the move, in their new order (includes {@link id}). */
6593
+ readonly siblingIds: readonly string[];
6594
+ }
6595
+ /**
6596
+ * Payload of {@link FlyGanttComponent.rowIndentChange} — the outline-level nudge behind an
6597
+ * indent / outdent action.
6598
+ *
6599
+ * `delta` is `+1` (indent — become a child of the row above) or `-1` (outdent — become a sibling of
6600
+ * the current parent). `parentId` is the resulting parent, resolved by the chart from the row's
6601
+ * neighbours so every consumer does not re-implement "which row would adopt it": `null` means the
6602
+ * row lands at the root.
6603
+ */
6604
+ interface GanttRowIndentChange {
6605
+ readonly id: string;
6606
+ readonly delta: 1 | -1;
6607
+ readonly parentId: string | null;
6608
+ }
6572
6609
  /** Payload of {@link FlyGanttComponent.dependencyDelete} — the edge the user removed. */
6573
6610
  interface GanttDependencyDelete {
6574
6611
  fromId: string;
@@ -6605,6 +6642,10 @@ interface GanttFlatRow {
6605
6642
  hasChildren: boolean;
6606
6643
  collapsed: boolean;
6607
6644
  }
6645
+ interface ResolvedDates {
6646
+ start: Date | null;
6647
+ end: Date | null;
6648
+ }
6608
6649
  /** Render shape a row resolves to — decides which geometry fields on its VM are meaningful. */
6609
6650
  type RowShape = 'bar' | 'milestone' | 'group' | 'empty';
6610
6651
  /** Which end of a bar a link gesture grabbed / was dropped on. */
@@ -6718,6 +6759,90 @@ interface GanttRowVm {
6718
6759
  /** Baseline overlay geometry, or `null` when the row has none (or baselines are hidden). */
6719
6760
  baseline: GanttBaselineVm | null;
6720
6761
  }
6762
+ /**
6763
+ * What the geometry needs from its host, as signals rather than a component reference — the same
6764
+ * one-way shape {@link GanttLabelPane} and {@link GanttLinkGestures} take.
6765
+ */
6766
+ interface GanttRowGeometryDeps {
6767
+ readonly rowHeight: Signal<number>;
6768
+ readonly rtl: Signal<boolean>;
6769
+ readonly domain: Signal<GanttDomain>;
6770
+ readonly pxPerDay: Signal<number>;
6771
+ readonly innerWidth: Signal<number>;
6772
+ readonly showBaselines: Signal<boolean>;
6773
+ readonly readonly: Signal<boolean>;
6774
+ /** Every row's effective `{start,end}` (`resolveRowDates`), including derived group spans. */
6775
+ readonly resolvedDates: Signal<ReadonlyMap<string, ResolvedDates>>;
6776
+ /** The in-flight bar drag, folded into the geometry so the bar follows the pointer. */
6777
+ readonly dragPreview: Signal<{
6778
+ id: string;
6779
+ start: Date;
6780
+ end: Date;
6781
+ } | null>;
6782
+ /** The row-band tint, already faded to its low alpha (or `null`). */
6783
+ readonly tintFor: (row: GanttRow) => string | null;
6784
+ /** The localized accessible label for a row. */
6785
+ readonly ariaFor: (row: GanttRow, start: Date | null, end: Date | null, shape: RowShape) => string;
6786
+ }
6787
+ /**
6788
+ * Render-space geometry for a Gantt row: the view-model one flattened row resolves to, and every
6789
+ * shape derived from it.
6790
+ *
6791
+ * ## Why this is not in the component
6792
+ * `fly-gantt` composes its gestures out of one-concern classes already ({@link GanttLabelPane},
6793
+ * {@link GanttLinkGestures}, {@link GanttViewport}, {@link GanttPan}) and this is the matching
6794
+ * carve on the RENDER side: everything here is "given a row and the current scale, where is it and
6795
+ * what shape is it", with no knowledge of selection, collapse, windowing, events or i18n beyond the
6796
+ * one label callback. Keeping it here means the component's remaining bulk is state and wiring, and
6797
+ * the geometry — the part that is actually hard to read — sits beside the scale math it calls.
6798
+ *
6799
+ * ## RTL
6800
+ * Nothing here branches on direction except through `mapX`/`projectBar` and the two `sign` terms
6801
+ * below, because the whole coordinate space is pre-mirrored (see `gantt-scale.ts`). The
6802
+ * consequence worth remembering: under RTL a row's `startX` is GREATER than its `endX`.
6803
+ */
6804
+ declare class GanttRowGeometry {
6805
+ private readonly deps;
6806
+ constructor(deps: GanttRowGeometryDeps);
6807
+ /** Bar height for the current row height — the band minus its vertical padding. */
6808
+ readonly barHeight: Signal<number>;
6809
+ /** Full geometry for one row — used for rendered rows AND for link endpoints outside the window. */
6810
+ forFlat(flat: GanttFlatRow, index: number): GanttRowVm;
6811
+ barTop(vm: GanttRowVm): number;
6812
+ /** Baseline underbar's top edge — a thin strip directly beneath the main bar. */
6813
+ baselineBarTop(vm: GanttRowVm): number;
6814
+ private milestoneR;
6815
+ /** Diamond polygon points for a milestone marker. */
6816
+ diamondPoints(vm: GanttRowVm): string;
6817
+ /** Hollow outline diamond for a milestone row's baseline date (its own x, same row's y). */
6818
+ baselineDiamondPoints(vm: GanttRowVm): string;
6819
+ /**
6820
+ * Link connector x for a milestone. A diamond has no width, so the connector is nudged clear
6821
+ * of the marker in the forward-in-time direction (which flips under RTL).
6822
+ */
6823
+ milestoneConnectorX(vm: GanttRowVm): number;
6824
+ /**
6825
+ * Connector x for one END of a bar, pushed into the gutter BESIDE the bar rather than sitting on
6826
+ * its edge (see {@link CONNECTOR_GAP}). Forward-in-time is `+x` LTR and `-x` RTL, so the start
6827
+ * connector always lands before the bar and the finish connector after it, in reading order.
6828
+ */
6829
+ connectorX(vm: GanttRowVm, anchor: 'start' | 'finish'): number;
6830
+ /** Summary-bracket path (thin bar + down-turned end caps) for a group row. */
6831
+ groupPath(vm: GanttRowVm): string;
6832
+ /** Inline-start label indent for a tree depth. */
6833
+ indentFor(depth: number): number;
6834
+ }
6835
+
6836
+ /** Which part of a bar the press grabbed. */
6837
+ type GanttBarDragMode = 'move' | 'resize-start' | 'resize-end';
6838
+ /** The follow-the-cursor date readout shown while a drag runs. */
6839
+ interface GanttDragTooltip {
6840
+ readonly x: number;
6841
+ readonly y: number;
6842
+ readonly text: string;
6843
+ }
6844
+
6845
+ /** One dependency arrow, routed and ready for the template. */
6721
6846
  interface LinkVm {
6722
6847
  key: string;
6723
6848
  fromId: string;
@@ -6762,9 +6887,21 @@ interface LinkVm {
6762
6887
  declare class FlyGanttComponent {
6763
6888
  readonly i18n: I18nService;
6764
6889
  private readonly destroyRef;
6890
+ /**
6891
+ * App-level presentation defaults, when the consuming app provided any. Optional by
6892
+ * construction: a chart with no provider behaves exactly as it did before the token existed.
6893
+ */
6894
+ private readonly appDefaults;
6765
6895
  readonly rows: _angular_core.InputSignal<readonly GanttRow[]>;
6766
6896
  readonly dependencies: _angular_core.InputSignal<readonly GanttDependency[]>;
6767
- readonly zoom: _angular_core.InputSignal<GanttZoom>;
6897
+ /**
6898
+ * Time-grid resolution. `null` defers to {@link FlyGanttDefaults.zoom}, then to `'week'`.
6899
+ *
6900
+ * Widened from `GanttZoom` to `GanttZoom | null` so the same three-step precedence
6901
+ * (input → app default → built-in) applies to every presentation input. Passing a concrete zoom
6902
+ * is unchanged.
6903
+ */
6904
+ readonly zoom: _angular_core.InputSignal<GanttZoom | null>;
6768
6905
  /** Draw the vertical "today" marker when today falls inside the domain. */
6769
6906
  readonly showToday: _angular_core.InputSignal<boolean>;
6770
6907
  /** Globally disable drag/resize/link (individual rows can also be `readonly`). */
@@ -6774,11 +6911,11 @@ declare class FlyGanttComponent {
6774
6911
  * divider from there; once they have, this input only re-seeds the pane if
6775
6912
  * {@link resetLabelWidth} is called (double-clicking the divider does exactly that).
6776
6913
  */
6777
- readonly labelWidth: _angular_core.InputSignal<number>;
6914
+ readonly labelWidth: _angular_core.InputSignal<number | null>;
6778
6915
  /** Let the user drag the divider between the label pane and the time grid. */
6779
6916
  readonly resizableLabels: _angular_core.InputSignal<boolean>;
6780
- /** Row band height in px. */
6781
- readonly rowHeight: _angular_core.InputSignal<number>;
6917
+ /** Row band height in px. `null` defers to {@link FlyGanttDefaults.rowHeight}, then to `34`. */
6918
+ readonly rowHeight: _angular_core.InputSignal<number | null>;
6782
6919
  /** Hard cap on the flattened row list; beyond it the list is truncated (see class doc).
6783
6920
  * `0` (the default) is uncapped — windowing (below) is what keeps an uncapped chart cheap. */
6784
6921
  readonly maxRows: _angular_core.InputSignal<number>;
@@ -6793,6 +6930,31 @@ declare class FlyGanttComponent {
6793
6930
  readonly virtualized: _angular_core.InputSignal<boolean>;
6794
6931
  /** Render the baseline underbar/diamond for rows carrying `baselineStart`/`baselineEnd`. */
6795
6932
  readonly showBaselines: _angular_core.InputSignal<boolean>;
6933
+ /**
6934
+ * Render a search field in the pinned corner that filters the label tree.
6935
+ *
6936
+ * Filtering preserves tree CONTEXT rather than flattening to a hit list — see
6937
+ * `gantt-search.ts` for why a plain `rows.filter()` silently re-parents matching children and
6938
+ * de-groups matching parents. A live query also expands every group (a match hidden inside a
6939
+ * collapsed summary is indistinguishable from no match at all) and suspends drag-reorder, since
6940
+ * a filtered sibling list is not a complete one to reorder against.
6941
+ *
6942
+ * `null` defers to {@link FlyGanttDefaults.searchable}, then to `false`.
6943
+ */
6944
+ readonly searchable: _angular_core.InputSignal<boolean | null>;
6945
+ /**
6946
+ * Let the user drag label rows to reorder / re-home them, emitting {@link rowReorder}.
6947
+ *
6948
+ * `null` defers to {@link FlyGanttDefaults.reorderable}, then to `false` — the chart never
6949
+ * offers a gesture whose result nobody is persisting.
6950
+ */
6951
+ readonly reorderable: _angular_core.InputSignal<boolean | null>;
6952
+ /**
6953
+ * The label-tree filter text. Two-way bindable, so a consumer can seed it from a deep link or
6954
+ * clear it on navigation; the built-in field writes it as the user types (debounced by
6955
+ * `fly-search-input`). Ignored unless {@link searchable}.
6956
+ */
6957
+ readonly searchQuery: _angular_core.ModelSignal<string>;
6796
6958
  /** Fires on a committed bar move / resize with the new ISO `start`/`end`. */
6797
6959
  readonly rowDatesChange: _angular_core.OutputEmitterRef<GanttRowDatesChange>;
6798
6960
  /**
@@ -6812,32 +6974,72 @@ declare class FlyGanttComponent {
6812
6974
  readonly rowExpand: _angular_core.OutputEmitterRef<string>;
6813
6975
  /** The component coarsened the requested zoom to stay under `maxCanvasPx`. */
6814
6976
  readonly effectiveZoomChange: _angular_core.OutputEmitterRef<GanttZoom>;
6977
+ /**
6978
+ * A label row was dragged to a new place. Carries the complete new sibling order, so a consumer
6979
+ * persists it with one ordered-set write. See {@link GanttRowReorder}.
6980
+ */
6981
+ readonly rowReorder: _angular_core.OutputEmitterRef<GanttRowReorder>;
6982
+ /**
6983
+ * A row's outline level was nudged by {@link indentRow} / {@link outdentRow}, with the resulting
6984
+ * parent already resolved from the row's neighbours. The chart does not re-render on its own —
6985
+ * like every other gesture here, the consumer persists and feeds new `rows` back in.
6986
+ */
6987
+ readonly rowIndentChange: _angular_core.OutputEmitterRef<GanttRowIndentChange>;
6815
6988
  private readonly _uid;
6816
6989
  readonly markerId: string;
6817
6990
  readonly HEADER_H: number;
6818
6991
  readonly HEADER_UPPER_H = 22;
6819
6992
  readonly HEADER_LOWER_H = 22;
6820
6993
  readonly HANDLE_W = 8;
6994
+ /** Connector radius — small, and parked outside the bar, so it never steals the resize
6995
+ * handle's press (`gantt-row-vm.ts`'s CONNECTOR_R note). */
6996
+ readonly CONNECTOR_R = 3;
6821
6997
  readonly MIN_LABEL_W = 140;
6822
6998
  readonly MAX_LABEL_W = 720;
6823
6999
  readonly BASELINE_BAR_H: number;
6824
7000
  readonly selectedId: _angular_core.WritableSignal<string | null>;
6825
7001
  /** Uncontrolled collapse state — the source of truth whenever the `collapsedIds` input is `null`. */
6826
7002
  private readonly _uncontrolledCollapsed;
6827
- /** Live drag preview `{id,start,end}` folded into geometry while a gesture runs. */
7003
+ /** Live drag preview `{id,start,end}` folded into geometry while a bar gesture runs, and the
7004
+ * cursor-following date readout beside it — both owned by {@link GanttBarDrag}, re-exposed here
7005
+ * because the row geometry reads the first and the template renders the second. */
6828
7006
  private readonly _dragPreview;
6829
- readonly dragTooltip: _angular_core.WritableSignal<{
6830
- x: number;
6831
- y: number;
6832
- text: string;
6833
- } | null>;
7007
+ readonly dragTooltip: _angular_core.Signal<GanttDragTooltip | null>;
6834
7008
  private readonly bodyRef;
6835
7009
  private readonly scrollElRef;
6836
7010
  private readonly canvasElRef;
6837
7011
  private readonly labelsElRef;
6838
7012
  readonly rtl: _angular_core.Signal<boolean>;
7013
+ /** The zoom the consumer asked for, before `maxCanvasPx` coarsening ({@link effectiveZoom}). */
7014
+ readonly requestedZoom: _angular_core.Signal<GanttZoom>;
7015
+ /** Row band height actually rendered. */
7016
+ readonly effectiveRowHeight: _angular_core.Signal<number>;
7017
+ /** The width the label pane falls back to until the user drags the divider. */
7018
+ private readonly _labelWidthInput;
7019
+ /** Whether the corner search field is rendered at all. */
7020
+ readonly searchEnabled: _angular_core.Signal<boolean>;
7021
+ /** The live filter text, or `''` when searching is off — the one signal every filter path reads. */
7022
+ readonly activeQuery: _angular_core.Signal<string>;
7023
+ /**
7024
+ * Whether a label row can be dragged right now.
7025
+ *
7026
+ * Suspended while a search is live, deliberately: {@link rowReorder} emits the COMPLETE sibling
7027
+ * order, and a filtered tree only holds the siblings that matched. Persisting that list would
7028
+ * quietly drop every row the filter is hiding — the sort of data loss whose cause is invisible
7029
+ * afterwards. A read-only chart never reorders either.
7030
+ */
7031
+ readonly reorderEnabled: _angular_core.Signal<boolean>;
6839
7032
  /** Effective collapse set: the controlled input when the consumer supplied one, else internal state. */
6840
7033
  private readonly _collapsed;
7034
+ /**
7035
+ * The consumer's rows narrowed by the live search filter (identity-stable when not searching).
7036
+ *
7037
+ * NOTE the domain and the resolved dates below deliberately stay on the UNFILTERED `rows()`: a
7038
+ * timeline whose axis and whose summary spans jumped on every keystroke would make the filter
7039
+ * feel like it was editing the plan. Filtering decides which rows are LISTED, not what the dates
7040
+ * are.
7041
+ */
7042
+ readonly filteredRows: _angular_core.Signal<readonly GanttRow[]>;
6841
7043
  /** Full visible list after tree flatten/collapse (no `maxRows` cap applied yet). */
6842
7044
  private readonly _flat;
6843
7045
  /** `_flat`, capped at `maxRows` when it is positive — unchanged from the pre-A9 contract. */
@@ -6882,26 +7084,15 @@ declare class FlyGanttComponent {
6882
7084
  * window with one endpoint outside it — the memo §4.3 rule-2 fix for the pre-A9 "both
6883
7085
  * endpoints rendered" predicate, which would erase exactly those crossing arrows. */
6884
7086
  readonly linkVms: _angular_core.Signal<LinkVm[]>;
6885
- /** Full geometry for one row — shared by `rowVms` and `linkVms` (endpoints outside the window). */
6886
- private _vmForFlat;
6887
- 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
7087
  /**
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).
7088
+ * Render-space geometry, in {@link GanttRowGeometry} the row view-model plus every shape
7089
+ * derived from it. Composed for the same reason as the gestures below it: given a row and the
7090
+ * current scale it answers "where is this and what shape is it", and knows nothing about
7091
+ * selection, collapse, windowing or events. `geo` is public because the TEMPLATE calls it.
6899
7092
  */
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;
7093
+ readonly geo: GanttRowGeometry;
7094
+ /** Bar height for the current row height bound directly by the template. */
7095
+ readonly barHeight: _angular_core.Signal<number>;
6905
7096
  /**
6906
7097
  * The divider's own state machine, in {@link GanttLabelPane}. It is composed rather than
6907
7098
  * inlined because it is the one region of this component with no coupling to the chart: give it
@@ -6936,12 +7127,29 @@ declare class FlyGanttComponent {
6936
7127
  onLabelRowKeydown(ev: Event, id: string): void;
6937
7128
  onRowClick(id: string): void;
6938
7129
  onRowDblClick(id: string): void;
7130
+ /**
7131
+ * Press on a row's own SHAPE — the group bracket today; bars and milestones reach the same place
7132
+ * through {@link onBarPointerDown}, which additionally starts a drag.
7133
+ *
7134
+ * `stopPropagation` is what keeps this apart from the background pan: the full-width lane rect
7135
+ * behind every row is deliberately NOT a selection target any more (clicking the empty part of a
7136
+ * row is a pan, not a pick), so only a press that lands on a painted shape may select.
7137
+ */
7138
+ onShapePointerDown(ev: PointerEvent, id: string): void;
7139
+ /** Settled (debounced) text from the corner search field. */
7140
+ onSearchQueryChange(query: string): void;
6939
7141
  onLinkClick(ev: Event, vm: LinkVm): void;
6940
7142
  deleteLink(ev: Event, vm: LinkVm): void;
6941
7143
  onGridKeydown(ev: KeyboardEvent): void;
6942
7144
  /** Shift both dates of the selected bar by `deltaDays` and emit (respecting read-only). */
6943
7145
  private _nudgeSelected;
6944
- onBarPointerDown(ev: PointerEvent, vm: GanttRowVm, mode: 'move' | 'resize-start' | 'resize-end'): void;
7146
+ /**
7147
+ * Moving and resizing a bar, in {@link GanttBarDrag} — the last of the three pointer gestures
7148
+ * to be composed rather than inlined, for the same reason as the other two: it needs a scale, a
7149
+ * direction and somewhere to report a committed pair of dates, and nothing else about the chart.
7150
+ */
7151
+ private readonly _barDrag;
7152
+ onBarPointerDown(ev: PointerEvent, vm: GanttRowVm, mode: GanttBarDragMode): void;
6945
7153
  /**
6946
7154
  * The dependency-drawing gesture, in {@link GanttLinkGestures}. Composed rather than inlined
6947
7155
  * because it is a self-contained second gesture whose interesting part is a RULE — the end you
@@ -6957,1582 +7165,1829 @@ declare class FlyGanttComponent {
6957
7165
  readonly linkGesturePath: _angular_core.Signal<string | null>;
6958
7166
  onLinkPointerDown(ev: PointerEvent, vm: GanttRowVm, anchor: GanttLinkAnchor): void;
6959
7167
  /**
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.
6963
- */
6964
- private _tintFor;
7168
+ * Ctrl+wheel scrolls horizontally and a press-drag on empty chart space pans, both in
7169
+ * {@link GanttPan}. Composed for the same reason as the other two gestures: it needs a scrolling
7170
+ * element and nothing else about the chart, and its two subtleties (a non-passive wheel listener
7171
+ * is the only kind that can out-vote browser zoom; a pan must swallow its own trailing click)
7172
+ * read better beside each other than scattered through this class.
7173
+ */
7174
+ private readonly _pan;
7175
+ /** True mid drag-pan — drives the host's grabbing cursor. */
7176
+ readonly panning: _angular_core.WritableSignal<boolean>;
7177
+ /**
7178
+ * Press on the chart background. Every interactive shape stops propagation on its own
7179
+ * `pointerdown`, so only genuinely empty space (the lane rects, the weekend bands, the
7180
+ * gridlines, the canvas itself) ever reaches this.
7181
+ */
7182
+ onPanPointerDown(ev: PointerEvent): void;
7183
+ /**
7184
+ * The flattened rows in the narrow shape the reorder resolver needs.
7185
+ *
7186
+ * `parentId` is recomputed from the rendered DEPTHS rather than copied off `GanttRow.parentId`,
7187
+ * and the difference is load-bearing: `flattenRows` treats an unknown, self- or cyclic parent as
7188
+ * a root, so a row can render at depth 0 while still carrying a `parentId` string. Trusting the
7189
+ * field would have the resolver build a sibling list for a parent that is not on screen.
7190
+ */
7191
+ private readonly _reorderRows;
7192
+ private readonly _reorder;
7193
+ /** Id of the row being dragged, or `null`. */
7194
+ readonly reorderDraggingId: _angular_core.WritableSignal<string | null>;
7195
+ /** Absolute row-space `y` of the drop indicator, or `null` when no drag is live. */
7196
+ readonly reorderIndicatorTop: _angular_core.Signal<number | null>;
7197
+ onLabelRowPointerDown(ev: PointerEvent, id: string): void;
7198
+ /** Whether `id` can become a child of the row above it (see `canIndentGanttRow`). */
7199
+ canIndentRow(id: string): boolean;
7200
+ /** Whether `id` can be lifted out of its parent — i.e. whether it has one. */
7201
+ canOutdentRow(id: string): boolean;
7202
+ /** Reactive twins for the currently selected row — what a magic-bar publisher binds to. */
7203
+ readonly canIndentSelected: _angular_core.Signal<boolean>;
7204
+ readonly canOutdentSelected: _angular_core.Signal<boolean>;
7205
+ /** Make `id` a child of the row directly above it, if it has an eligible sibling there. */
7206
+ indentRow(id: string): void;
7207
+ /** Lift `id` out to its grandparent's level (`null` = the root). */
7208
+ outdentRow(id: string): void;
6965
7209
  private _mapTicks;
7210
+ /** Bridges the pure a11y label builder to this component's localizer + Date-typed geometry. */
6966
7211
  private _ariaFor;
6967
7212
  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>;
7213
+ 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
7214
  }
6970
7215
 
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
7216
  /**
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.
7217
+ * App-level defaults for every {@link FlyGanttComponent} under a given injector.
7218
+ *
7219
+ * ## Why a token and not "just use the input"
7220
+ * Every field here is already a per-instance `input()`, and for a one-off chart that is the right
7221
+ * knob. But a planning app draws the SAME chart on four or five screens (a project plan, a
7222
+ * schedule, a portfolio roadmap, a programme rollup), and the presentation decisions — how wide the
7223
+ * label pane starts, how tall a row band is, whether the label pane is searchable — are properties
7224
+ * of the APP, not of any one screen. Repeating them at every call site is how the fifth screen ends
7225
+ * up with a 240px pane while the other four use 360, with no single place that says which is right.
7226
+ *
7227
+ * So: provide once at the app (or a feature-area) injector, override per instance where a screen
7228
+ * genuinely differs. An explicit input ALWAYS wins over the token, and the token over the built-in
7229
+ * default — the plain three-step precedence, stated once in `FlyGanttComponent`'s resolvers.
7230
+ *
7231
+ * ```ts
7232
+ * // app.config.ts
7233
+ * providers: [provideFlyGanttDefaults({ labelWidth: 360, searchable: true })]
7234
+ * ```
7235
+ *
7236
+ * Every field is optional: a partial object overrides only what it names, so an app can move the
7237
+ * label width without also inheriting an opinion about row height.
7002
7238
  */
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[];
7239
+ interface FlyGanttDefaults {
7240
+ /**
7241
+ * Initial inline-start label-pane width in px, before the user drags the divider. Clamped into
7242
+ * `[GANTT_LABEL_W_MIN, GANTT_LABEL_W_MAX]` like every other width source.
7243
+ */
7244
+ readonly labelWidth?: number;
7245
+ /** Row band height in px. */
7246
+ readonly rowHeight?: number;
7247
+ /** Time-grid resolution a chart opens at. */
7248
+ readonly zoom?: GanttZoom;
7249
+ /** Render the label-pane search field (see `FlyGanttComponent.searchable`). */
7250
+ readonly searchable?: boolean;
7251
+ /** Allow dragging label rows to reorder them (see `FlyGanttComponent.reorderable`). */
7252
+ readonly reorderable?: boolean;
7026
7253
  }
7027
- /** How an opted-in `[showScore]` host renders a choice option's points. */
7028
- type FlyFormScoreDisplay = 'inline' | 'badge';
7029
7254
  /**
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`)
7255
+ * DI token carrying {@link FlyGanttDefaults}. Optional everywhere a chart with no provider falls
7256
+ * back to the component's own built-in defaults, which is what every pre-existing consumer gets.
7039
7257
  */
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
- }
7258
+ declare const FLY_GANTT_DEFAULTS: InjectionToken<FlyGanttDefaults>;
7259
+ /** Provider helper — `providers: [provideFlyGanttDefaults({ labelWidth: 360 })]`. */
7260
+ declare function provideFlyGanttDefaults(defaults: FlyGanttDefaults): Provider;
7261
+
7262
+ /** Label-pane width bounds (px) the splitter clamps to. */
7263
+ declare const GANTT_LABEL_W_MIN = 140;
7264
+ declare const GANTT_LABEL_W_MAX = 720;
7053
7265
  /**
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.
7266
+ * Width (px) a label pane opens at when neither the consumer's `labelWidth` input nor an app-level
7267
+ * `FlyGanttDefaults` says otherwise.
7268
+ *
7269
+ * Raised from the original 240. A Gantt label is a full work-breakdown title, routinely carrying a
7270
+ * prefix the consumer composed in (PPM puts the assignee's initials on every task row and a weight
7271
+ * suffix on every milestone), and at 240px the ellipsis landed inside the NAME for a majority of
7272
+ * real rows — which turns the pane into a list of indistinguishable truncations rather than a
7273
+ * readable outline. It remains an INITIAL value only: the divider drag, the keyboard resize and
7274
+ * `resetLabelWidth()` all behave exactly as before.
7057
7275
  */
7058
- interface FlyFileSelection {
7059
- questionId: string;
7060
- files: File[];
7061
- }
7276
+ declare const GANTT_LABEL_W_DEFAULT = 320;
7062
7277
 
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
7278
  /**
7073
- * **`fly-dynamic-form`** the design-system data-contract-driven form renderer.
7279
+ * Magic-bar contribution contract (UX v2 "Nova", D5 §4).
7074
7280
  *
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.
7281
+ * The magic bar is the shell's top-bar toolbar pill. Unlike the fixed right
7282
+ * cluster (Help / Tasks / Notifications / assistant orb shell-owned, an app can
7283
+ * never remove it), the pill's contents belong to **whatever view the user is
7284
+ * looking at**, and the design's reference implementation re-publishes the whole
7285
+ * set on every selection, tab and save (`pubTabActions`). Two consequences shape
7286
+ * every type below:
7083
7287
  *
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"`.
7288
+ * 1. **A contribution is a full replace, never a patch.** There is no "add one
7289
+ * action" call, because the publisher never knows what the previous view left
7290
+ * behind. That also collapses D5's `search: null` ("suppress") vs `search`
7291
+ * absent distinction under full replace both simply mean "this view offers
7292
+ * no search".
7293
+ * 2. **Render data and behaviour are separated in the type system.** Each family
7294
+ * has a `*Spec` base carrying ONLY the fields that affect what is painted;
7295
+ * the publisher-facing type extends it with callbacks, and the shell-facing
7296
+ * `*View` type extends it with bound invokers. The base IS the equality
7297
+ * surface {@link MagicBarRegistry} dedupes on — a re-publish whose specs are
7298
+ * unchanged must not disturb the shell, even though its closures are brand
7299
+ * new objects every time (an arrow function in a component method never
7300
+ * compares equal to its predecessor). See `magic-bar-projection.ts`.
7089
7301
  *
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.
7302
+ * **Strings are i18n keys, never resolved text** same rule as
7303
+ * {@link AgentCommandRegistration} (`labelKey` / `descriptionKey` / `hintKey`),
7304
+ * for the same two reasons: a contribution outlives a locale switch (a resolved
7305
+ * label would silently go stale until the publisher happened to re-publish), and
7306
+ * keys are locale-invariant so the dedupe fingerprint doesn't churn when the user
7307
+ * changes language. Keys resolve in the OWNING app's bundle.
7095
7308
  *
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
- * ```
7107
- */
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[]>;
7309
+ * **Every `*Key` has an optional `*Params` sibling** ({@link MagicBarTextParams}),
7310
+ * fed straight to `I18nService.t(key, params)`. Without it a count-carrying string
7311
+ * ("Search 1,204 tasks…") forces the publisher to pass pre-formatted TEXT as the
7312
+ * key: it renders correctly in `en`, renders identically untranslated in ar/fr/ur,
7313
+ * and no locale tool can see it. Params participate in the dedupe fingerprint, so
7314
+ * a changing count re-paints.
7315
+ *
7316
+ * Which of D5 §4's "dynamics the shell must honor" live where:
7317
+ *
7318
+ * | # | Dynamic | Owner |
7319
+ * |---|---|---|
7320
+ * | 1 | Live re-publication, idempotent replace | {@link MagicBarRegistry} (here) |
7321
+ * | 2 | Disabled ≠ hidden, auto disabled-tooltip | contract keeps `disabled` rows; `fly-magic-actions` renders + suffixes |
7322
+ * | 3 | `toolbarPopIn` 40 ms stagger | renderer — it tracks on {@link MagicBarActionSpec.id}, which is why ids are unique across the WHOLE contribution |
7323
+ * | 4 | i18n on every label/tip | contract (keys + params, above) |
7324
+ * | 5 | Fixed right cluster is not contributable | contract — no slot exists for it |
7325
+ * | 6 | Mobile renders the same contribution | contract carries no form-factor branch; `openWidth` is a desktop hint mobile ignores |
7326
+ * | 7 | `shortcut` reserved (design has no shortcut affordance yet) | contract carried, unrendered |
7327
+ *
7328
+ * On dynamic 3 specifically: the registry's dedupe does NOT stop the stagger
7329
+ * replaying, and believing it does would produce a renderer that re-animates on
7330
+ * every click. Dedupe fires only when *nothing painted* changed, and the dominant
7331
+ * case `disabled: !hasSelection` changes the fingerprint on every selection.
7332
+ * What holds the DOM still is the renderer's `@for (…; track item.id)`.
7333
+ */
7334
+ /**
7335
+ * How an action paints. `'text'` is the labelled-pill form (calendar's "Today").
7336
+ *
7337
+ * `kind` and {@link MagicBarActionSpec.iconPath} are INDEPENDENT, and the three useful
7338
+ * combinations are all reachable:
7339
+ *
7340
+ * | `kind` | `iconPath` | Paints |
7341
+ * |---|---|---|
7342
+ * | absent / `'icon'` | set | icon only — the default, for a verb with a universally-read glyph |
7343
+ * | `'text'` | absent | label only — for a value with no honest glyph ("Shortlisted", "Deferred") |
7344
+ * | `'text'` | set | **icon + label** for a verb whose glyph needs naming ("Promote to schedule") |
7345
+ *
7346
+ * Prefer icon-only for the small set of verbs a user reads without help — add, edit, delete, close,
7347
+ * save. Anything else earns its label: an unlabelled glyph the reader has to hover to identify is a
7348
+ * quiz, and the tooltip is not an answer on touch.
7349
+ */
7350
+ type MagicBarActionKind = 'icon' | 'text' | 'toggle';
7351
+ /**
7352
+ * Semantic colour of an action. `'danger'` is the red Delete treatment (red glyph
7353
+ * + red 18 % hover tint, D7); `'success'` is the green "Mark as complete" pressed
7354
+ * state (D5 §1.4 + §4 prop table: "pressed bg tint-sel / green for complete").
7355
+ *
7356
+ * A union rather than the boolean `danger` this started as: the design already
7357
+ * demands a second colour, and widening a shipped `danger?: boolean` into a union
7358
+ * later would mean REMOVING an interface member MAJOR under `tools/ds-compat`
7359
+ * (rule 7), which forks the federation singleton for every remote.
7360
+ */
7361
+ type MagicBarTone = 'default' | 'danger' | 'success';
7362
+ /**
7363
+ * Interpolation params for one i18n key — the `params` argument of
7364
+ * `I18nService.t(key, params)`, substituting `{{name}}` placeholders.
7365
+ *
7366
+ * Values are `string | number` only: the fingerprint has to encode them, and the
7367
+ * renderer resolves them through `t()`, which stringifies. A `Date` or a nested
7368
+ * object would fingerprint as `{}` and paint as `[object Object]`.
7369
+ */
7370
+ type MagicBarTextParams = Readonly<Record<string, string | number>>;
7371
+ /**
7372
+ * Identity of a contribution's publisher, and the arbitration key the shell uses
7373
+ * to pick which contribution the bar renders.
7374
+ *
7375
+ * `windowId` is optional but strongly preferred: two windows of the same app can
7376
+ * be open on different selections, and keying on `appId` alone would let the
7377
+ * background one paint the focused one's toolbar. It is optional only because a
7378
+ * federated remote that predates per-window plumbing can still contribute —
7379
+ * {@link MagicBarRegistry} resolves an `appId`-keyed slot as a fallback.
7380
+ *
7381
+ * It only has to be unique **within the publishing app** (the shell passes its own
7382
+ * `win-<appId>-<timestamp>` window id; a tab index or a route-derived id is equally
7383
+ * valid) — {@link magicBarOwnerKey} scopes it by `appId`, so a value another app
7384
+ * also uses can never cross over.
7385
+ */
7386
+ interface MagicBarOwnerRef {
7387
+ readonly appId: string;
7388
+ readonly windowId?: string;
7389
+ }
7390
+ /**
7391
+ * Storage/arbitration key for an owner. Exported because the shell computes it
7392
+ * on the janitor path (`clear` after a window closes) without holding the
7393
+ * publisher.
7394
+ *
7395
+ * **Both fields key the slot**, even though the shell's own ids
7396
+ * (`win-<appId>-<timestamp>`) are already unique. `windowId` is publisher-supplied
7397
+ * DATA that crosses the federation boundary, and the field is documented only as
7398
+ * "two windows of the same app" — nothing stops a remote passing a tab index or a
7399
+ * route-derived id. Keyed on `windowId` alone, two apps that both call their first
7400
+ * window `"1"` would share a slot: one silently paints the other's chrome, and the
7401
+ * shell's janitor `clear()` wipes the wrong one.
7402
+ */
7403
+ declare function magicBarOwnerKey(owner: MagicBarOwnerRef): string;
7404
+ /** One `menuitemradio` row. `value` is the publisher's own vocabulary. */
7405
+ interface MagicBarRadioOption {
7406
+ readonly value: string;
7156
7407
  /**
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.
7408
+ * i18n key for this row. Required, and it stays required the overwhelming case is a fixed
7409
+ * vocabulary (statuses, sort orders) that MUST translate. When {@link label} is supplied this
7410
+ * is the accessible fallback used if the row's data-derived text is ever empty, so give it a
7411
+ * generic key ("Untitled") rather than inventing a per-value one.
7161
7412
  */
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;
7413
+ readonly labelKey: string;
7414
+ /** Params for {@link labelKey} — the count-badged option ("All statuses ({{count}})"). */
7415
+ readonly labelParams?: MagicBarTextParams;
7178
7416
  /**
7179
- * Types rendered with the DS `<fly-select>` (labelled via `ariaLabel`).
7417
+ * ALREADY-RESOLVED display text, for a row whose label is TENANT DATA rather than a caption —
7418
+ * a campaign name, an area of focus, an owner. When present it wins over {@link labelKey}.
7180
7419
  *
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}.
7420
+ * ## Why this had to exist
7421
+ * Everything else in this contract is an i18n key, deliberately (see the module header). But a
7422
+ * key can only name a string the APP ships in its locale bundles, and a tenant's campaign names
7423
+ * are rows in their database. Without this field a publisher had exactly two bad options: put
7424
+ * the name in `labelKey`, which renders correctly in `en`, renders identically untranslated in
7425
+ * ar/fr/ur, and is invisible to every locale tool; or leave the filter out of the bar entirely.
7426
+ * Thoughts' `top-trending` chose the second and kept its campaign/area filters in-page — that
7427
+ * is the gap this closes.
7428
+ *
7429
+ * ## What it does NOT license
7430
+ * Do not use it for captions. If the string is something your app authored — "All statuses",
7431
+ * "Newest first", an action name — it belongs in a locale bundle and `labelKey` is the field.
7432
+ * The test is where the string comes from: shipped with the app ⇒ `labelKey`; read from the
7433
+ * tenant's data ⇒ `label`. A resolved caption here is untranslatable text smuggled past the
7434
+ * locale tooling, which is precisely what the key-only rule exists to prevent.
7435
+ *
7436
+ * It participates in the dedupe fingerprint, so a renamed campaign repaints.
7186
7437
  */
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;
7438
+ readonly label?: string;
7439
+ }
7440
+ /** Render surface of a radio menu — the dedupe key for {@link MagicBarRadioMenu}. */
7441
+ interface MagicBarRadioMenuSpec {
7442
+ /** Eyebrow above the options ("Filter by status" / "Sort by"). */
7443
+ readonly titleKey: string;
7444
+ /** Params for {@link titleKey}. */
7445
+ readonly titleParams?: MagicBarTextParams;
7446
+ readonly options: readonly MagicBarRadioOption[];
7447
+ /** Current selection — check glyph + 600 weight on the matching row. */
7448
+ readonly value: string;
7449
+ /** The unfiltered/unsorted baseline; the trigger shows its accent dot iff `value !== defaultValue`. */
7450
+ readonly defaultValue: string;
7451
+ }
7452
+ /** What a publisher supplies for a radio menu. */
7453
+ interface MagicBarRadioMenu extends MagicBarRadioMenuSpec {
7454
+ readonly onSelect?: (value: string) => void;
7455
+ }
7456
+ /** What the shell renders. `select` always exists; it no-ops when the publisher supplied no handler. */
7457
+ interface MagicBarRadioMenuView extends MagicBarRadioMenuSpec {
7458
+ select(value: string): void;
7459
+ }
7460
+ /**
7461
+ * Render surface of an action — the dedupe key for {@link MagicBarAction}.
7462
+ *
7463
+ * `id` must be unique across the WHOLE contribution (every group, plus `primary`
7464
+ * and `overflow`), not merely within its group: it is the address the shell's
7465
+ * invoker resolves against the publisher's latest handlers.
7466
+ */
7467
+ interface MagicBarActionSpec {
7468
+ readonly id: string;
7469
+ /** i18n key — the `aria-label` and the default tooltip. */
7470
+ readonly labelKey: string;
7471
+ /** Params for {@link labelKey}, e.g. `{ count: 3 }` for "Delete {{count}} items". */
7472
+ readonly labelParams?: MagicBarTextParams;
7190
7473
  /**
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.
7474
+ * Inner-SVG geometry for a 24×24 glyph (`stroke-width: 1.8`, round caps)
7475
+ * the design's icon transport. Reference {@link FLY_MAGIC_BAR_ICONS} by name
7476
+ * rather than hand-authoring; a publisher MAY author its own, subject to the
7477
+ * contract below.
7478
+ *
7479
+ * **This value is not trusted.** The renderer parses it against a closed
7480
+ * allowlist — self-closing `path`/`circle`/`rect`/`ellipse`/`line`/`polyline`/
7481
+ * `polygon` elements carrying geometry attributes only (`d`, `cx`, `points`,
7482
+ * …) — and binds the result as real attributes. It is never assigned as HTML.
7483
+ * Anything else (a container element, an `on*` handler, `style`, `href`,
7484
+ * `fill="url(…)"`, a stray text node) rejects the WHOLE fragment: the button
7485
+ * renders with no glyph and logs. See `magic-actions-icon.ts`.
7486
+ *
7487
+ * The type stays `string` deliberately. Narrowing it to a branded/opaque type
7488
+ * would be a ds-compat MAJOR, and it would not buy anything the renderer's own
7489
+ * validation does not already guarantee — a nominal type describes where a
7490
+ * value came from, not what it contains.
7194
7491
  */
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;
7492
+ readonly iconPath?: string;
7493
+ /** Default `'icon'`. */
7494
+ readonly kind?: MagicBarActionKind;
7495
+ /** Dimmed (`opacity .38`), NEVER hidden — a vanishing action teaches the user nothing. */
7496
+ readonly disabled?: boolean;
7227
7497
  /**
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.
7498
+ * Tooltip override while disabled. Absent is the normal case: the renderer
7499
+ * auto-suffixes the resolved label ("… select an item first"). Resolution
7500
+ * and the connector phrasing are the renderer's job precisely because they are
7501
+ * locale work, and this store holds keys rather than text.
7502
+ */
7503
+ readonly disabledTipKey?: string;
7504
+ /** Params for {@link disabledTipKey}. */
7505
+ readonly disabledTipParams?: MagicBarTextParams;
7506
+ /**
7507
+ * Mirror the glyph horizontally under `dir="rtl"`.
7233
7508
  *
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.
7509
+ * Most marks in {@link FLY_MAGIC_BAR_ICONS} are direction-neutral a trash can, a gear, a
7510
+ * calendar grid read the same in every locale, and mirroring them would be wrong. A minority
7511
+ * encode a DIRECTION as their meaning: indent/outdent, promote (an up-and-right arrow), a
7512
+ * next/previous chevron, an export tray. Those are mirrored by every RTL desktop application
7513
+ * there is, and left unmirrored they point at the wrong edge of the screen in `ar` and `ur` —
7514
+ * three of this platform's four locales.
7515
+ *
7516
+ * Opt-in rather than automatic because no property of the geometry distinguishes the two
7517
+ * groups: only the publisher knows whether its arrow means "forward" (mirror) or "north-east"
7518
+ * (do not). Defaults to `false`, so every glyph shipped before this field existed paints exactly
7519
+ * as it did.
7520
+ *
7521
+ * It flips only the ICON. The button, its label and the bar's own item order already mirror
7522
+ * through logical CSS properties — see `magic-actions.component.scss`'s header.
7237
7523
  */
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;
7524
+ readonly mirrorInRtl?: boolean;
7525
+ /** Tooltip override while enabled. Absent the renderer uses `labelKey`. */
7526
+ readonly tipKey?: string;
7527
+ /** Params for {@link tipKey}. */
7528
+ readonly tipParams?: MagicBarTextParams;
7529
+ /**
7530
+ * Semantic colour. Default `'default'`; `'danger'` is the ONLY thing that makes
7531
+ * an icon red (D7), `'success'` the green completed-toggle treatment.
7532
+ */
7533
+ readonly tone?: MagicBarTone;
7534
+ /** `aria-pressed` for `kind: 'toggle'`. */
7535
+ readonly pressed?: boolean;
7536
+ /** Small accent dot — a non-default filter/sort is active. */
7537
+ readonly badgeDot?: boolean;
7538
+ /**
7539
+ * Reserved (D5 §4 item 7). The designs carry no keyboard-shortcut affordance
7540
+ * anywhere; the field exists so adding one later is a renderer change rather
7541
+ * than a contract break. Nothing renders it today.
7542
+ */
7543
+ readonly shortcut?: string;
7250
7544
  }
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;
7545
+ /** What a publisher supplies for one action. */
7546
+ interface MagicBarAction extends MagicBarActionSpec {
7547
+ readonly menu?: MagicBarRadioMenu;
7548
+ /**
7549
+ * Invoked on activation. The argument is the DOM element the renderer painted
7550
+ * for this actionsee {@link MagicBarActionView.run} for why it is passed and
7551
+ * what a publisher may do with it. Ignore it and this is the zero-argument
7552
+ * handler it has always been.
7553
+ */
7554
+ readonly onSelect?: (trigger?: HTMLElement) => void;
7281
7555
  }
7282
7556
  /**
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.
7557
+ * What the shell renders. `run()` always exists and always dispatches to the
7558
+ * publisher's LATEST handler for this id — including after a re-publish the
7559
+ * registry deliberately did not propagate (see `magic-bar-projection.ts`).
7287
7560
  */
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;
7561
+ interface MagicBarActionView extends MagicBarActionSpec {
7562
+ readonly menu: MagicBarRadioMenuView | null;
7563
+ /**
7564
+ * Fire the publisher's newest handler for this id.
7565
+ *
7566
+ * ## Why it carries the trigger element
7567
+ * An action that opens a POPOVER the publisher owns — the canonical case is a
7568
+ * listing screen's advanced-filter panel has to position that popover under
7569
+ * the button the user just pressed. The publisher cannot find that button:
7570
+ * embedded, it is painted by `fly-magic-actions` inside the shell's top-bar
7571
+ * pill, in the shell's DOM, several stacking contexts and one federation
7572
+ * boundary away from the remote that published the action. Before this argument
7573
+ * existed, PPM's projects register had to anchor its panel to its own page
7574
+ * header instead, which put the panel in the middle of the window while the
7575
+ * button that opened it sat in the chrome above — an affordance with no visible
7576
+ * relationship to its own trigger.
7577
+ *
7578
+ * Passing the element rather than modelling the popover is deliberate. The
7579
+ * panel's CONTENT is app schema (see {@link MagicBarSearch.onOpenFilters} for
7580
+ * the same ruling), so it must stay in the publisher's own DOM and injection
7581
+ * context; only its POSITION is chrome-relative. An element reference crosses
7582
+ * the federation boundary without any of the coupling a descriptor would need —
7583
+ * a DOM node is a DOM node in every bundle. `fly-filter-panel`'s `[anchorEl]`
7584
+ * is the intended consumer.
7585
+ *
7586
+ * ## What a publisher must not assume
7587
+ * The element is the trigger AS PAINTED RIGHT NOW. It is valid for the duration
7588
+ * of the popover, not beyond: a re-publish that drops this action id destroys it
7589
+ * (`@for (…; track action.id)`), and a mode switch repaints it in a different
7590
+ * chrome entirely. Hold it in a signal the popover's open-state clears, never in
7591
+ * long-lived state, and never mutate it.
7592
+ *
7593
+ * It is OPTIONAL because not every renderer has one to give — a keyboard-driven
7594
+ * invocation, a synthetic call in a test, an inline composer that paints no
7595
+ * button. A publisher that receives `undefined` must still work: for a popover
7596
+ * that means falling back to its own in-page anchoring, which is exactly what
7597
+ * `fly-filter-panel` does when `[anchorEl]` is null.
7598
+ */
7599
+ run(trigger?: HTMLElement): void;
7314
7600
  }
7315
- /** `delete` requestsoft delete (tombstone), author or moderator. */
7316
- interface FlyCommentDeleteRequest {
7317
- commentId: string;
7601
+ /** Render surface of a group the dedupe key for {@link MagicBarGroup}. */
7602
+ interface MagicBarGroupSpec {
7603
+ readonly id: string;
7604
+ /** i18n key for the `role="group"` aria-label ("Signal actions"). */
7605
+ readonly labelKey: string;
7606
+ /** Framed variant: 2 px gaps + a trailing separator. Plain (default) uses 8 px gaps. */
7607
+ readonly framed?: boolean;
7318
7608
  }
7319
- /** `report` request typed reason + optional free-text detail. */
7320
- interface FlyCommentReportRequest {
7321
- commentId: string;
7322
- reason: FlyCommentReportReason;
7323
- detail?: string;
7609
+ interface MagicBarGroup extends MagicBarGroupSpec {
7610
+ readonly items: readonly MagicBarAction[];
7324
7611
  }
7325
- /** `lockToggle` request moderator lock/unlock of the whole thread. */
7326
- interface FlyCommentLockToggleRequest {
7327
- locked: boolean;
7612
+ interface MagicBarGroupView extends MagicBarGroupSpec {
7613
+ readonly items: readonly MagicBarActionView[];
7328
7614
  }
7329
-
7330
7615
  /**
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.
7616
+ * Render surface of the expanding search field — the dedupe key for
7617
+ * {@link MagicBarSearch}.
7360
7618
  *
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
- * ```
7619
+ * This is a per-page typeahead, NOT a command palette: neither design package
7620
+ * has a global ⌘K surface, and the only "command" affordance is the Ask-AI menu.
7378
7621
  */
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>;
7622
+ interface MagicBarSearchSpec {
7623
+ /** Context-aware placeholder key ("Search tasks…", "Search events…"). */
7624
+ readonly placeholderKey: string;
7625
+ /** Params for {@link placeholderKey}, e.g. `{ count: 1204 }` for "Search {{count}} tasks…". */
7626
+ readonly placeholderParams?: MagicBarTextParams;
7402
7627
  /**
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.
7628
+ * The text the search field should SHOW literal text, not an i18n key, because
7629
+ * it is the user's own query rather than a caption.
7407
7630
  *
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.
7631
+ * Exists because a view can arrive **already filtered**: a deep link carrying
7632
+ * `?q=…`, a restored session, a filter applied from a detail pane. Without it the
7633
+ * box renders empty over a visibly filtered list, and the user is looking at a
7634
+ * contradiction the chrome cannot explain.
7412
7635
  *
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;
7636
+ * ## Why it is not called `query`
7637
+ * {@link MagicBarSearchView} which extends this interface — already declares
7638
+ * `query(text: string): void`, the late-bound invoker. A `query?: string` here
7639
+ * would make that view an illegal extension (TS2430: `(text: string) => void` is
7640
+ * not assignable to `string | undefined`), and the only other way out — renaming
7641
+ * the invoker removes a member of a shipped exported interface, which is a
7642
+ * ds-compat MAJOR (rule 7) and forks the federation singleton for every remote.
7643
+ * So the field takes the name that pairs with the invoker instead of colliding
7644
+ * with it: `queryText` is the text, `query()` submits it.
7645
+ *
7646
+ * ## Precedence — this is a SEED, not a controlled value
7647
+ * The field's live text is owned by the **shell**, because the user types into
7648
+ * it. This field is the publisher's chance to move it, and the shell adopts the
7649
+ * published value exactly three times:
7650
+ *
7651
+ * 1. the first time it binds this view,
7652
+ * 2. when {@link MagicBarContribution.viewKey} changes (reset-on-navigate the
7653
+ * previous view's query must not survive into the next one), and
7654
+ * 3. when the published value **itself changes** from the one previously
7655
+ * published.
7656
+ *
7657
+ * A re-publish carrying an UNCHANGED `queryText` never touches what the user has
7658
+ * typed. That third clause is the whole rule: the dominant re-publish is
7659
+ * `disabled: !hasSelection` flipping on a selection change, and a shell that
7660
+ * re-read `queryText` on every emitted view would revert a keystroke every time
7661
+ * the user selected a row mid-search — the classic controlled-input bug. The rule
7662
+ * is authored once, as {@link magicBarSearchText}, so no renderer re-derives it.
7663
+ *
7664
+ * `undefined` and `''` are **different** and the difference is load-bearing:
7665
+ * absent means "this view does not manage the field" (nothing is ever forced),
7666
+ * `''` means "clear it". Both the fingerprint and the seed comparison distinguish
7667
+ * them.
7668
+ *
7669
+ * ## Do not echo the user's keystrokes back through this field
7670
+ * A publisher that mirrors its `onQuery` argument straight back into `queryText`
7671
+ * is harmless (the seed changes to text the field already shows). A publisher
7672
+ * that mirrors it back **debounced** is not: typing "abc" then publishing
7673
+ * `queryText: 'a'` 300 ms later is, by rule 3, a genuine publisher-initiated
7674
+ * change, and the field snaps back to "a". Publish here only for state the view
7675
+ * owns independently of the field.
7676
+ */
7677
+ readonly queryText?: string;
7481
7678
  /**
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`.
7679
+ * Expanded width in px a **desktop layout hint**, carried verbatim.
7486
7680
  *
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.
7681
+ * The design's range is 140–420 (default 230), but this store deliberately does
7682
+ * NOT validate or clamp it: enforcing a paint range here would reject a whole
7683
+ * contribution over a cosmetic hint (leaving the previous view's actions in the
7684
+ * chrome), and mobile ignores the field entirely, so there is no single range to
7685
+ * enforce. `fly-magic-actions` clamps to the design range at paint time and is
7686
+ * the only place that decides what an out-of-range or non-finite value means.
7490
7687
  */
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>;
7688
+ readonly openWidth?: number;
7689
+ /** Accent dot on the Filters button a non-default filter is applied. */
7690
+ readonly filtersActive?: boolean;
7528
7691
  }
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;
7692
+ interface MagicBarSearch extends MagicBarSearchSpec {
7693
+ readonly onQuery?: (query: string) => void;
7694
+ /**
7695
+ * Opens the view's own Advanced-search overlay. Presence of this handler is
7696
+ * what makes the Filters button exist — the DS deliberately does not model the
7697
+ * dialog itself. Its fields are app schema (Circles' PESTLE/horizon grid), and
7698
+ * a descriptor for them would be business logic the design system cannot
7699
+ * validate or render honestly.
7700
+ */
7701
+ readonly onOpenFilters?: (trigger?: HTMLElement) => void;
7548
7702
  }
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;
7703
+ interface MagicBarSearchView extends MagicBarSearchSpec {
7704
+ /** True iff the publisher can open an advanced-search overlay. */
7705
+ readonly hasFilters: boolean;
7706
+ /**
7707
+ * Submit the field's current text to the publisher's newest `onQuery`. The
7708
+ * inbound half what the field should SHOW is
7709
+ * {@link MagicBarSearchSpec.queryText}, and that asymmetry in naming is forced:
7710
+ * this member shipped first, and renaming it would be a ds-compat MAJOR.
7711
+ */
7712
+ query(text: string): void;
7713
+ /**
7714
+ * Open the publisher's advanced-search overlay. Carries the Filters button the
7715
+ * shell painted, for the same reason and under the same caveats as
7716
+ * {@link MagicBarActionView.run} — a publisher whose overlay is a popover
7717
+ * anchors it there instead of guessing at a position inside its own page.
7718
+ */
7719
+ openFilters(trigger?: HTMLElement): void;
7564
7720
  }
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
7721
  /**
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.
7722
+ * One view's complete claim on the magic bar.
7723
+ *
7724
+ * Identity (`appId` / `windowId`) is deliberately absent: it belongs to the
7725
+ * {@link MagicBarPublisher} that was minted for this owner, so a later publish
7726
+ * cannot re-target another app's slot.
7576
7727
  */
7577
- declare function flyCaptchaBuildToken(solution: FlyCaptchaSolution): string;
7728
+ interface MagicBarContribution {
7729
+ /**
7730
+ * What the publisher is showing, e.g. `'tasks:detail'`. A change here is
7731
+ * reset-on-navigate: the registry replaces the slot wholesale rather than
7732
+ * reconciling, so nothing from the previous view can survive.
7733
+ */
7734
+ readonly viewKey: string;
7735
+ readonly search?: MagicBarSearch | null;
7736
+ /** Ordered clusters, separator-joined. */
7737
+ readonly groups?: readonly MagicBarGroup[];
7738
+ /** The accent-filled CTA ("New task", "Add signal"). */
7739
+ readonly primary?: MagicBarAction | null;
7740
+ /**
7741
+ * Ids of {@link AgentCommand}s this view considers most relevant, most-relevant
7742
+ * first — the Ask-AI menu's per-view sets.
7743
+ *
7744
+ * Ids, not command objects: the platform already models commands (manifest
7745
+ * `commands[]` → `AgentCommandRegistry`), and D5 §6 item 9 is explicit that
7746
+ * production must source these from manifests instead of the mock's hardcoded
7747
+ * `{ title, note, prompt }` triples. Duplicating that model here would fork the
7748
+ * palette and reintroduce hardcoded prompt text. Unknown ids are dropped by the
7749
+ * resolver; an empty list leaves the shell's affinity ranking in charge.
7750
+ */
7751
+ readonly quickCommandIds?: readonly string[];
7752
+ /** The "…" overflow set. Reserved design intent (D5 §6 item 8) — Print / Export / Settings. */
7753
+ readonly overflow?: readonly MagicBarAction[];
7754
+ }
7755
+ /** The shell-facing projection of a contribution. See `magic-bar-projection.ts`. */
7756
+ interface MagicBarView {
7757
+ readonly appId: string;
7758
+ readonly windowId?: string;
7759
+ /** {@link magicBarOwnerKey} of the publisher. */
7760
+ readonly ownerKey: string;
7761
+ readonly viewKey: string;
7762
+ readonly search: MagicBarSearchView | null;
7763
+ readonly groups: readonly MagicBarGroupView[];
7764
+ readonly primary: MagicBarActionView | null;
7765
+ readonly quickCommandIds: readonly string[];
7766
+ readonly overflow: readonly MagicBarActionView[];
7767
+ }
7578
7768
  /**
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
- * ```
7769
+ * A view's handle on its magic-bar slot. Minted once per publisher (not per
7770
+ * publish) because the publish rate is the whole point of this surface: the
7771
+ * sibling registries hand back a handle per `register()` and expect a handful of
7772
+ * calls per app lifetime, whereas a magic-bar publisher re-emits on every
7773
+ * selection change. Handing out a fresh handle each time would leave the caller
7774
+ * holding a dead one and force it to reassign on every keystroke.
7601
7775
  */
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;
7776
+ interface MagicBarPublisher {
7777
+ readonly owner: MagicBarOwnerRef;
7628
7778
  /**
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.
7779
+ * Replace this owner's contribution. No-op after {@link dispose}, and no-op
7780
+ * after the shell has {@link MagicBarRegistry.clear}ed this owner key.
7781
+ *
7782
+ * **Publish is last-writer-wins and eviction is not** a deliberate asymmetry.
7783
+ * A stale publisher (one that has been replaced on this key by a newer one)
7784
+ * cannot `dispose()` the newer one out of the slot, but it *can* still take the
7785
+ * slot back by publishing. Symmetry would need a "newest publisher wins" rule
7786
+ * enforced on write, which would make the first publish of a replacement
7787
+ * publisher silently drop whenever two live views briefly share a key. The
7788
+ * exposure is bounded: two live publishers on one owner key only happen while a
7789
+ * view is being replaced in place, and the loser re-publishes on its next
7790
+ * selection change. The unbounded case — a publisher that outlives its window —
7791
+ * is closed by `clear()`'s tombstone, not by write ordering.
7792
+ */
7793
+ publish(contribution: MagicBarContribution): void;
7794
+ /**
7795
+ * Release the slot. Idempotent, and a no-op if another publisher has since
7796
+ * taken this owner key — the same stale-handle rule the sibling registries use.
7635
7797
  */
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>;
7798
+ dispose(): void;
7646
7799
  }
7647
7800
 
7648
7801
  /**
7649
- * Singleton registry of slash commands offered by the agent input palette.
7802
+ * The Gantt **Period** control as a magic-bar radio-menu item.
7650
7803
  *
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.
7804
+ * ## Why it is called Period and not Zoom
7805
+ * The control does not scale the chart — it changes the unit the time axis is measured in
7806
+ * (day / week / month / quarter), which is what a planner calls the *period*. "Zoom" invites the
7807
+ * browser's own Ctrl+wheel meaning, which this chart deliberately remaps to horizontal scrolling
7808
+ * (`gantt-pan.ts`), so two different verbs would have shared one word on the same surface. The
7809
+ * underlying type stays {@link GanttZoom} — renaming a shipped exported type is MAJOR under
7810
+ * `tools/ds-compat` and would fork the federation singleton for every remote over a caption.
7654
7811
  *
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.
7812
+ * ## Why it lives in the design system
7813
+ * A period picker is not app vocabulary: every screen that draws a `fly-gantt` needs exactly these
7814
+ * four levels under exactly these labels, and the first app to grow a second Gantt screen
7815
+ * immediately has two copies to keep in step (PPM had a project plan, a project schedule and a
7816
+ * portfolio roadmap). Shipping the item beside the chart means the labels ride the DS baseline
7817
+ * locales in all four languages and a consumer supplies only state and a callback.
7818
+ *
7819
+ * `defaultValue` is the CALLER's load-time default — `'week'` on a single project's plan, `'month'`
7820
+ * on a portfolio spanning years — because the trigger's accent "non-default" dot should light only
7821
+ * once the user has left that screen's own entry point.
7822
+ *
7823
+ * `labelKeyPrefix` exists for a consumer that already ships its own translated period labels and
7824
+ * does not want the DS baseline (PPM's `ppm.projects.plan.zoom.*` predate this helper). It appends
7825
+ * `.label` for the trigger and `.<zoom>` per option, matching the baseline layout exactly.
7661
7826
  */
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
- }
7827
+ declare function ganttPeriodBarItem(spec: {
7828
+ value: GanttZoom;
7829
+ defaultValue: GanttZoom;
7830
+ disabled?: boolean;
7831
+ /** Key namespace for the trigger + option captions. Defaults to the DS baseline `gantt.period`. */
7832
+ labelKeyPrefix?: string;
7833
+ onSelect: (zoom: GanttZoom) => void;
7834
+ }): MagicBarAction;
7835
+ /**
7836
+ * The **Indent** / **Outdent** pair as magic-bar actions, for a chart whose consumer maps outline
7837
+ * level onto something it can persist.
7838
+ *
7839
+ * Two actions rather than one toggle: they are opposite verbs with independent availability (a row
7840
+ * can be indentable and not outdentable, and at the top of a list neither applies), and a toggle
7841
+ * would have to pick a label for a state the user cannot see.
7842
+ *
7843
+ * ## Icon-only, and why they are still discoverable
7844
+ * Both are icon-only despite `skills/magic-bar-view-contributions.md` §5's default, because the
7845
+ * indent/outdent glyph pair is one of the few marks a user genuinely reads without help — it is the
7846
+ * same pair every word processor, IDE and desktop planner has shipped for thirty years, and the two
7847
+ * are identified by being MIRRORS of each other, which a label cannot improve on. They also sort
7848
+ * inside Add/Delete/Edit under {@link orderMagicBarItems}, so they never take the outer edge from a
7849
+ * verb that needs it.
7850
+ *
7851
+ * ## Disabled, never hidden
7852
+ * `canIndent` / `canOutdent` are state gates, not permission gates: the same row becomes indentable
7853
+ * the moment a suitable row appears above it. So both stay rendered and dimmed, with a tip saying
7854
+ * why §6's default. Pass `disabledTipKey`s when your app's reason is more specific than the
7855
+ * baseline "this row cannot move any further".
7856
+ */
7857
+ declare function ganttOutlineBarItems(spec: {
7858
+ canIndent: boolean;
7859
+ canOutdent: boolean;
7860
+ onIndent: () => void;
7861
+ onOutdent: () => void;
7862
+ /** Tip shown while Indent is dimmed. Defaults to the DS baseline reason. */
7863
+ indentDisabledTipKey?: string;
7864
+ /** Tip shown while Outdent is dimmed. Defaults to the DS baseline reason. */
7865
+ outdentDisabledTipKey?: string;
7866
+ }): MagicBarAction[];
7696
7867
 
7697
7868
  /**
7698
- * Singleton registry of entity lookups offered by the `/lookup` typeahead.
7869
+ * Normalise a label / query for comparison: case-folded and stripped of combining marks, so a
7870
+ * search for `resume` finds `Résumé` and an Arabic query matches regardless of harakat.
7699
7871
  *
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.
7872
+ * `NFD` + strip `\p{M}` rather than a hand-rolled accent table: the table approach covers Latin-1
7873
+ * and silently fails on every other script this platform ships (`ar`, `ur`), which is precisely the
7874
+ * locale pair where a user is most likely to type an unmarked form.
7875
+ */
7876
+ declare function normalizeGanttSearchText(value: string): string;
7877
+ /**
7878
+ * Filter a Gantt row set to the rows a query is "about", **preserving tree context**.
7706
7879
  *
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.
7880
+ * A flat `rows.filter(matches)` is the obvious implementation and it is wrong here for two
7881
+ * independent reasons, both of which change what the user sees rather than merely how it looks:
7712
7882
  *
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.
7883
+ * 1. **A matching child whose parent is filtered out becomes a root.** `flattenRows` treats an
7884
+ * unknown `parentId` as a root, so a task matching "deploy" would jump to depth 0 and lose the
7885
+ * milestone that gives it meaning. The user asked to find a row, not to re-parent it.
7886
+ * 2. **A matching parent whose children are filtered out stops being a group.** Its chevron
7887
+ * disappears and its derived span collapses to its own dates — so searching for a milestone
7888
+ * silently changes the bar the chart draws for it.
7889
+ *
7890
+ * So the kept set is the **closure** of the matches over both directions of the tree: every match,
7891
+ * every ancestor of a match (context above), and every descendant of a match (context below).
7892
+ * Searching a milestone's name shows that milestone with its whole task list intact; searching a
7893
+ * task's name shows the task under its own milestone.
7894
+ *
7895
+ * Order is preserved — this is a filter, never a re-sort. An empty / whitespace query returns the
7896
+ * input array by reference, so a non-searching chart pays nothing and its `computed` never
7897
+ * invalidates on identity alone.
7716
7898
  */
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>;
7899
+ declare function filterGanttRows(rows: readonly GanttRow[], query: string): readonly GanttRow[];
7900
+
7901
+ /**
7902
+ * Apply a {@link GanttRowReorder} to a plain `GanttRow[]`, returning a new array.
7903
+ *
7904
+ * Optional: a consumer that persists to a server and re-reads does not need it. It exists for the
7905
+ * two cases that do — an optimistic update while the write is in flight, and an app whose row
7906
+ * order is client-side only — because the fiddly part is not the parent change, it is keeping the
7907
+ * moved row's DESCENDANTS with it and landing the block after the previous sibling's whole
7908
+ * subtree rather than immediately after that sibling's own row.
7909
+ *
7910
+ * The moved row's `parentId` is rewritten to the move's parent; every other row is untouched, so a
7911
+ * consumer's extra fields survive.
7912
+ */
7913
+ declare function applyGanttRowReorder(rows: readonly GanttRow[], move: GanttRowReorder): GanttRow[];
7914
+
7915
+ /** The 14 rendered field kinds. Drives the control the renderer picks. */
7916
+ type FlyFormQuestionType = 'Text' | 'MultilineText' | 'Number' | 'Email' | 'Date' | 'DateTime' | 'Boolean' | 'SingleSelect' | 'MultipleSelect' | 'RadioButton' | 'Country' | 'File' | 'MultiFile' | 'Image';
7917
+ /**
7918
+ * A selectable option on a choice field (SingleSelect / MultipleSelect /
7919
+ * RadioButton). `linkedQuestionIds` drive conditional branching: selecting this
7920
+ * option reveals those (branch-target) fields. `score` is author-only and is only
7921
+ * surfaced to the viewer when the host opts in via `[showScore]` (a scorecard);
7922
+ * a respondent-facing form leaves `showScore` false and never renders it.
7923
+ */
7924
+ interface FlyFormOption {
7925
+ id: string;
7926
+ label: string;
7927
+ score?: number;
7928
+ linkedQuestionIds?: string[];
7929
+ }
7930
+ /** Per-type constraints/affordances an author can attach to a field. */
7931
+ interface FlyFormFieldOptions {
7932
+ minLength?: number;
7933
+ maxLength?: number;
7934
+ numericMin?: number;
7935
+ numericMax?: number;
7936
+ rows?: number;
7937
+ maxFileSizeMb?: number;
7938
+ maxFileCount?: number;
7939
+ allowedExtensions?: string[];
7940
+ hasSearch?: boolean;
7941
+ }
7942
+ /**
7943
+ * A single form field. `isBranchTarget` fields start hidden. `sectionId` /
7944
+ * `criteriaGroup` are additive grouping hints a scorecard uses; they are ignored
7945
+ * by the respondent-facing survey path, so binding them changes nothing there.
7946
+ */
7947
+ interface FlyFormQuestion {
7948
+ id: string;
7949
+ text: string;
7950
+ description?: string;
7951
+ type: FlyFormQuestionType;
7952
+ required: boolean;
7953
+ isBranchTarget: boolean;
7954
+ /** Additive: an author-hidden field. `false` excludes it from the visible set (and thus from
7955
+ * required-enforcement); omitted / `true` renders as normal. Mirrors the backend `IsVisible`. */
7956
+ isVisible?: boolean;
7957
+ fieldOptions?: FlyFormFieldOptions;
7958
+ options: FlyFormOption[];
7959
+ /** Additive: groups fields into a visual section (scorecard). Ignored by surveys. */
7960
+ sectionId?: string;
7961
+ /** Additive: labels the weighted-criteria group a scored field belongs to. Ignored by surveys. */
7962
+ criteriaGroup?: string;
7963
+ }
7964
+ /** A full form definition — the top-level `[definition]` binding. */
7965
+ interface FlyFormDefinition {
7966
+ title: string;
7967
+ description?: string;
7968
+ declaration?: string;
7969
+ questions: FlyFormQuestion[];
7970
+ }
7971
+ /** How an opted-in `[showScore]` host renders a choice option's points. */
7972
+ type FlyFormScoreDisplay = 'inline' | 'badge';
7973
+ /**
7974
+ * The polymorphic answer payload for one field. Only the field(s) relevant to the
7975
+ * type are populated:
7976
+ * - text: Text / MultilineText / Email
7977
+ * - number: Number
7978
+ * - dateValue: Date / DateTime (raw `<input>` value string)
7979
+ * - boolValue: Boolean
7980
+ * - selectedOptionIds: SingleSelect / MultipleSelect / RadioButton / Country
7981
+ * - fileIds: File / MultiFile / Image (filled by the host after upload; this
7982
+ * component emits it empty and surfaces the raw selection via `fileSelected`)
7983
+ */
7984
+ interface FlyFormAnswerValue {
7985
+ text?: string;
7986
+ number?: number;
7987
+ dateValue?: string;
7988
+ boolValue?: boolean;
7989
+ selectedOptionIds?: string[];
7990
+ fileIds?: string[];
7991
+ }
7992
+ /** One field's answer — the unit emitted by `answersChange` / `submit`. */
7993
+ interface FlyFormAnswer {
7994
+ questionId: string;
7995
+ value: FlyFormAnswerValue;
7996
+ }
7997
+ /**
7998
+ * Raw file selection surfaced by File / MultiFile / Image fields. The host wires
7999
+ * this to its own upload API and, once ids are known, feeds them back via the
8000
+ * `[answers]` prefill (`fileIds`). The DS component never touches an upload endpoint.
8001
+ */
8002
+ interface FlyFileSelection {
8003
+ questionId: string;
8004
+ files: File[];
7795
8005
  }
7796
8006
 
8007
+ /** A render segment: either an ungrouped field (`group === null`) or a titled `criteriaGroup` section.
8008
+ * `index` is the field's running position across the whole form (drives unique element ids). */
8009
+ interface FlyFormFieldGroup {
8010
+ group: string | null;
8011
+ items: {
8012
+ q: FlyFormQuestion;
8013
+ index: number;
8014
+ }[];
8015
+ }
7797
8016
  /**
7798
- * Singleton registry of chip-renderer components and keyboard-alternative draggable
7799
- * items, keyed by `kind` and `appId`.
8017
+ * **`fly-dynamic-form`** the design-system data-contract-driven form renderer.
7800
8018
  *
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.
8019
+ * Renders a {@link FlyFormDefinition} (14 field types) into an accessible,
8020
+ * theme-aware form, owning all the fiddly behaviour a form needs: conditional
8021
+ * **branching** (options reveal branch-target fields), per-visible-field
8022
+ * **required validation**, a **declaration** acceptance gate, and a **readonly**
8023
+ * replay mode. It is a platform surface — zero coupling to any specific app, and
8024
+ * (deliberately) no coupling to an upload API: File / Image / MultiFile fields
8025
+ * surface the raw selection via {@link fileSelected} and emit `fileIds` empty; the
8026
+ * host wires the upload and feeds ids back through the `[answers]` prefill.
7804
8027
  *
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.
8028
+ * The historical **`fly-survey-form`** selector is retained on this same component
8029
+ * (multi-selector), so every existing `<fly-survey-form>` binding keeps working
8030
+ * unchanged. Surveys never sets {@link showScore}, so its behaviour (never rendering
8031
+ * `option.score`) is identical. A scorecard host opts into score display with
8032
+ * `[showScore]="true"`.
7807
8033
  *
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).
8034
+ * i18n is self-sufficient through the `form.*` keys in `DS_BASELINE_LOCALES`
8035
+ * (en/ar/fr/ur), overridable by any consumer key of the same name. RTL works via
8036
+ * logical CSS. Styling uses DS theme tokens (`--surface-*`, `--label-*`,
8037
+ * `--separator`, `--fill-*`, `--accent`, `--system-*`) with light-neutral
8038
+ * fallbacks so it stays readable even in a consumer that hasn't mapped them.
8039
+ *
8040
+ * @example
8041
+ * ```html
8042
+ * <fly-dynamic-form
8043
+ * [definition]="def"
8044
+ * [answers]="prefill"
8045
+ * [showScore]="true"
8046
+ * (answersChange)="draft = $event"
8047
+ * (validityChange)="valid = $event"
8048
+ * (fileSelected)="upload($event)"
8049
+ * (submit)="persist($event)" />
8050
+ * ```
7810
8051
  */
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;
8052
+ declare class FlyDynamicFormComponent implements OnInit {
8053
+ private readonly _i18n;
8054
+ /** The form to render. */
8055
+ readonly definition: _angular_core.InputSignal<FlyFormDefinition>;
8056
+ /** Replay mode — every control is disabled and no validation errors / submit show.
8057
+ * Aliased to `[readonly]` (the public binding); the class member avoids shadowing
8058
+ * the TypeScript `readonly` keyword. */
8059
+ readonly isReadonly: _angular_core.InputSignal<boolean>;
8060
+ /** Hides the submit button while leaving every control interactive — the author's
8061
+ * pre-publish PREVIEW, which must let you open dropdowns and try the form out but has
8062
+ * nothing to submit to. Distinct from `readonly`, which disables the controls outright
8063
+ * (a submission replay). A host that hides submit should not bind `(submit)`. */
8064
+ readonly hideSubmit: _angular_core.InputSignal<boolean>;
8065
+ /** Prefill answers (e.g. a saved draft or a completed response). Answers for
8066
+ * currently-hidden branch targets are ignored. */
8067
+ readonly answers: _angular_core.InputSignal<readonly FlyFormAnswer[]>;
8068
+ /** Additive, opt-in: render each choice option's `score` (a weighted scorecard).
8069
+ * Defaults false so the respondent-facing survey path never leaks the answer key. */
8070
+ readonly showScore: _angular_core.InputSignal<boolean>;
8071
+ /** How an opted-in score is rendered next to an option (`badge` chip or `inline` text). */
8072
+ readonly scoreDisplay: _angular_core.InputSignal<FlyFormScoreDisplay>;
8073
+ /** The full visible answer set on every change. */
8074
+ readonly answersChange: _angular_core.OutputEmitterRef<FlyFormAnswer[]>;
8075
+ /** Required-field validity on every change (does NOT include the declaration gate). */
8076
+ readonly validityChange: _angular_core.OutputEmitterRef<boolean>;
8077
+ /** Fires only when the form is valid AND (if a declaration is set) accepted. */
8078
+ readonly submit: _angular_core.OutputEmitterRef<FlyFormAnswer[]>;
8079
+ /** Raw file selection for a File / MultiFile / Image field — the host uploads
8080
+ * and feeds the resulting ids back via `[answers]`. */
8081
+ readonly fileSelected: _angular_core.OutputEmitterRef<FlyFileSelection>;
8082
+ private readonly _base;
8083
+ /** questionId → answer value. Only ever contains VISIBLE, non-empty answers. */
8084
+ private readonly _values;
8085
+ /** questionId → locally-picked File[] (transient, pre-upload). Never emitted as ids. */
8086
+ private readonly _fileSelections;
8087
+ private readonly _declarationAccepted;
8088
+ private readonly _submitAttempted;
8089
+ /** questionIds the user has interacted with — gates when a required error shows. */
8090
+ private readonly _interacted;
8091
+ /** Per-field local file-validation error message (already localized). */
8092
+ private readonly _fileErrors;
8093
+ private readonly _countryOptions;
8094
+ constructor();
8095
+ ngOnInit(): void;
8096
+ /** The set of field ids currently shown (base fields + revealed branch targets). */
8097
+ readonly visibleQuestionIds: _angular_core.Signal<Set<string>>;
8098
+ /** Definition fields filtered to the visible set, in definition order. */
8099
+ readonly visibleQuestions: _angular_core.Signal<FlyFormQuestion[]>;
7815
8100
  /**
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.
8101
+ * The visible fields laid out in render order, segmented by `criteriaGroup`: fields that share a
8102
+ * (non-empty) group render together inside one titled section (a scorecard's weighted-criteria
8103
+ * grouping); a field with no `criteriaGroup` is its own ungrouped segment and renders flat, exactly
8104
+ * as before. Each item keeps its running index across the whole form so element ids stay unique.
7822
8105
  */
7823
- rendererFor(kind: string): Type<AgentChipHostInputs> | null;
8106
+ readonly groupedQuestions: _angular_core.Signal<FlyFormFieldGroup[]>;
8107
+ readonly declarationRequired: _angular_core.Signal<boolean>;
8108
+ /** Required-field validity (declaration NOT included — see {@link canSubmit}). */
8109
+ readonly isValid: _angular_core.Signal<boolean>;
8110
+ /** Whether `(submit)` may fire: valid required set AND declaration accepted (if any). */
8111
+ readonly canSubmit: _angular_core.Signal<boolean>;
8112
+ readonly declarationAccepted: _angular_core.Signal<boolean>;
8113
+ fieldId(index: number): string;
8114
+ descId(index: number): string;
8115
+ errId(index: number): string;
8116
+ describedBy(q: FlyFormQuestion, index: number): string | null;
8117
+ isChoice(type: FlyFormQuestionType): boolean;
8118
+ isFile(type: FlyFormQuestionType): boolean;
8119
+ /** Types rendered as a native input group inside `<fieldset><legend>`. */
8120
+ isFieldset(type: FlyFormQuestionType): boolean;
8121
+ isCountry(type: FlyFormQuestionType): boolean;
7824
8122
  /**
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.
8123
+ * Types rendered with the DS `<fly-select>` (labelled via `ariaLabel`).
7828
8124
  *
7829
- * Returns a {@link AgentCommandHandle} (re-used to keep the disposable shape uniform
7830
- * across registries) whose `dispose()` removes this exact registration.
8125
+ * Every select-like type now qualifies. `hasSearch` used to gate the CONTROL, which left a plain
8126
+ * SingleSelect on a native `<select>` an OS-chrome popup that cannot be themed (it paints its
8127
+ * rows with the control's translucent background over a white backing, so the list rendered
8128
+ * white-on-white on the dark shell). `hasSearch` now only decides whether the DS panel carries a
8129
+ * search box; see {@link selectSearchable}.
7831
8130
  */
7832
- register<T = unknown>(reg: AgentDropRendererRegistration<T>): AgentCommandHandle;
8131
+ usesDsSelect(q: FlyFormQuestion): boolean;
8132
+ /** Search box: always for Country (the list is ~200 long), else only when the author asked. */
8133
+ selectSearchable(q: FlyFormQuestion): boolean;
7833
8134
  /**
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`.
8135
+ * The points to show for a choice option, or null to render nothing. Non-null only when the host
8136
+ * opted in via `[showScore]` and the option actually carries a score — this is the sole gate that
8137
+ * keeps a respondent-facing survey (which never binds `showScore`) from leaking the answer key.
7837
8138
  */
7838
- publishDraggables(appId: string, items: readonly AgentDraggableItem[]): void;
8139
+ optionScore(opt: FlyFormOption): number | null;
8140
+ /** Whether a required error should be visible for a field right now. */
8141
+ showError(q: FlyFormQuestion): boolean;
8142
+ fileError(q: FlyFormQuestion): string | null;
8143
+ textValue(q: FlyFormQuestion): string;
8144
+ numberValue(q: FlyFormQuestion): number | null;
8145
+ dateValue(q: FlyFormQuestion): string;
8146
+ boolValue(q: FlyFormQuestion): boolean | null;
8147
+ singleValue(q: FlyFormQuestion): string | null;
8148
+ isSelected(q: FlyFormQuestion, optionId: string): boolean;
8149
+ countryOptions(q: FlyFormQuestion): readonly FlySelectOption[];
8150
+ selectOptions(q: FlyFormQuestion): readonly FlySelectOption[];
8151
+ fileNames(q: FlyFormQuestion): string[];
8152
+ acceptFor(q: FlyFormQuestion): string;
8153
+ onText(q: FlyFormQuestion, value: string): void;
8154
+ onNumber(q: FlyFormQuestion, value: string): void;
8155
+ onDate(q: FlyFormQuestion, value: string): void;
8156
+ onBoolean(q: FlyFormQuestion, value: boolean): void;
8157
+ onSingleSelect(q: FlyFormQuestion, value: string | readonly string[] | null): void;
8158
+ onRadio(q: FlyFormQuestion, optionId: string): void;
8159
+ onMultiToggle(q: FlyFormQuestion, optionId: string, checked: boolean): void;
8160
+ onFiles(q: FlyFormQuestion, input: HTMLInputElement): void;
8161
+ onDeclarationToggle(checked: boolean): void;
8162
+ onSubmit(): void;
8163
+ private _touch;
8164
+ private _setFileError;
8165
+ private _mutate;
8166
+ private _emit;
8167
+ private _collectAnswers;
8168
+ private _isAnswered;
8169
+ private _isEmptyValue;
8170
+ private _selectedIds;
7839
8171
  /**
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()`.
8172
+ * The set of visible field ids for a given answer map. Base (non-branch-target)
8173
+ * fields are always visible; a branch target becomes visible when a
8174
+ * currently-visible field has a selected option that links to it. Computed to a
8175
+ * fixpoint so chained reveals work, and cycle-safe: the visible set only grows and
8176
+ * iteration is capped at `questions.length` passes, so an A↔B link cycle terminates.
8177
+ *
8178
+ * This is the TypeScript twin of the backend Fly.Sdk.Forms `BranchResolver`
8179
+ * fixpoint — the two must stay in lockstep so server + client agree on which fields
8180
+ * are active.
7843
8181
  */
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>;
8182
+ private _computeVisibleIds;
8183
+ /** Imperative localization for file-validation messages (the pipe covers view strings). */
8184
+ private _t;
8185
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyDynamicFormComponent, never>;
8186
+ 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
8187
  }
7852
8188
 
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;
8189
+ interface FlyCountry {
8190
+ /** ISO 3166-1 alpha-2 code (uppercase), e.g. `AE`. */
8191
+ code: string;
8192
+ /** English display name. */
8193
+ name: string;
8194
+ }
8195
+ /** Frozen alpha-2 list, alphabetical by English name. */
8196
+ declare const FLY_COUNTRIES: readonly FlyCountry[];
8197
+
8198
+ /** Backend `CommentReportReason` enum — string-persisted, member names frozen. */
8199
+ type FlyCommentReportReason = 'Spam' | 'Harassment' | 'OffTopic' | 'Misinformation' | 'Inappropriate' | 'Other';
8200
+ /** Backend `CommentReportStatus` enum — string-persisted, member names frozen. */
8201
+ type FlyCommentReportStatus = 'Open' | 'UnderReview' | 'ActionTaken' | 'Dismissed';
8202
+ /**
8203
+ * One comment, at any depth. `isDeleted` comments render as a tombstone (the
8204
+ * host still supplies `body`-less structural fields so the reply subtree stays
8205
+ * navigable; the component never assumes `body` is present when `isDeleted`).
8206
+ * `canEdit` / `canDelete` / `canModerate` are pre-resolved by the host (Cerbos
8207
+ * decision or equivalent) the component never evaluates authorization itself.
8208
+ */
8209
+ interface FlyComment {
8210
+ id: string;
8211
+ authorUserId: string;
8212
+ authorDisplayName?: string;
8213
+ authorAvatarUrl?: string;
8214
+ parentCommentId?: string;
8215
+ depth: number;
8216
+ body: string;
8217
+ replyCount: number;
8218
+ isEdited: boolean;
8219
+ isDeleted: boolean;
8220
+ createdAt: string;
8221
+ updatedAt?: string;
8222
+ canEdit: boolean;
8223
+ canDelete: boolean;
8224
+ canModerate: boolean;
7895
8225
  }
7896
8226
  /**
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.
8227
+ * A paged slice of comments at one nesting level — mirrors the service's
8228
+ * `GET .../comments?parentCommentId=&page=&pageSize=` shape. Every depth is
8229
+ * paged independently: the root page and each expanded parent's reply page are
8230
+ * separate {@link FlyCommentPage} instances tracked by the host.
7901
8231
  */
7902
- declare class AgentActionUnsupportedDispatchError extends Error {
7903
- readonly dispatch: AgentActionDispatch;
7904
- constructor(dispatch: AgentActionDispatch);
8232
+ interface FlyCommentPage {
8233
+ items: FlyComment[];
8234
+ page: number;
8235
+ pageSize: number;
8236
+ total: number;
8237
+ hasMore: boolean;
8238
+ }
8239
+ /** `loadReplies` request — expand a collapsed reply subtree, one page at a time. */
8240
+ interface FlyCommentLoadRepliesRequest {
8241
+ parentCommentId: string;
8242
+ page: number;
8243
+ }
8244
+ /** `loadMore` request — next page at the root level (`parentCommentId` omitted) or under a parent. */
8245
+ interface FlyCommentLoadMoreRequest {
8246
+ parentCommentId?: string;
8247
+ page: number;
8248
+ }
8249
+ /** `submit` request — a new root comment (`parentCommentId` omitted) or a reply. */
8250
+ interface FlyCommentSubmitRequest {
8251
+ body: string;
8252
+ parentCommentId?: string;
8253
+ }
8254
+ /** `edit` request — author-only body edit of an existing comment. */
8255
+ interface FlyCommentEditRequest {
8256
+ commentId: string;
8257
+ body: string;
8258
+ }
8259
+ /** `delete` request — soft delete (tombstone), author or moderator. */
8260
+ interface FlyCommentDeleteRequest {
8261
+ commentId: string;
8262
+ }
8263
+ /** `report` request — typed reason + optional free-text detail. */
8264
+ interface FlyCommentReportRequest {
8265
+ commentId: string;
8266
+ reason: FlyCommentReportReason;
8267
+ detail?: string;
8268
+ }
8269
+ /** `lockToggle` request — moderator lock/unlock of the whole thread. */
8270
+ interface FlyCommentLockToggleRequest {
8271
+ locked: boolean;
7905
8272
  }
7906
8273
 
7907
8274
  /**
7908
- * Imperative sibling to {@link AgentCommandRegistry} / {@link AgentDropRegistry}.
8275
+ * **`fly-comment-thread`** the design-system generic threaded-comments renderer.
7909
8276
  *
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.
8277
+ * Renders a {@link FlyCommentPage} (root comments) plus lazily-fetched nested
8278
+ * reply pages into an accessible, theme-aware thread: reply composer, inline
8279
+ * edit, soft-delete (tombstone render), typed report-with-reason dialog, and
8280
+ * moderator affordances (lock/unlock, delete-any). It is a platform surface
8281
+ * transport-agnostic like `fly-survey-form` / `fly-dynamic-form`: this component
8282
+ * never calls an API. It only renders the pages the host hands it via
8283
+ * `[rootPage]` / `[repliesByParent]` and emits typed requests
8284
+ * (`loadReplies`/`loadMore`/`submit`/`edit`/`delete`/`report`/`lockToggle`) —
8285
+ * the host owns HTTP, optimistic-insert reconciliation, and any live-update
8286
+ * channel (SignalR, polling, …).
7916
8287
  *
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.
8288
+ * Nested replies are fetched **lazily on expand** (`loadReplies`, never an
8289
+ * eager load-all) the explicit fix for the legacy unpaged-children scale bug
8290
+ * and each level pages independently via `loadMore`.
7920
8291
  *
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.
8292
+ * The `comments` core app owns no user profile data, so `FlyComment` carries
8293
+ * `authorUserId` and an OPTIONAL `authorDisplayName`. Rather than make every host
8294
+ * map names in itself (and render GUIDs when it forgets), bind
8295
+ * `[resolveDisplayName]` to `FlyUserDirectoryService.label` — see
8296
+ * {@link FlyCommentThreadComponent.resolveDisplayName}. Still transport-agnostic:
8297
+ * the resolver is a function the host hands over, not a call this component makes.
8298
+ *
8299
+ * i18n is self-sufficient through the `comment.*` keys in `DS_BASELINE_LOCALES`
8300
+ * (en/ar/fr/ur), overridable by any consumer key of the same name. RTL works via
8301
+ * logical CSS. Styling uses DS theme tokens (`--surface-*`, `--label-*`,
8302
+ * `--separator`, `--fill-*`, `--accent`, `--system-*`) with light-neutral
8303
+ * fallbacks so it stays readable even in a consumer that hasn't mapped them.
8304
+ *
8305
+ * @example
8306
+ * ```html
8307
+ * <fly-comment-thread
8308
+ * [rootPage]="rootPage()"
8309
+ * [repliesByParent]="repliesByParent()"
8310
+ * [currentUserId]="me.id"
8311
+ * [resolveDisplayName]="directory.label"
8312
+ * [canModerateThread]="canModerate()"
8313
+ * [isLocked]="thread().isLocked"
8314
+ * (loadReplies)="onLoadReplies($event)"
8315
+ * (loadMore)="onLoadMore($event)"
8316
+ * (submit)="onSubmit($event)"
8317
+ * (edit)="onEdit($event)"
8318
+ * (delete)="onDelete($event)"
8319
+ * (report)="onReport($event)"
8320
+ * (lockToggle)="onLockToggle($event)" />
8321
+ * ```
7924
8322
  */
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>;
8323
+ declare class FlyCommentThreadComponent {
8324
+ /** The root-level comment page (accumulated across `loadMore` calls by the host). */
8325
+ readonly rootPage: _angular_core.InputSignal<FlyCommentPage>;
7934
8326
  /**
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).
8327
+ * Accumulated reply page per expanded parent (keyed by `parentCommentId`), also
8328
+ * accumulated across `loadMore` calls by the host. Additive to the frozen
8329
+ * contract's `rootPage` input required because a lazily-nested tree has no
8330
+ * other channel to carry fetched reply data back into the component.
7951
8331
  */
7952
- readonly inFlight: _angular_core.WritableSignal<AgentAction<unknown> | null>;
8332
+ readonly repliesByParent: _angular_core.InputSignal<Readonly<Record<string, FlyCommentPage>>>;
8333
+ /** Current sort order label (host owns re-fetch on {@link sortChange}; purely descriptive here otherwise). */
8334
+ readonly sort: _angular_core.InputSignal<"newest" | "oldest">;
8335
+ /** Locked threads disable the composer (root + reply) entirely, for every user. */
8336
+ readonly isLocked: _angular_core.InputSignal<boolean>;
8337
+ /** The signed-in user's id — gates the "report" affordance (can't report your own comment). */
8338
+ readonly currentUserId: _angular_core.InputSignal<string | null>;
8339
+ /** Replay / read-only mode — hides composer, reply, edit, delete, report, and lock affordances.
8340
+ * Aliased to `[readonly]` (the public binding); the class member avoids shadowing the
8341
+ * TypeScript `readonly` keyword. */
8342
+ readonly isReadonly: _angular_core.InputSignal<boolean>;
8343
+ /** Thread-scoped moderator permission (lock/unlock). Additive — distinct from the
8344
+ * per-comment `canModerate` flag, which gates delete-any on that specific comment. */
8345
+ readonly canModerateThread: _angular_core.InputSignal<boolean>;
7953
8346
  /**
7954
- * Push an action onto the bus.
8347
+ * Optional id→name lookup, for the common case where the backend DTO carries only
8348
+ * `authorUserId`. The `comments` core app owns no user profile data, so without
8349
+ * this every host had to map display names into `authorDisplayName` itself — and
8350
+ * a host that forgot rendered raw GUIDs at the reader.
7955
8351
  *
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.
8352
+ * Bind `FlyUserDirectoryService.label` (a signal-backed read-through cache that
8353
+ * resolves on demand and batches). Because the component reads it during render,
8354
+ * the name appears by itself once the lookup lands — the host keeps a plain
8355
+ * page signal, with no `computed` derivation and no priming.
7962
8356
  *
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.
8357
+ * The component still never calls an API: this is a function the HOST supplies,
8358
+ * exactly like `fly-entity-lookup`'s `LOOKUP_APP_NAME_RESOLVER`. Absent (or
8359
+ * returning `null`), rendering falls back as before. See {@link authorLabel} for
8360
+ * the resolution order.
7974
8361
  */
7975
- settle(action: AgentAction): void;
8362
+ readonly resolveDisplayName: _angular_core.InputSignal<((userId: string) => string | null) | null>;
8363
+ /** Expand a collapsed reply subtree — always page 1 of that parent. */
8364
+ readonly loadReplies: _angular_core.OutputEmitterRef<{
8365
+ parentCommentId: string;
8366
+ page: number;
8367
+ }>;
8368
+ /** Fetch the next page at a level: root when `parentCommentId` is omitted, else that parent's replies. */
8369
+ readonly loadMore: _angular_core.OutputEmitterRef<{
8370
+ parentCommentId?: string;
8371
+ page: number;
8372
+ }>;
8373
+ /** A new root comment (`parentCommentId` omitted) or reply. */
8374
+ readonly submit: _angular_core.OutputEmitterRef<{
8375
+ body: string;
8376
+ parentCommentId?: string;
8377
+ }>;
8378
+ /** Author-only body edit of an existing comment. */
8379
+ readonly edit: _angular_core.OutputEmitterRef<{
8380
+ commentId: string;
8381
+ body: string;
8382
+ }>;
8383
+ /** Soft delete (tombstone) — author's own comment, or any comment for a moderator. */
8384
+ readonly delete: _angular_core.OutputEmitterRef<{
8385
+ commentId: string;
8386
+ }>;
8387
+ /** Typed report submission. */
8388
+ readonly report: _angular_core.OutputEmitterRef<{
8389
+ commentId: string;
8390
+ reason: FlyCommentReportReason;
8391
+ detail?: string;
8392
+ }>;
8393
+ /** Moderator lock/unlock of the whole thread. */
8394
+ readonly lockToggle: _angular_core.OutputEmitterRef<{
8395
+ locked: boolean;
8396
+ }>;
8397
+ /** Additive: a sort-order pick from the header control — the host re-fetches page 1 in that order. */
8398
+ readonly sortChange: _angular_core.OutputEmitterRef<"newest" | "oldest">;
8399
+ private readonly _base;
8400
+ readonly reportReasons: readonly FlyCommentReportReason[];
8401
+ /** commentIds whose reply subtree is currently expanded/visible. */
8402
+ private readonly _expanded;
8403
+ /** Composer draft text, keyed by parentCommentId (root uses {@link ROOT_DRAFT_KEY}). */
8404
+ private readonly _drafts;
8405
+ /** commentIds whose reply composer box is currently open (distinct from `_expanded`, which toggles the read view). */
8406
+ private readonly _replyBoxOpen;
8407
+ private readonly _editingId;
8408
+ private readonly _editDraft;
8409
+ private readonly _confirmingDeleteId;
8410
+ private readonly _reportingId;
8411
+ private readonly _reportReason;
8412
+ private readonly _reportDetail;
8413
+ readonly rootDraft: _angular_core.Signal<string>;
8414
+ readonly editingId: _angular_core.Signal<string | null>;
8415
+ readonly confirmingDeleteId: _angular_core.Signal<string | null>;
8416
+ readonly reportingId: _angular_core.Signal<string | null>;
8417
+ readonly reportReason: _angular_core.Signal<FlyCommentReportReason | null>;
8418
+ readonly reportDetail: _angular_core.Signal<string>;
8419
+ fieldId(suffix: string): string;
8420
+ isExpanded(commentId: string): boolean;
8421
+ isReplyBoxOpen(commentId: string): boolean;
8422
+ repliesFor(commentId: string): FlyCommentPage | undefined;
8423
+ replyDraft(commentId: string): string;
8424
+ isOwn(comment: FlyComment): boolean;
7976
8425
  /**
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.
8426
+ * The name to render for a comment's author. Resolution order:
8427
+ * explicit `authorDisplayName` (a host that already knows the name, or a
8428
+ * backend that returns one, always wins) {@link resolveDisplayName}
8429
+ * the raw `authorUserId`.
7983
8430
  *
7984
- * Pass the same action reference returned from {@link inFlight} or held
7985
- * by the dispatcher; identity is the gate.
8431
+ * The raw id remains the last resort rather than a placeholder: it is stable,
8432
+ * unique, and lets a reader correlate two comments by the same author even
8433
+ * when the directory is unreachable.
7986
8434
  */
7987
- cancel(action: AgentAction): void;
7988
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentActionBus, never>;
7989
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentActionBus>;
8435
+ authorLabel(comment: FlyComment): string;
8436
+ /** First character of {@link authorLabel}, for the avatar-initials fallback. */
8437
+ authorInitial(comment: FlyComment): string;
8438
+ canReport(comment: FlyComment): boolean;
8439
+ canReply(): boolean;
8440
+ canDeleteComment(comment: FlyComment): boolean;
8441
+ canEditComment(comment: FlyComment): boolean;
8442
+ formatDate(iso: string): string;
8443
+ onRootDraftChange(value: string): void;
8444
+ submitRoot(): void;
8445
+ toggleReplies(comment: FlyComment): void;
8446
+ private _expand;
8447
+ loadMoreRoot(): void;
8448
+ loadMoreReplies(parentId: string): void;
8449
+ openReplyBox(commentId: string): void;
8450
+ closeReplyBox(commentId: string): void;
8451
+ onReplyDraftChange(commentId: string, value: string): void;
8452
+ submitReply(commentId: string): void;
8453
+ startEdit(comment: FlyComment): void;
8454
+ onEditDraftChange(value: string): void;
8455
+ saveEdit(comment: FlyComment): void;
8456
+ cancelEdit(): void;
8457
+ requestDelete(comment: FlyComment): void;
8458
+ confirmDelete(comment: FlyComment): void;
8459
+ cancelDelete(): void;
8460
+ openReport(comment: FlyComment): void;
8461
+ onReportReasonChange(reason: FlyCommentReportReason): void;
8462
+ onReportDetailChange(value: string): void;
8463
+ submitReport(): void;
8464
+ closeReport(): void;
8465
+ /** Space would otherwise scroll the page while the backdrop (role="button") is focused. */
8466
+ onOverlayBackdropSpace(event: Event): void;
8467
+ toggleLock(): void;
8468
+ onSortPick(next: 'newest' | 'oldest'): void;
8469
+ private _setDraft;
8470
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyCommentThreadComponent, never>;
8471
+ 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
8472
  }
7991
8473
 
7992
8474
  /**
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.
8475
+ * A proof-of-work challenge issued by `Fly.Sdk.Captcha` (`CaptchaChallenge`).
8476
+ * The solver must find `n` in `[0, maxNumber)` such that
8477
+ * `sha256HexLower(salt + n) === challenge`.
8017
8478
  */
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>;
8479
+ interface FlyCaptchaChallenge {
8480
+ /** Always `"SHA-256"` today. */
8481
+ algorithm: string;
8482
+ /** Lowercase-hex SHA-256 of `salt + number` for the secret solution number. */
8483
+ challenge: string;
8484
+ /** Base64 random salt, unique per challenge. */
8485
+ salt: string;
8486
+ /** HMAC signature binding challenge + maxNumber + expiresAt (server-verified). */
8487
+ signature: string;
8488
+ /** Exclusive upper bound of the search space (difficulty). */
8489
+ maxNumber: number;
8490
+ /** ISO-8601 instant after which the solution is rejected. Echoed back verbatim. */
8491
+ expiresAt: string;
8047
8492
  }
8048
-
8049
8493
  /**
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)`.
8494
+ * The solved payload, matching `Fly.Sdk.Captcha.CaptchaSolution` field-for-field
8495
+ * (camelCase Web JSON). Serialized to the opaque base64 token the backend decodes:
8496
+ * `base64(utf8(JSON.stringify(solution)))` i.e. `Convert.ToBase64String` +
8497
+ * `JsonSerializerDefaults.Web`. Every field except `number` is echoed from the
8498
+ * challenge unchanged so the server's stateless signature check passes.
8104
8499
  */
8105
- /** How an action paints. `'text'` is the labelled-pill form (calendar's "Today"). */
8106
- type MagicBarActionKind = 'icon' | 'text' | 'toggle';
8500
+ interface FlyCaptchaSolution {
8501
+ algorithm: string;
8502
+ challenge: string;
8503
+ salt: string;
8504
+ number: number;
8505
+ signature: string;
8506
+ maxNumber: number;
8507
+ expiresAt: string;
8508
+ }
8509
+ /** The solver lifecycle surfaced by the component's `state()` signal. */
8510
+ type FlyCaptchaState = 'idle' | 'verifying' | 'verified' | 'expired' | 'error';
8511
+
8512
+ /** Lowercase-hex SHA-256 of `input` via Web Crypto — matches `Convert.ToHexStringLower(SHA256…)`. */
8513
+ declare function flyCaptchaSha256Hex(input: string): Promise<string>;
8514
+ /** Standard base64 of the UTF-8 bytes of `s` — matches .NET `Convert.ToBase64String`. */
8515
+ declare function flyCaptchaBase64Utf8(s: string): string;
8107
8516
  /**
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.
8517
+ * Builds the opaque base64 token the backend's `CaptchaSolution.FromToken` expects.
8518
+ * Exported so a host (or a test) can construct/verify the token shape without the
8519
+ * component.
8116
8520
  */
8117
- type MagicBarTone = 'default' | 'danger' | 'success';
8521
+ declare function flyCaptchaBuildToken(solution: FlyCaptchaSolution): string;
8118
8522
  /**
8119
- * Interpolation params for one i18n key the `params` argument of
8120
- * `I18nService.t(key, params)`, substituting `{{name}}` placeholders.
8523
+ * **`fly-captcha`** the design-system client for the self-hosted proof-of-work
8524
+ * captcha (`Fly.Sdk.Captcha`).
8121
8525
  *
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.
8526
+ * Given a {@link FlyCaptchaChallenge}, it brute-forces `n` in `[0, maxNumber)`
8527
+ * until `sha256HexLower(salt + n) === challenge`, then emits the solution as the
8528
+ * base64 token the backend decodes ({@link flyCaptchaBuildToken}). The search
8529
+ * hashes candidates with a vendored **synchronous** SHA-256 ({@link sha256HexSync}
8530
+ * from `./sha256` — no `crypto.subtle`/promise overhead per candidate, which used
8531
+ * to dominate solve time), in chunks scheduled on `requestAnimationFrame` so a
8532
+ * chunk boundary still yields back to the browser between slices. It auto-starts
8533
+ * whenever the `[challenge]` input changes. Implements
8534
+ * {@link ControlValueAccessor} so it drops into a reactive form / `[(ngModel)]` —
8535
+ * the control value IS the token string.
8130
8536
  *
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.
8537
+ * Expiry is the host's concern to refresh: the component checks `expiresAt` up
8538
+ * front and while solving, emits {@link expired}, and stops. i18n is
8539
+ * self-sufficient via the `captcha.*` baseline keys; RTL works via logical CSS.
8136
8540
  *
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.
8541
+ * @example
8542
+ * ```html
8543
+ * <fly-captcha [challenge]="challenge()" (solved)="post($event)" (expired)="refresh()" />
8544
+ * ```
8141
8545
  */
8142
- interface MagicBarOwnerRef {
8143
- readonly appId: string;
8144
- readonly windowId?: string;
8546
+ declare class FlyCaptchaComponent implements ControlValueAccessor, OnDestroy {
8547
+ /** The challenge to solve. Setting (or replacing) it auto-starts a fresh solve. */
8548
+ readonly challenge: _angular_core.InputSignal<FlyCaptchaChallenge | null>;
8549
+ /** Candidates hashed synchronously in one slice before yielding back to the browser
8550
+ * on the next animation frame. Higher = fewer `requestAnimationFrame` hops (each
8551
+ * carries ~16ms of frame-boundary overhead), more synchronous work per slice.
8552
+ * 20,000 keeps a slice's own hashing time well under a long-task budget on modern
8553
+ * hardware (the vendored sync SHA-256 does several hundred thousand hashes/sec)
8554
+ * while needing only a handful of hops to clear the library's default difficulty. */
8555
+ readonly chunkSize: _angular_core.InputSignal<number>;
8556
+ /** The solution token once solved — the exact string to POST back to the backend. */
8557
+ readonly solved: _angular_core.OutputEmitterRef<string>;
8558
+ /** The challenge expired (up-front or mid-solve). The host should request a new one. */
8559
+ readonly expired: _angular_core.OutputEmitterRef<void>;
8560
+ readonly state: _angular_core.WritableSignal<FlyCaptchaState>;
8561
+ readonly token: _angular_core.WritableSignal<string | null>;
8562
+ /** Monotonic generation — bumped on every restart/destroy so a stale async chunk
8563
+ * or timer that captured an older generation no-ops instead of racing. */
8564
+ private _gen;
8565
+ private _raf;
8566
+ private _expiryTimer;
8567
+ private _onChange;
8568
+ private _onTouched;
8569
+ constructor();
8570
+ ngOnDestroy(): void;
8571
+ private _restart;
8572
+ /**
8573
+ * Hashes `[start, end)` synchronously with {@link sha256HexSync} — no per-candidate
8574
+ * `await`, so there is no promise/microtask overhead between candidates — then
8575
+ * yields to the next animation frame for the remainder. Bails to `error` state if
8576
+ * hashing itself throws (defensive; a pure function over a short string should not)
8577
+ * or if the whole `[0, maxNumber)` space is exhausted with no match, which means the
8578
+ * challenge is corrupt or was tampered with.
8579
+ */
8580
+ private _solveChunk;
8581
+ private _onSolved;
8582
+ private _cancel;
8583
+ private _schedule;
8584
+ private _cancelSchedule;
8585
+ writeValue(value: string | null): void;
8586
+ registerOnChange(fn: (v: string | null) => void): void;
8587
+ registerOnTouched(fn: () => void): void;
8588
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyCaptchaComponent, never>;
8589
+ 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
8590
  }
8591
+
8146
8592
  /**
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.
8593
+ * Singleton registry of slash commands offered by the agent input palette.
8150
8594
  *
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.
8595
+ * Lives in the DS so it crosses the federation boundary as a single instance via
8596
+ * `sharedMappings: ['@flyos/design-system']`. Federated remotes register
8597
+ * their commands at boot (and dispose on window close) without forking the shell.
8598
+ *
8599
+ * Storage is a signal store. `register` is O(1) for the unique-id case and O(n) when
8600
+ * replacing an existing id (filter then push). The append-then-replace strategy is
8601
+ * deliberate: registrations are rare (each one corresponds to a remote's app-init
8602
+ * effect), and the tradeoff buys us a stable id-collision contract — the *latest*
8603
+ * registration wins, and the previous handle's `dispose()` becomes a no-op rather
8604
+ * than removing the new entry.
8158
8605
  */
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;
8606
+ declare class AgentCommandRegistry {
8607
+ private readonly _commands;
8608
+ /** All currently-registered commands, in insertion order. */
8609
+ readonly all: Signal<readonly AgentCommandRegistration[]>;
8163
8610
  /**
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.
8611
+ * Returns a signal of commands whose scope is `'global'` OR whose `scope.appId` is in
8612
+ * the live app set. The signal recomputes when either the registry or `liveAppIds`
8613
+ * changes pass a `Signal<ReadonlySet<string>>` from the host's app-registry service
8614
+ * for reactive filtering.
8168
8615
  */
8169
- readonly labelKey: string;
8170
- /** Params for {@link labelKey} — the count-badged option ("All statuses ({{count}})"). */
8171
- readonly labelParams?: MagicBarTextParams;
8616
+ visible(liveAppIds: ReadonlySet<string> | Signal<ReadonlySet<string>>): Signal<readonly AgentCommandRegistration[]>;
8172
8617
  /**
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.
8618
+ * Register a single command. Returns a handle whose `dispose()` removes the row by
8619
+ * id. If the same id is later re-registered, the original handle's `dispose()`
8620
+ * becomes a no-op (the newer registration owns the row). Idempotent disposal.
8621
+ */
8622
+ register(cmd: AgentCommandRegistration): AgentCommandHandle;
8623
+ /**
8624
+ * Bulk register. Rolls back on duplicate id within the input batch (throws before any
8625
+ * row lands). Cross-batch duplicates against existing rows follow the standard
8626
+ * "latest wins" rule and do NOT trigger rollback.
8191
8627
  *
8192
- * It participates in the dedupe fingerprint, so a renamed campaign repaints.
8628
+ * Returns a handle whose `dispose()` tears down every row registered by this call.
8193
8629
  */
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;
8630
+ registerAll(cmds: readonly AgentCommandRegistration[]): AgentCommandHandle;
8631
+ /** Tear down by id. Idempotent. */
8632
+ unregister(id: string): void;
8633
+ /** Monotonic counter; identifies which registration call currently owns each id. */
8634
+ private _generation;
8635
+ /** id → generation. Used so a stale handle's `dispose()` is a no-op after replacement. */
8636
+ private readonly _owners;
8637
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentCommandRegistry, never>;
8638
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentCommandRegistry>;
8215
8639
  }
8640
+
8216
8641
  /**
8217
- * Render surface of an action the dedupe key for {@link MagicBarAction}.
8642
+ * Singleton registry of entity lookups offered by the `/lookup` typeahead.
8218
8643
  *
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.
8644
+ * Mirrors {@link AgentCommandRegistry}'s federation-singleton story
8645
+ * (`sharedMappings: ['@flyos/design-system']`), id-collision
8646
+ * "latest wins" contract, and disposable-handle ergonomics. OS-core entities
8647
+ * (note / calendar event / file) register once at shell bootstrap via
8648
+ * `CORE_APP_LOOKUPS`; federated remotes (Circles: scenario / trend / signal)
8649
+ * register at remote-component boot and dispose on window close.
8650
+ *
8651
+ * **Scope semantics diverge from commands.** Commands HIDE when their `appId`
8652
+ * isn't in `liveAppIds`. Lookups DO NOT — they're always offered, and
8653
+ * `{appId}` is just a *priority hint* that bumps that lookup to the top of
8654
+ * the entity picker when the app is live. See {@link LookupRegistration.scope}
8655
+ * for the rationale.
8656
+ *
8657
+ * Storage is a signal store keyed on {@link LookupRegistration.entity}. Because
8658
+ * `entity` is the collision key, an app re-registering the same entity replaces
8659
+ * the prior descriptor; a stale handle's `dispose()` then no-ops.
8222
8660
  */
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;
8661
+ declare class AgentLookupRegistry {
8662
+ private readonly _lookups;
8663
+ /** All currently-registered lookups, in insertion order. */
8664
+ readonly all: Signal<readonly LookupRegistration[]>;
8229
8665
  /**
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.
8666
+ * All registered lookups, sorted by affinity to `liveAppIds`:
8234
8667
  *
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`.
8668
+ * 1. Lookups whose `scope.appId` is in the live app set (in registration
8669
+ * order within that bucket).
8670
+ * 2. Then everything else `'global'` lookups AND scoped lookups whose
8671
+ * app isn't currently live in registration order.
8242
8672
  *
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.
8673
+ * Recomputes when either the registry or `liveAppIds` changes. Pass a
8674
+ * `Signal<ReadonlySet<string>>` from the host's app-registry for reactive
8675
+ * re-sorting. **Always returns the full registry** see the type doc on
8676
+ * {@link LookupRegistration.scope} for why this differs from
8677
+ * {@link AgentCommandRegistry.visible}.
8269
8678
  */
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;
8275
- /**
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.
8679
+ visible(liveAppIds: ReadonlySet<string> | Signal<ReadonlySet<string>>): Signal<readonly LookupRegistration[]>;
8680
+ /**
8681
+ * Register one lookup. Returns a handle whose `dispose()` removes the row by
8682
+ * `entity`. A later re-registration of the same entity makes the original
8683
+ * handle's `dispose()` a no-op (the newer registration owns the row).
8279
8684
  */
8280
- readonly shortcut?: string;
8281
- }
8282
- /** What a publisher supplies for one action. */
8283
- interface MagicBarAction extends MagicBarActionSpec {
8284
- readonly menu?: MagicBarRadioMenu;
8685
+ register(lookup: LookupRegistration): LookupHandle;
8285
8686
  /**
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.
8687
+ * Bulk register. Rolls back on a duplicate entity WITHIN the input batch
8688
+ * (throws before any row lands). Cross-batch duplicates against existing rows
8689
+ * follow the standard "latest wins" rule and do NOT trigger rollback.
8290
8690
  */
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;
8691
+ registerAll(lookups: readonly LookupRegistration[]): LookupHandle;
8300
8692
  /**
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.
8693
+ * Resolve a deep-link anchor to a concrete launch target.
8314
8694
  *
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.
8695
+ * `kind` is the dotted `<appId>.<entity>` token the agents backend emits
8696
+ * inside `flyos:<kind>/<id>` chat-answer anchors the same entity-kind
8697
+ * vocabulary as drag-payload kinds and `ref` parts. Returns
8698
+ * `{ appId, route }` when a registered lookup for that `(appId, entity)`
8699
+ * pair carries a {@link LookupDescriptor.deepLinkRoute} template; `null`
8700
+ * otherwise (unknown entity, app mismatch, or no template e.g. the
8701
+ * owning app isn't installed) so the caller renders plain text rather than
8702
+ * a dead link.
8322
8703
  *
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.
8704
+ * `appId` and `entity` are both dot-free by their own grammars, so the
8705
+ * FIRST dot is the unambiguous split point; a dotless `kind` can't carry an
8706
+ * app and never resolves. The template's single `{id}` placeholder is
8707
+ * substituted URL-encoded.
8708
+ */
8709
+ resolveDeepLink(kind: string, id: string): {
8710
+ readonly appId: string;
8711
+ readonly route: string;
8712
+ } | null;
8713
+ /**
8714
+ * Resolve a deep-link target from a bare `(entity, id)` pair — the shape a
8715
+ * `/lookup` ref carries (it has no `<appId>.<entity>` kind token; the owning
8716
+ * app is implicit in the registered descriptor). `entity` is the registry's
8717
+ * unique storage key, so it identifies the descriptor unambiguously without
8718
+ * an app prefix.
8329
8719
  *
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.
8720
+ * Returns `{ appId, route }` (the descriptor's {@link LookupDescriptor.appId}
8721
+ * / affinity `scope.appId` as the owner, `{id}` substituted URL-encoded) when
8722
+ * a matching descriptor carries a {@link LookupDescriptor.deepLinkRoute};
8723
+ * `null` otherwise (unknown entity, no template, or the owning app has since
8724
+ * unregistered) so callers render plain text rather than a dead link — the
8725
+ * same graceful-degrade contract as {@link resolveDeepLink}.
8335
8726
  */
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[];
8727
+ resolveDeepLinkForEntity(entity: string, id: string): {
8728
+ readonly appId: string;
8729
+ readonly route: string;
8730
+ } | null;
8731
+ /** Tear down by entity. Idempotent. */
8732
+ unregister(entity: string): void;
8733
+ /** Monotonic counter; identifies which registration call currently owns each entity. */
8734
+ private _generation;
8735
+ /** entity → generation. Lets a stale handle's `dispose()` no-op after replacement. */
8736
+ private readonly _owners;
8737
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentLookupRegistry, never>;
8738
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentLookupRegistry>;
8351
8739
  }
8740
+
8352
8741
  /**
8353
- * Render surface of the expanding search field — the dedupe key for
8354
- * {@link MagicBarSearch}.
8742
+ * Singleton registry of chip-renderer components and keyboard-alternative draggable
8743
+ * items, keyed by `kind` and `appId`.
8355
8744
  *
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.
8745
+ * Like {@link AgentCommandRegistry}, this lives in the DS so it crosses the federation
8746
+ * boundary as a single instance. Hosts (the shell `<fly-agent-input>`) lookup
8747
+ * renderers; remotes register them.
8748
+ *
8749
+ * Renderer lookup is `O(n)` over the registered list — the registry is small (one or
8750
+ * two entries per app) and lookups happen on drop, not per frame.
8751
+ *
8752
+ * Draggable storage is per-`appId` writable signal cached in a Map, so each `appId`
8753
+ * gets a stable {@link Signal} reference across reads (callers can `===`-compare).
8358
8754
  */
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;
8755
+ declare class AgentDropRegistry {
8756
+ private readonly _renderers;
8757
+ /** Per-appId writable store for draggables. Read-only mirror returned to callers. */
8758
+ private readonly _draggablesByApp;
8364
8759
  /**
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.
8760
+ * Look up the chip-renderer component class for a `kind`. The newest registration for
8761
+ * a given `kind` wins, regardless of `appId` — we scan the list in reverse so a later
8762
+ * `register` call shadows an earlier one for the same `kind`.
8405
8763
  *
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.
8764
+ * Returns `null` when no renderer is registered; the host falls back to a generic
8765
+ * `plainTextFallback` chip.
8413
8766
  */
8414
- readonly queryText?: string;
8767
+ rendererFor(kind: string): Type<AgentChipHostInputs> | null;
8415
8768
  /**
8416
- * Expanded width in px a **desktop layout hint**, carried verbatim.
8769
+ * Register a renderer for a `(kind, appId)` pair. Re-registering the same pair
8770
+ * replaces the prior entry; the disposal handle for the prior registration becomes
8771
+ * a no-op.
8417
8772
  *
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.
8773
+ * Returns a {@link AgentCommandHandle} (re-used to keep the disposable shape uniform
8774
+ * across registries) whose `dispose()` removes this exact registration.
8424
8775
  */
8425
- readonly openWidth?: number;
8426
- /** Accent dot on the Filters button — a non-default filter is applied. */
8427
- readonly filtersActive?: boolean;
8776
+ register<T = unknown>(reg: AgentDropRendererRegistration<T>): AgentCommandHandle;
8777
+ /**
8778
+ * Apps publish their live "draggable from focused window" set so the keyboard
8779
+ * "Attach from app…" menu can offer them. Hosts call this each time the user-visible
8780
+ * draggable list changes; passing an empty array clears the entry for this `appId`.
8781
+ */
8782
+ publishDraggables(appId: string, items: readonly AgentDraggableItem[]): void;
8783
+ /**
8784
+ * Reactive read of the draggable set published for `appId`. Empty when none. The
8785
+ * returned signal is stable across calls (cached by `appId`), so consumers can use
8786
+ * it as a stable input to `computed()`.
8787
+ */
8788
+ draggablesFor(appId: string): Signal<readonly AgentDraggableItem[]>;
8789
+ /** Lazy-init the per-appId writable bucket. Returns the writable handle for internal use. */
8790
+ private bucketFor;
8791
+ private _generation;
8792
+ private readonly _owners;
8793
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentDropRegistry, never>;
8794
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentDropRegistry>;
8795
+ }
8796
+
8797
+ /**
8798
+ * Imperative action published by an app, consumed by the agent panel.
8799
+ *
8800
+ * Re-uses {@link AgentDragPayload} as the wire envelope so a dragged item
8801
+ * and a programmatic "Explain" click are byte-for-byte the same shape —
8802
+ * renderers, validators, and serialisation paths never fork on transport.
8803
+ *
8804
+ * Adding a verb is a minor DS bump (consumers ignore unknown verbs in
8805
+ * their `switch`). Removing one is a major DS bump.
8806
+ */
8807
+ type AgentActionVerb = 'explain' | 'why-empty' | 'compose-query' | 'compare' | 'summarize';
8808
+ /**
8809
+ * Whether the agent panel sends the staged payload immediately or stages
8810
+ * the chip for the user to edit and send manually.
8811
+ *
8812
+ * Phase 1 (DS v2.6.0) supports `'stage'` only. Dispatching with `'auto'`
8813
+ * throws {@link AgentActionUnsupportedDispatchError} so callers don't
8814
+ * silently fail. `'auto'` lands once `AgentInputComponent.programmaticSubmit`
8815
+ * is exposed and reviewed against the input's state machine.
8816
+ */
8817
+ type AgentActionDispatch = 'auto' | 'stage';
8818
+ interface AgentAction<T = unknown> {
8819
+ /** Intent the agent should apply to {@link payload}. */
8820
+ readonly verb: AgentActionVerb;
8821
+ /** The wire envelope. Validated against {@link validateAgentPayload}'s
8822
+ * size caps before the bus fans it out. */
8823
+ readonly payload: AgentDragPayload<T>;
8824
+ /** Optional slash command id to bind before send (e.g. `'explain-report'`).
8825
+ * Phase 1 captures this for telemetry only — actual binding lands when
8826
+ * the input's programmatic-send API ships. An unknown id is dropped
8827
+ * silently with a console warning, the chip still arrives. */
8828
+ readonly autoCommandId?: string;
8829
+ /** Phase 1 supports `'stage'` only. See {@link AgentActionDispatch}. */
8830
+ readonly dispatch: AgentActionDispatch;
8831
+ /** Source DOM rect for the FLIP entry animation. Omit to skip the
8832
+ * animation (e.g. dispatching from a keyboard shortcut with no anchor). */
8833
+ readonly originRect?: DOMRect;
8834
+ /** Optional HTML snippet rendered inside the flight ghost. Callers are
8835
+ * responsible for escaping untrusted text — the bus does not sanitise.
8836
+ * Defaults (when omitted) to a strong-wrapped escape of the payload's
8837
+ * `plainTextFallback` rendered by the panel host. */
8838
+ readonly originPreviewHtml?: string;
8428
8839
  }
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;
8840
+ /**
8841
+ * Thrown synchronously by {@link AgentActionBus.dispatch} when a caller
8842
+ * supplies a dispatch mode this DS version doesn't implement yet. Catching
8843
+ * by class name lets a forward-compatible caller fall back to `'stage'`
8844
+ * without depending on instanceof across federation boundaries.
8845
+ */
8846
+ declare class AgentActionUnsupportedDispatchError extends Error {
8847
+ readonly dispatch: AgentActionDispatch;
8848
+ constructor(dispatch: AgentActionDispatch);
8439
8849
  }
8440
- interface MagicBarSearchView extends MagicBarSearchSpec {
8441
- /** True iff the publisher can open an advanced-search overlay. */
8442
- readonly hasFilters: boolean;
8850
+
8851
+ /**
8852
+ * Imperative sibling to {@link AgentCommandRegistry} / {@link AgentDropRegistry}.
8853
+ *
8854
+ * Apps call {@link dispatch} to push a typed {@link AgentAction} onto the bus;
8855
+ * the agent panel subscribes once at construct and routes by verb. The bus
8856
+ * itself is a thin pass-through — it does NOT decide UI behaviour. The
8857
+ * subscriber (agent-panel) owns: showing the panel, staging the chip,
8858
+ * triggering the flight animation, and binding the command. This keeps the
8859
+ * DS free of host policy.
8860
+ *
8861
+ * Federation-safe: `providedIn: 'root'` + `sharedMappings: ['@flyos/design-system']`
8862
+ * give every federated remote the same singleton, so a remote's "Explain"
8863
+ * button reaches the host's panel without any cross-bundle wiring.
8864
+ *
8865
+ * Validation runs synchronously inside `dispatch` so a caller that sends an
8866
+ * oversize payload sees the throw at their site, not on the subscriber. The
8867
+ * subscriber therefore never has to defend against malformed envelopes.
8868
+ */
8869
+ declare class AgentActionBus {
8870
+ private readonly _actions$;
8871
+ /** Hot stream of actions in dispatch order. Subscribers receive only
8872
+ * actions dispatched AFTER they subscribe — late subscribers see nothing
8873
+ * retroactively. Use {@link lastAction} for the latest snapshot. */
8874
+ readonly actions$: Observable<AgentAction>;
8875
+ /** Most recent action — for DevTools, smoke tests, and late-subscriber
8876
+ * catch-up. Null until the first successful dispatch. */
8877
+ readonly lastAction: _angular_core.WritableSignal<AgentAction<unknown> | null>;
8443
8878
  /**
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.
8879
+ * The action currently being processed by the subscriber, or null when
8880
+ * none. Set by {@link dispatch} immediately before emitting on
8881
+ * {@link actions$}; cleared by the subscriber via {@link settle} once
8882
+ * it finishes its handler (success or fail). Lets the dispatcher render
8883
+ * a busy state on the originating control — e.g. a card swapping its
8884
+ * sparkle icon for a spinner while the agent panel mints the optimistic
8885
+ * thread and starts the request. Identity check (`bus.inFlight() === act`)
8886
+ * is the panel-side contract; dispatchers usually project to a stable id
8887
+ * inside the payload (e.g. <c>reportId</c>) to scope busy-state visually.
8888
+ *
8889
+ * If multiple dispatches race, the latest wins — the prior in-flight
8890
+ * action is dropped on the floor here (the panel may still handle it,
8891
+ * but the dispatcher's busy indicator follows the newer action). Apps
8892
+ * that need stricter single-flight semantics should guard at the call
8893
+ * site (the agent-panel's <c>_pendingTempThreadId</c> already does so
8894
+ * for the explain verb).
8448
8895
  */
8449
- query(text: string): void;
8896
+ readonly inFlight: _angular_core.WritableSignal<AgentAction<unknown> | null>;
8450
8897
  /**
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.
8898
+ * Push an action onto the bus.
8899
+ *
8900
+ * Throws synchronously when:
8901
+ * - `dispatch === 'auto'` (not implemented in this DS version) see
8902
+ * {@link AgentActionUnsupportedDispatchError}.
8903
+ * - the payload fails {@link validateAgentPayload} (oversize, invalid
8904
+ * version, invalid kind). The error message carries the field path
8905
+ * so the caller can fix the offending field.
8906
+ *
8907
+ * Subscribers see the action via {@link actions$} on the next tick of
8908
+ * the Subject; the {@link lastAction} signal updates synchronously
8909
+ * before the Subject emits so an effect reading both stays consistent.
8455
8910
  */
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 {
8911
+ dispatch<T>(action: AgentAction<T>): void;
8466
8912
  /**
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.
8913
+ * Subscriber contract: call after the handler for {@link inFlight}
8914
+ * completes (success or fail). Only clears {@link inFlight} if it still
8915
+ * points at the passed action a no-op when a later dispatch already
8916
+ * superseded it. Pass the same action reference the subscriber received
8917
+ * from {@link actions$}; identity is the gate.
8470
8918
  */
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;
8919
+ settle(action: AgentAction): void;
8477
8920
  /**
8478
- * Ids of {@link AgentCommand}s this view considers most relevant, most-relevant
8479
- * first the Ask-AI menu's per-view sets.
8921
+ * Semantic alias of {@link settle} for explicit user-driven cancellation
8922
+ * e.g. a future "Stop" button in the agent input tray, or a dispatcher
8923
+ * teardown that wants to abandon its own in-flight action. Identical
8924
+ * runtime behaviour (identity check + clear), but the two-method surface
8925
+ * lets the UI distinguish "handler finished" from "user said no" in
8926
+ * telemetry / logs without sniffing a "reason" parameter.
8480
8927
  *
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.
8928
+ * Pass the same action reference returned from {@link inFlight} or held
8929
+ * by the dispatcher; identity is the gate.
8487
8930
  */
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[];
8931
+ cancel(action: AgentAction): void;
8932
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentActionBus, never>;
8933
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentActionBus>;
8504
8934
  }
8935
+
8505
8936
  /**
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.
8937
+ * FLIP-style entry animation for payloads landing in the agent panel.
8938
+ *
8939
+ * Pure DOM + Web Animations API no Chart.js, no Angular animations module,
8940
+ * no CSS transitions racing layout. Honours `prefers-reduced-motion`: the
8941
+ * ghost is appended then removed without animating when the user asked for
8942
+ * less motion (so DOM side-effects stay consistent).
8943
+ *
8944
+ * Lifecycle:
8945
+ * 1. The agent panel calls {@link registerTarget} in `ngAfterViewInit`
8946
+ * with its header element.
8947
+ * 2. A source app dispatches an `AgentAction` carrying an `originRect`
8948
+ * from `getBoundingClientRect()` on the click target.
8949
+ * 3. The bus subscriber calls {@link flyInto} with that rect.
8950
+ * 4. The animator creates a fixed-position ghost at the origin, animates
8951
+ * transform + opacity toward the registered target's rect, then
8952
+ * removes itself on `onfinish` / `oncancel`.
8953
+ *
8954
+ * Uses `getBoundingClientRect()` (physical viewport coords) so the animation
8955
+ * is RTL-correct without inset-inline math — the rect already encodes the
8956
+ * physical position regardless of `dir`.
8957
+ *
8958
+ * The 900 ms duration and easing curve are deliberately hardcoded — making
8959
+ * them configurable surfaces an API the host can't usefully tune without
8960
+ * understanding motion design as a whole.
8512
8961
  */
8513
- interface MagicBarPublisher {
8514
- readonly owner: MagicBarOwnerRef;
8962
+ declare class AgentFlightAnimator {
8963
+ /** Hardcoded — see class doc. */
8964
+ private static readonly DURATION_MS;
8965
+ private static readonly EASING;
8966
+ /** Floor the target/source scale ratio so a tiny target rect doesn't
8967
+ * collapse the ghost to invisibility before the animation finishes. */
8968
+ private static readonly MIN_SCALE;
8969
+ private targetEl;
8970
+ /** Called by the panel host to publish where flights should land. Pass
8971
+ * `null` on destroy so a re-mounted panel doesn't leave the animator
8972
+ * pointing at a detached node. */
8973
+ registerTarget(el: HTMLElement | null): void;
8515
8974
  /**
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.
8975
+ * Animate a ghost element from {@link from} to the registered target's
8976
+ * rect. No-ops when:
8977
+ * - no target is registered (silent — panel may not be mounted yet)
8978
+ * - running outside a browser (SSR safety)
8979
+ * - the user has `prefers-reduced-motion: reduce` set (DOM is still
8980
+ * touched so callers see consistent side-effects, but no animation
8981
+ * runs)
8518
8982
  *
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.
8983
+ * The ghost is appended to `document.body` (not the panel) so a parent
8984
+ * `overflow: hidden` on the panel can't clip the flight path.
8534
8985
  */
8535
- dispose(): void;
8986
+ flyInto(from: DOMRect, opts?: {
8987
+ previewHtml?: string;
8988
+ }): void;
8989
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AgentFlightAnimator, never>;
8990
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AgentFlightAnimator>;
8536
8991
  }
8537
8992
 
8538
8993
  /**
@@ -8813,6 +9268,36 @@ interface MagicBarSearchSeed {
8813
9268
  */
8814
9269
  declare function magicBarSearchText(previous: MagicBarSearchSeed | null, next: MagicBarSearchSeed): string | null;
8815
9270
 
9271
+ /**
9272
+ * Order one group's items the way every FlyOS bar orders them.
9273
+ *
9274
+ * **Icon-only actions occupy the outer edge of the flow — the right in LTR — in the precedence
9275
+ * Add, Delete, Edit reading inward; labelled (icon+label) actions sit inside them.**
9276
+ *
9277
+ * The outer edge is where the hand and the eye finish, so it belongs to the actions that need no
9278
+ * reading: a bare glyph is fastest to hit and slowest to identify, and putting the glyphs where
9279
+ * they are found by position rather than by reading is what makes that trade pay. A labelled
9280
+ * action is identified by its text wherever it sits, so it gives the edge up.
9281
+ *
9282
+ * Order is expressed as FLOW order, never as physical left/right: the bar mirrors under RTL, so
9283
+ * "outermost" becomes the left edge in `ar`/`ur` automatically. Encoding it as a physical side
9284
+ * would be a bug in three of this platform's four locales.
9285
+ *
9286
+ * Stable within each band — callers keep authoring items in whatever order reads best in source,
9287
+ * and two labelled actions stay in the order they were written (PPM's "Promote to schedule" stays
9288
+ * after "Add backlog task").
9289
+ *
9290
+ * ## Why this lives in the design system
9291
+ * It shipped first inside PPM (`ppm/shared/magic-bar-order.util.ts`) and
9292
+ * `skills/magic-bar-view-contributions.md` §7 named that file as the reference implementation —
9293
+ * which is exactly the shape of a rule that is about to be copy-pasted into the next External App.
9294
+ * The ordering is a property of the BAR, not of any one app: the same renderer paints every
9295
+ * contribution, so two apps disagreeing about it is a platform-visible inconsistency, not a local
9296
+ * style choice. Same reasoning as {@link magicBarSearchText} — a documented-but-unimplemented rule
9297
+ * gets re-derived per consumer, and the copies disagree on precisely the case that is hard to see.
9298
+ */
9299
+ declare function orderMagicBarItems(items: readonly MagicBarAction[]): MagicBarAction[];
9300
+
8816
9301
  /**
8817
9302
  * Allowlist parser for {@link MagicBarActionSpec.iconPath} — the geometry-only
8818
9303
  * gate between a publisher-supplied glyph fragment and the DOM.
@@ -9034,6 +9519,15 @@ declare class FlyMagicActionsComponent {
9034
9519
  * what stops `@for (…; track $index)` rebuilding the glyph each time.
9035
9520
  */
9036
9521
  protected iconNodes(action: MagicBarActionView): readonly FlyMagicBarIconNode[];
9522
+ /**
9523
+ * Whether this action has a glyph to paint at all.
9524
+ *
9525
+ * Derived from the PARSED nodes, never from `iconPath` being a non-empty string: a fragment the
9526
+ * allowlist rejected parses to zero nodes, and treating that as "has an icon" would paint an
9527
+ * empty `<svg>` box — on a labelled action, a permanent gap in front of the text. The parse
9528
+ * memoizes, so asking twice costs nothing.
9529
+ */
9530
+ protected hasGlyph(action: MagicBarActionView): boolean;
9037
9531
  /** `null` (not `'default'`) so `[attr.data-tone]` omits the attribute entirely for the common case. */
9038
9532
  protected toneAttr(action: MagicBarActionView): string | null;
9039
9533
  /**
@@ -9164,6 +9658,17 @@ declare const FLY_MAGIC_BAR_ICONS: {
9164
9658
  * the settings gear, not the mode switcher).
9165
9659
  */
9166
9660
  readonly calendarView: "<rect x=\"3.5\" y=\"3.5\" width=\"17\" height=\"17\" rx=\"2.5\"/><path d=\"M3.5 12h17M12 3.5v17\"/>";
9661
+ /**
9662
+ * Gantt / outline "Indent" — the block pushed in, with the arrow pointing INTO the indent.
9663
+ * Pairs with {@link outdent}: the two differ only in the arrow, which is the convention every
9664
+ * word processor and desktop planner has shipped for decades and the reason both stay icon-only.
9665
+ *
9666
+ * Directional, so publishers pass `mirrorInRtl: true` alongside it — the block sits on the
9667
+ * reading-start side, which is the right edge in `ar`/`ur`.
9668
+ */
9669
+ 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\"/>";
9670
+ /** Gantt / outline "Outdent" — {@link indent}'s block with the arrow pulling back out. */
9671
+ 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
9672
  /** `UX/FlyOS Desktop-app/MainContent.dc.html:927` (`pubTabActions`'s local `P` table). */
9168
9673
  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
9674
  /** `MainContent.dc.html:928`. Reused for every "Edit …" action (evidence, link, task). */
@@ -14314,6 +14819,6 @@ declare const AUDIENCE_ERROR_CODES: {
14314
14819
  };
14315
14820
  type AudienceErrorCode = (typeof AUDIENCE_ERROR_CODES)[keyof typeof AUDIENCE_ERROR_CODES];
14316
14821
 
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 };
14822
+ 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_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_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, 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 };
14823
+ 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, 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
14824
  //# sourceMappingURL=flyos-design-system.d.ts.map