@artemis-studio/plugin-sdk 2026.9.37 → 2026.9.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -3,8 +3,11 @@
3
3
  const outsideStudio = (name) => () => {
4
4
  throw new Error(`${name}: @artemis-studio/plugin-sdk is provided by Artemis Studio at runtime; load this plugin in Studio.`);
5
5
  };
6
+ export const ACTION_SECTIONS = outsideStudio('ACTION_SECTIONS');
7
+ export const ActionMenuItem = outsideStudio('ActionMenuItem');
6
8
  export const ApiError = outsideStudio('ApiError');
7
9
  export const CONTRACT = outsideStudio('CONTRACT');
10
+ export const CapabilityGate = outsideStudio('CapabilityGate');
8
11
  export const ConfirmByTyping = outsideStudio('ConfirmByTyping');
9
12
  export const NAV_GROUPS = outsideStudio('NAV_GROUPS');
10
13
  export const NodeOutcomeSummary = outsideStudio('NodeOutcomeSummary');
@@ -15,6 +18,7 @@ export const VirtualTable = outsideStudio('VirtualTable');
15
18
  export const clusterKey = outsideStudio('clusterKey');
16
19
  export const clusterRoute = outsideStudio('clusterRoute');
17
20
  export const definePlugin = outsideStudio('definePlugin');
21
+ export const gateFor = outsideStudio('gateFor');
18
22
  export const notify = outsideStudio('notify');
19
23
  export const pluginApi = outsideStudio('pluginApi');
20
24
  export const pluginPath = outsideStudio('pluginPath');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artemis-studio/plugin-sdk",
3
- "version": "2026.9.37",
3
+ "version": "2026.9.39",
4
4
  "description": "Build the UI of an Artemis Studio plugin: typings for the Studio APIs a plugin may use, and a Vite preset.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -0,0 +1,14 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * Hosts the dialogs row actions open (ADR-0107), outside any grid: a dialog mounted in a
4
+ * virtualized row is unmounted when the row scrolls away or when the refresh its own success
5
+ * triggers removes the row, and its outcome goes with it.
6
+ *
7
+ * <p>A dialog is mounted closed and opened on the next frame. Dialogs take their dry-run preview
8
+ * in `onEnterTransitionEnd`, which a modal mounted already open never fires. On close it is kept
9
+ * for its exit transition, then removed, and focus goes back to what opened it — unless the dialog
10
+ * navigated away, where moving focus would be a jump the operator did not ask for.
11
+ */
12
+ export declare function ActionHostProvider({ children }: {
13
+ children: ReactNode;
14
+ }): import("react").JSX.Element;
@@ -0,0 +1,4 @@
1
+ import type { ActionHost } from './types.ts';
2
+ export declare const HostContext: import("react").Context<ActionHost | null>;
3
+ /** The action host of the surrounding cluster layout (or test harness). */
4
+ export declare function useActionHost(): ActionHost;
@@ -0,0 +1,130 @@
1
+ import type { ComponentType, ReactNode } from 'react';
2
+ import type { components } from '../api/schema.d.ts';
3
+ import type { GateVerdict } from '../../ui/capabilityGate.ts';
4
+ type Schemas = components['schemas'];
5
+ /**
6
+ * The sections of a row's action menu, in the order it shows them (ADR-0107). The list is closed,
7
+ * like the navigation groups: an action names one, and adding one is a kernel change.
8
+ */
9
+ export declare const ACTION_SECTIONS: readonly [{
10
+ readonly id: "open";
11
+ readonly label: "Open";
12
+ }, {
13
+ readonly id: "copy";
14
+ readonly label: "Copy";
15
+ }, {
16
+ readonly id: "operate";
17
+ readonly label: "Operate";
18
+ }, {
19
+ readonly id: "destroy";
20
+ readonly label: "Destroy";
21
+ }];
22
+ export type ActionSection = (typeof ACTION_SECTIONS)[number]['id'];
23
+ /**
24
+ * `act` offers everything; `navigate` only the Open and Copy sections, for a view that must never
25
+ * change the broker (Flow).
26
+ */
27
+ export type ActionMode = 'act' | 'navigate';
28
+ export interface QueueTarget {
29
+ queueName: string;
30
+ address?: string | null;
31
+ snapshot?: Schemas['QueueView'];
32
+ }
33
+ export interface AddressTarget {
34
+ address: string;
35
+ snapshot?: Schemas['AddressView'];
36
+ }
37
+ /** Connections, sessions, consumers and producers are per node; a close needs to know which. */
38
+ interface NodeScoped {
39
+ nodeId: string;
40
+ nodeName: string;
41
+ }
42
+ export interface ConnectionTarget extends NodeScoped {
43
+ connectionId: string;
44
+ snapshot?: Schemas['ConnectionView'];
45
+ }
46
+ export interface SessionTarget extends NodeScoped {
47
+ sessionId: string;
48
+ connectionId?: string | null;
49
+ snapshot?: Schemas['SessionView'];
50
+ }
51
+ export interface ConsumerTarget extends NodeScoped {
52
+ consumerId: string;
53
+ queueName?: string | null;
54
+ sessionId?: string | null;
55
+ snapshot?: Schemas['ConsumerView'];
56
+ }
57
+ export interface ProducerTarget extends NodeScoped {
58
+ producerId: string;
59
+ address?: string | null;
60
+ sessionId?: string | null;
61
+ snapshot?: Schemas['ProducerView'];
62
+ }
63
+ export interface MessageTarget {
64
+ queueName: string;
65
+ messageId: number;
66
+ /** The node the message was browsed on; absent for the live node. */
67
+ node?: string;
68
+ }
69
+ export interface DivertTarget {
70
+ name: string;
71
+ snapshot?: Schemas['DivertView'];
72
+ }
73
+ /** A client of the flow view: an identity that may stand for many connections. */
74
+ export interface ClientTarget {
75
+ label: string;
76
+ }
77
+ /** Each resource kind a row menu or a link can be about, with its target. */
78
+ export interface ActionTargets {
79
+ queue: QueueTarget;
80
+ address: AddressTarget;
81
+ connection: ConnectionTarget;
82
+ session: SessionTarget;
83
+ consumer: ConsumerTarget;
84
+ producer: ProducerTarget;
85
+ message: MessageTarget;
86
+ divert: DivertTarget;
87
+ client: ClientTarget;
88
+ }
89
+ export type ActionKind = keyof ActionTargets;
90
+ /** What the action host gives a dialog it hosts. */
91
+ export interface HostedDialogProps {
92
+ opened: boolean;
93
+ onClose: () => void;
94
+ }
95
+ type Blocked = Extract<GateVerdict, {
96
+ kind: 'blocked';
97
+ }>;
98
+ /**
99
+ * Where an action's dialog lives (ADR-0107): outside the grid, so it outlives the row that opened
100
+ * it, the menu that offered it, and the refresh that removes the row when the action succeeds.
101
+ */
102
+ export interface ActionHost {
103
+ /**
104
+ * Opens `Dialog` with `props`, plus `opened` and `onClose`. On close, focus goes back through
105
+ * `restoreFocus` — unless the dialog navigated away.
106
+ */
107
+ open<P extends HostedDialogProps>(Dialog: ComponentType<P>, props: NoInfer<Omit<P, keyof HostedDialogProps>>, options?: {
108
+ restoreFocus?: () => void;
109
+ }): void;
110
+ /** Explains why an action is unavailable, in full. */
111
+ explain(verdict: Blocked, what: string, options?: {
112
+ restoreFocus?: () => void;
113
+ }): void;
114
+ /** Copies `text` and says so; `what` names it ("queue name"). */
115
+ copy(text: string, what: string): void;
116
+ }
117
+ /** What a row-action contribution is given. */
118
+ export interface ActionProps<T> {
119
+ clusterId: string;
120
+ target: T;
121
+ host: ActionHost;
122
+ mode: ActionMode;
123
+ }
124
+ /** What a link contribution is given: it wraps `children` (the name) in a link to the resource. */
125
+ export interface LinkProps<T> {
126
+ clusterId: string;
127
+ target: T;
128
+ children: ReactNode;
129
+ }
130
+ export {};
@@ -4568,6 +4568,12 @@ export interface components {
4568
4568
  matchesAvailable: number;
4569
4569
  note?: string | null;
4570
4570
  };
4571
+ MetricNodeSeries: {
4572
+ nodeId: string;
4573
+ nodeName: string;
4574
+ sampled: boolean;
4575
+ series: components["schemas"]["MetricSeries"][];
4576
+ };
4571
4577
  MetricPoint: {
4572
4578
  /** Format: date-time */
4573
4579
  ts: string;
@@ -4590,6 +4596,8 @@ export interface components {
4590
4596
  step: string;
4591
4597
  truncated: boolean;
4592
4598
  series: components["schemas"]["MetricSeries"][];
4599
+ splitBy?: string | null;
4600
+ byNode?: components["schemas"]["MetricNodeSeries"][] | null;
4593
4601
  };
4594
4602
  FlowBrokerNodeView: {
4595
4603
  nodeId?: string;
@@ -4609,6 +4617,14 @@ export interface components {
4609
4617
  consumersTotal: number;
4610
4618
  truncated: boolean;
4611
4619
  brokerXmlSnippet?: string;
4620
+ /** Format: int64 */
4621
+ backlog?: number | null;
4622
+ /** Format: int64 */
4623
+ consumers?: number | null;
4624
+ /** Format: double */
4625
+ inRate?: number | null;
4626
+ /** Format: double */
4627
+ outRate?: number | null;
4612
4628
  };
4613
4629
  FlowEdgeView: {
4614
4630
  id?: string;
@@ -4639,6 +4655,7 @@ export interface components {
4639
4655
  presentOf?: number;
4640
4656
  studio: boolean;
4641
4657
  faults?: ("NO_CONSUMER" | "STALLED" | "BRIDGE_DOWN" | "PARTIAL_PRESENCE")[];
4658
+ byNode?: components["schemas"]["FlowNodeRate"][] | null;
4642
4659
  };
4643
4660
  FlowFocusView: {
4644
4661
  kind?: string;
@@ -4674,6 +4691,28 @@ export interface components {
4674
4691
  /** Format: int32 */
4675
4692
  faults: number;
4676
4693
  };
4694
+ FlowNodeRate: {
4695
+ nodeId: string;
4696
+ node: string;
4697
+ /** Format: double */
4698
+ rate?: number | null;
4699
+ /** Format: date-time */
4700
+ asOf?: string | null;
4701
+ stale: boolean;
4702
+ };
4703
+ FlowNodeShare: {
4704
+ nodeId: string;
4705
+ node: string;
4706
+ /** Format: int64 */
4707
+ messageCount?: number | null;
4708
+ /** Format: int64 */
4709
+ consumerCount?: number | null;
4710
+ /** Format: double */
4711
+ inRate?: number | null;
4712
+ /** Format: double */
4713
+ outRate?: number | null;
4714
+ stale: boolean;
4715
+ };
4677
4716
  FlowNodeView: {
4678
4717
  id?: string;
4679
4718
  /** @enum {string} */
@@ -4693,6 +4732,7 @@ export interface components {
4693
4732
  users?: string[];
4694
4733
  brokerNodes?: string[];
4695
4734
  faults?: ("NO_CONSUMER" | "STALLED" | "BRIDGE_DOWN" | "PARTIAL_PRESENCE")[];
4735
+ byNode?: components["schemas"]["FlowNodeShare"][] | null;
4696
4736
  };
4697
4737
  FlowTotals: {
4698
4738
  /** Format: int32 */
@@ -8481,6 +8521,7 @@ export interface operations {
8481
8521
  from?: string;
8482
8522
  to?: string;
8483
8523
  step?: string;
8524
+ splitBy?: string;
8484
8525
  };
8485
8526
  header?: never;
8486
8527
  path: {
@@ -8532,6 +8573,7 @@ export interface operations {
8532
8573
  limit?: number;
8533
8574
  groupBy?: "CLIENT_ID" | "USER" | "HOST";
8534
8575
  layers?: string;
8576
+ byNode?: boolean;
8535
8577
  };
8536
8578
  header?: never;
8537
8579
  path: {
@@ -45,13 +45,23 @@ export interface NavContribution {
45
45
  Badge?: ComponentType<{
46
46
  clusterId: string;
47
47
  }>;
48
+ /**
49
+ * The letter that, after `g`, goes to this view (ADR-0109). Unique among the built-in views; a
50
+ * plugin's is ignored, so a plugin can never take a letter an operator already relies on.
51
+ */
52
+ hotkey?: string;
48
53
  }
49
54
  /**
50
55
  * A feature's command-palette groups. It is rendered inside the palette, so it may use hooks, and calls
51
56
  * `report` whenever its groups change; `clusterId` is the cluster in view, if there is one.
57
+ *
58
+ * `query` is what the operator has typed (debounced) and `opened` whether the palette is open: a source
59
+ * that searches fetches only while it is open, never on every keystroke of a broker (ADR-0109).
52
60
  */
53
61
  export type PaletteSource = ComponentType<{
54
62
  clusterId?: string;
63
+ query: string;
64
+ opened: boolean;
55
65
  report: (groups: SpotlightActionGroupData[]) => void;
56
66
  }>;
57
67
  /** Handles one frame of a stream topic the feature owns (ADR-0070). */
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Every keyboard shortcut, in one place (ADR-0109), with the switch that turns the single-key ones
3
+ * off: a header button and the popover it opens, like the refresh control beside it. `?` opens the
4
+ * same popover; the button keeps it reachable when the single-key shortcuts are off.
5
+ */
6
+ export declare function ShortcutsHelp(): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { type RefObject } from 'react';
2
+ /** Registers a view's filter field for `/` while the view is mounted. */
3
+ export declare function useFilterShortcut(ref: RefObject<HTMLInputElement | null>): void;
4
+ /** Focuses the registered filter field; false when the view has none, so the key is left alone. */
5
+ export declare function focusFilter(): boolean;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The printable key an event stands for, in the Latin layout the shortcuts are named in. On a
3
+ * non-Latin layout (Persian, Russian, Greek) `event.key` is that script's letter, so the physical
4
+ * key decides instead: an operator does not switch layout to press `g q`.
5
+ */
6
+ export declare function latinKey(event: KeyboardEvent): string;
7
+ /**
8
+ * Whether a key press belongs to something else and must not be taken as a shortcut (ADR-0109):
9
+ * typing into a field or an editor, a modified key, an IME composition, a key already handled, and
10
+ * anything inside a dialog or a menu — a confirmation is never navigated away from by a stray key.
11
+ */
12
+ export declare function ignoredForShortcuts(event: KeyboardEvent): boolean;
@@ -0,0 +1,5 @@
1
+ export declare function singleKeyShortcutsEnabled(): boolean;
2
+ export declare function setSingleKeyShortcuts(next: boolean): void;
3
+ export declare function useSingleKeyShortcuts(): [boolean, (next: boolean) => void];
4
+ export declare function setShortcutsHelpOpen(next: boolean): void;
5
+ export declare function useShortcutsHelpOpen(): boolean;
@@ -0,0 +1,16 @@
1
+ import { type NavContribution } from '../feature.ts';
2
+ /**
3
+ * The views that have a `g` letter: built-in views only — a plugin's letter is ignored, so a plugin
4
+ * can never take a letter an operator already relies on (ADR-0109).
5
+ */
6
+ export declare function viewHotkeys(features: {
7
+ id: string;
8
+ nav?: NavContribution[];
9
+ }[]): Map<string, NavContribution>;
10
+ /**
11
+ * The single-key shortcuts (ADR-0109), as one document listener mounted by the shell:
12
+ * `g` then a view's letter goes to that view of the open cluster, `?` opens the list of shortcuts,
13
+ * and `/` focuses the view's filter when it has one. All of them are off when the operator turned
14
+ * single-key shortcuts off, and ignored while typing, with a modifier, and inside dialogs and menus.
15
+ */
16
+ export declare function useKeySequences(): void;
@@ -0,0 +1,21 @@
1
+ import type { NavContribution, StudioFeature } from '../feature.ts';
2
+ export interface CurrentView {
3
+ clusterId: string;
4
+ /** The navigation entry the address is under, when there is one. */
5
+ item?: NavContribution;
6
+ /** Its group's label. */
7
+ groupLabel?: string;
8
+ }
9
+ /**
10
+ * Which cluster view an address is under (ADR-0109): the navigation entry whose path is the longest
11
+ * prefix of the path after `/clusters/<id>/`, so a queue's message browser is under Queues and a
12
+ * bulk run under Bulk runs. Null outside any cluster.
13
+ */
14
+ export declare function matchView(pathname: string, features: StudioFeature[]): CurrentView | null;
15
+ export declare function useCurrentView(): CurrentView | null;
16
+ /**
17
+ * The address of the same view on another cluster (ADR-0109): an operator comparing two clusters
18
+ * stays on Queues. The view's search is not carried — its filter and its open resource name things
19
+ * that may not exist there. Without a current view, the cluster's landing page.
20
+ */
21
+ export declare function sameViewOn(clusterId: string, view: CurrentView | null): string;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Where the operator is inside a cluster (ADR-0109): cluster › group › view › open resource. The
3
+ * last crumb is the current page; the ones before it that are places are links back to them.
4
+ */
5
+ export declare function Breadcrumb(): import("react").JSX.Element | null;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * One cluster's screen: the header its features contribute, then the routed view.
3
3
  *
4
- * It mounts the cluster's one SSE stream, subscribed to the topics of every enabled feature
4
+ * It hosts the dialogs row actions open (ADR-0107), and mounts the cluster's one SSE stream, subscribed to the topics of every enabled feature
5
5
  * (ADR-0018, ADR-0070). A view never opens a second one for a topic a feature handles: a second
6
6
  * `EventSource` is a second connection, and it fights over the shared stream-status store. The
7
7
  * stream reconnects indefinitely and reports its state to the header's freshness indicator
@@ -1,7 +1,12 @@
1
1
  /**
2
- * ⌘K navigation across the console: jump to a view, or to whatever the features contribute — a
3
- * cluster, a queue by name. Mounted once in the root layout; the shortcut is registered by
4
- * {@link Spotlight}.
2
+ * ⌘K navigation across the console: jump to a view, a recent place, a cluster, or a queue by name, or
3
+ * search a live view (ADR-0109). Mounted once in the root layout; the shortcut is registered by
4
+ * {@link Spotlight}, and the header's Search button opens it too.
5
+ *
6
+ * The typed query reaches the features' sources, debounced, together with whether the palette is
7
+ * open, so a source searches only while someone is looking — and only what Studio already holds.
8
+ * A view the operator may not open is still listed, disabled, with the reason: the rail shows it the
9
+ * same way.
5
10
  *
6
11
  * Refresh and pause live here rather than on a hotkey: the browser owns both
7
12
  * shortcuts an operator would reach for (⌘R and ⇧⌘R), and taking either would be
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The parts of "where the operator is" that the shell cannot derive from the address itself
3
+ * (ADR-0109): the cluster's name, which only the clusters feature knows, and the open resource,
4
+ * which only its view knows. The shell combines them with the view it matches from the address into
5
+ * the document title and the breadcrumb.
6
+ */
7
+ export interface TitleParts {
8
+ cluster?: string;
9
+ resource?: string;
10
+ }
11
+ export declare function useTitleParts(): TitleParts;
12
+ /** Declares one part of the title while the calling component is mounted. */
13
+ export declare function useTitlePart(part: keyof TitleParts, value: string | null | undefined): void;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The views and resources an operator opened most recently on a cluster, for the palette's Recent
3
+ * group (ADR-0109). Browser-local and per viewer: a convenience that is fine to lose with site data,
4
+ * never state anything depends on.
5
+ */
6
+ export interface Recent {
7
+ label: string;
8
+ /** Where it is: the pathname, and the search it was opened with. */
9
+ to: string;
10
+ search: Record<string, unknown>;
11
+ /** What kind of place it is, shown as the entry's description. */
12
+ kind: string;
13
+ }
14
+ export declare function readRecents(clusterId: string): Recent[];
15
+ /** Records a visit, most recent first, one entry per label. */
16
+ export declare function recordRecent(clusterId: string, recent: Recent): void;
@@ -1,4 +1,6 @@
1
1
  import type { ComponentType } from 'react';
2
+ import type { ActionProps, ActionSection, AddressTarget, ClientTarget, ConnectionTarget, ConsumerTarget, DivertTarget, LinkProps, MessageTarget, ProducerTarget, QueueTarget, SessionTarget } from './actions/types.ts';
3
+ import type { MetricRange } from './time/ranges.ts';
2
4
  /** Queues picked by name, or every queue matching the queues screen's filter (`q`, blank for all). */
3
5
  export type QueueSelection = {
4
6
  kind: 'names';
@@ -74,6 +76,12 @@ export interface SlotProps {
74
76
  'metrics.panels': {
75
77
  clusterId: string;
76
78
  };
79
+ /** In the flow view's monitoring pane, when a queue is selected: its history over `range`, per node. */
80
+ 'flow.selection.panels': {
81
+ clusterId: string;
82
+ queueName: string;
83
+ range: MetricRange;
84
+ };
77
85
  /** Inside a box on the topology graph, after its name. `nodeIds` are the broker endpoints the box stands for. */
78
86
  'topology.node.marks': {
79
87
  clusterId: string;
@@ -91,6 +99,19 @@ export interface SlotProps {
91
99
  'admin.tabs': object;
92
100
  /** A section of the signed-in user's Account page, under the contribution's title. */
93
101
  'account.sections': object;
102
+ 'queue.actions': ActionProps<QueueTarget>;
103
+ 'address.actions': ActionProps<AddressTarget>;
104
+ 'connection.actions': ActionProps<ConnectionTarget>;
105
+ 'session.actions': ActionProps<SessionTarget>;
106
+ 'consumer.actions': ActionProps<ConsumerTarget>;
107
+ 'producer.actions': ActionProps<ProducerTarget>;
108
+ 'message.actions': ActionProps<MessageTarget>;
109
+ 'divert.actions': ActionProps<DivertTarget>;
110
+ 'client.actions': ActionProps<ClientTarget>;
111
+ 'queue.link': LinkProps<QueueTarget>;
112
+ 'address.link': LinkProps<AddressTarget>;
113
+ 'connection.link': LinkProps<ConnectionTarget>;
114
+ 'session.link': LinkProps<SessionTarget>;
94
115
  }
95
116
  export type SlotName = keyof SlotProps;
96
117
  /**
@@ -121,6 +142,8 @@ export interface SlotContribution<P> {
121
142
  title?: string;
122
143
  /** `settings.sections` only: the heading its tab sits under. Without one it is listed under Plugins. */
123
144
  group?: SettingsGroupId;
145
+ /** `*.actions` only: the menu section the item is listed under. */
146
+ section?: ActionSection;
124
147
  Component: ComponentType<P>;
125
148
  }
126
149
  export type SlotContributions = {
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Preset relative metric windows, shared by every view that charts history (metrics, and the
3
+ * Flow pane that shows its panels). Kept out of `router.tsx` so importing it (a
4
+ * component like `RangePicker` needs the runtime array, not just the type)
5
+ * never has to evaluate the whole route tree — that module calls
6
+ * `createRootRoute`/`createRoute` at import time, which only a real router
7
+ * context (or a full mock of it) can satisfy.
8
+ */
9
+ export declare const METRIC_RANGES: readonly ["15m", "1h", "6h", "24h", "7d"];
10
+ export type MetricRange = (typeof METRIC_RANGES)[number];
11
+ /**
12
+ * Each range's width and the bucket it asks the server for, in one place.
13
+ *
14
+ * The two travel together on purpose: the window advances in whole buckets
15
+ * (ADR-0055), the axis chooses its tick format from the range, and the poll
16
+ * cadence is derived from both. Splitting them across three modules is how they
17
+ * drift.
18
+ *
19
+ * `step` is a hint. The server clamps it to the fastest sampling tier and widens
20
+ * it to keep a response under its point cap, and reports what it actually used;
21
+ * nothing here assumes the hint was honoured.
22
+ */
23
+ export interface RangeSpec {
24
+ /** Window width. */
25
+ windowMs: number;
26
+ /** Requested bucket width, as the ISO-8601 duration the endpoint takes. */
27
+ step: string;
28
+ /** The same bucket width in ms — what the window quantizes to. */
29
+ stepMs: number;
30
+ }
31
+ export declare const RANGE_SPEC: Record<MetricRange, RangeSpec>;
32
+ export declare function rangeSpec(range: MetricRange): RangeSpec;
@@ -13,6 +13,10 @@ export { NAV_GROUPS, type NavGroupId } from '../kernel/nav/groups.ts';
13
13
  export { clusterRoute, rootRoute } from '../kernel/routing/roots.ts';
14
14
  export { ApiError, clusterKey, request } from '../kernel/api/request.ts';
15
15
  export { useCan } from '../kernel/auth/useCan.ts';
16
+ export { ACTION_SECTIONS, type ActionHost, type ActionMode, type ActionProps, type ActionSection, type ActionTargets, type AddressTarget, type ConnectionTarget, type ConsumerTarget, type DivertTarget, type HostedDialogProps, type MessageTarget, type ProducerTarget, type QueueTarget, type SessionTarget, } from '../kernel/actions/types.ts';
17
+ export { ActionMenuItem } from '../ui/ActionMenuItem.tsx';
18
+ export { CapabilityGate } from '../ui/CapabilityGate.tsx';
19
+ export { gateFor, type GateVerdict } from '../ui/capabilityGate.ts';
16
20
  export { useMe } from '../kernel/auth/api.ts';
17
21
  export { ConfirmByTyping } from '../ui/ConfirmByTyping.tsx';
18
22
  export { NodeOutcomeSummary, OutcomeSummary, type OutcomeRow } from '../ui/NodeOutcomeSummary.tsx';
@@ -0,0 +1,27 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { GateVerdict } from './capabilityGate.ts';
3
+ type Blocked = Extract<GateVerdict, {
4
+ kind: 'blocked';
5
+ }>;
6
+ /**
7
+ * One item of a row's action menu (ADR-0107).
8
+ *
9
+ * <p>An item the operator may not use is never removed and never `disabled`: Mantine skips disabled
10
+ * items in keyboard navigation, which would leave a keyboard user no way to learn why. It is
11
+ * `aria-disabled`, states the first sentence of its reason, and activating it hands the full reason
12
+ * (with its `broker.xml`) to `onExplain`.
13
+ *
14
+ * <p>A navigation item takes `href`, so it can be opened in a new tab; a plain activation still goes
15
+ * through `onSelect`, which navigates inside the app.
16
+ */
17
+ export declare function ActionMenuItem({ label, icon, verdict, tone, href, onSelect, onExplain, }: {
18
+ label: string;
19
+ icon?: ReactNode;
20
+ verdict?: GateVerdict;
21
+ /** `danger` for an action that destroys something; the word still carries it. */
22
+ tone?: 'danger';
23
+ href?: string;
24
+ onSelect: () => void;
25
+ onExplain?: (verdict: Blocked) => void;
26
+ }): import("react").JSX.Element;
27
+ export {};
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { MenuAnchor } from './menuAnchor.ts';
3
+ /**
4
+ * One controlled menu opened at a point rather than from a trigger it wraps (ADR-0107). A grid
5
+ * renders one of these for all of its rows: the items of a closed menu are not mounted, so a page
6
+ * of rows costs one menu, not one per row.
7
+ *
8
+ * <p>The anchor is a clipped point element, portalled so that no transformed ancestor (virtualized
9
+ * rows are placed by `transform`) can shift it. It carries `label`, which is what names the menu.
10
+ * Focus is not returned by the menu itself: only the caller knows what opened it, and whether a
11
+ * dialog has taken over.
12
+ */
13
+ export declare function AnchoredMenu({ opened, anchor, label, onClose, children, }: {
14
+ opened: boolean;
15
+ anchor: MenuAnchor | null;
16
+ /** The menu's accessible name, e.g. "Actions for orders". */
17
+ label: string;
18
+ onClose: () => void;
19
+ children: ReactNode;
20
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { GateVerdict } from './capabilityGate.ts';
3
+ /**
4
+ * Wraps a control that may be unavailable, keeping it visible and explaining
5
+ * itself in place (non-negotiable #5). A silently missing button teaches the
6
+ * operator that the product cannot do something, when the truth is that this
7
+ * connection is not configured for it.
8
+ *
9
+ * <p>The explanation is a popover on a real focusable button, not a `title` or a
10
+ * hover tooltip, because a disabled control takes no focus and a keyboard user
11
+ * would otherwise have no way to reach the reason at all.
12
+ *
13
+ * <p>`what` names the control being explained. A screen with several gated
14
+ * controls otherwise announces the same "Why this is unavailable" for each of
15
+ * them, which tells a screen-reader user nothing about which one it belongs to.
16
+ */
17
+ export declare function CapabilityGate({ verdict, what, children, }: {
18
+ verdict: GateVerdict;
19
+ /** "applying address setting orders.#"; defaults to "this". */
20
+ what?: string;
21
+ children: ReactNode;
22
+ }): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Why a control is unavailable, in full: the reason, and the `broker.xml` that enables it where one
3
+ * exists (non-negotiable #5). The one rendering of it, shared by the {@link CapabilityGate} popover
4
+ * and the explanation a blocked menu item opens, so both say the same thing the same way.
5
+ */
6
+ export declare function CapabilityReason({ reason, snippet, size, }: {
7
+ reason: string;
8
+ snippet?: string | null;
9
+ /** `xs` in a popover; `sm` where the explanation is the whole dialog. */
10
+ size?: 'xs' | 'sm';
11
+ }): import("react").JSX.Element;
@@ -14,11 +14,29 @@ export interface GridColumn<T> {
14
14
  */
15
15
  width?: number;
16
16
  }
17
+ /** What a row menu's items get: a way to close the menu, and to put focus back on the row. */
18
+ export interface RowMenuContext {
19
+ close: () => void;
20
+ /**
21
+ * Puts focus back on the row's Actions control, scrolling it into view if it has left; on the
22
+ * grid when the row is gone. For a dialog opened from the menu to call when it closes.
23
+ */
24
+ restoreFocus: () => void;
25
+ }
26
+ /** A per-row action menu (ADR-0107): its items are rendered only while it is open. */
27
+ export interface RowMenu<T> {
28
+ /** Names the row in "Actions for <label>". */
29
+ label: (row: T) => string;
30
+ render: (row: T, context: RowMenuContext) => React.ReactNode;
31
+ }
17
32
  interface VirtualTableProps<T> {
18
33
  columns: GridColumn<T>[];
19
34
  data: T[];
35
+ /** What the grid lists, as its accessible name: "Queues", "Connections". */
36
+ label?: string;
20
37
  sort?: string;
21
38
  onSortChange?: (sort: string | undefined) => void;
39
+ /** Activating a row, by click or by Enter on any of its cells. */
22
40
  onRowClick?: (row: T) => void;
23
41
  rowKey: (row: T) => string;
24
42
  emptyLabel?: React.ReactNode;
@@ -36,6 +54,10 @@ interface VirtualTableProps<T> {
36
54
  * the caller needs to know in order to hold new rows back.
37
55
  */
38
56
  onAtTopChange?: (atTop: boolean) => void;
57
+ /** A per-row action menu, opened by right-click, by the row's Actions control, or by Shift+F10. */
58
+ rowMenu?: RowMenu<T>;
59
+ /** Sized to its rows, up to a short cap, instead of to the viewport: a handful of rows in a pane. */
60
+ compact?: boolean;
39
61
  }
40
62
  /**
41
63
  * A virtualized data grid: one CSS grid track list, declared once and shared by
@@ -51,6 +73,13 @@ interface VirtualTableProps<T> {
51
73
  * <p>Sorting is a URL round-trip, not local state: the header carries
52
74
  * `aria-sort` from the current `sort` param and clicking it navigates. Row
53
75
  * selection is opt-in (`selectable`) and its state lives with the caller.
76
+ *
77
+ * <p>The keyboard model is the WAI-ARIA grid (ADR-0108): the grid is one tab
78
+ * stop, the arrow keys move a roving focus between cells (the header row
79
+ * included), Enter activates a row, Space selects it, Shift+F10 opens its menu,
80
+ * and Ctrl/Cmd+C copies a focused cell. The focused cell is remembered by row
81
+ * key, so a refresh or a re-sort does not move it to another row, and its row is
82
+ * always rendered however far it is scrolled.
54
83
  */
55
- export declare function VirtualTable<T>({ columns, data, sort, onSortChange, onRowClick, rowKey, emptyLabel, rowClassName, selectable, selected, onToggleRow, onToggleAll, onAtTopChange, }: VirtualTableProps<T>): import("react").JSX.Element;
84
+ export declare function VirtualTable<T>({ columns, data, label, sort, onSortChange, onRowClick, rowKey, emptyLabel, rowClassName, selectable, selected, onToggleRow, onToggleAll, onAtTopChange, rowMenu, compact, }: VirtualTableProps<T>): import("react").JSX.Element;
56
85
  export {};
@@ -0,0 +1,28 @@
1
+ import type { components } from '../kernel/api/schema.d.ts';
2
+ type CapabilityView = components['schemas']['CapabilityView'];
3
+ export type GateVerdict = {
4
+ kind: 'allowed';
5
+ uncertain: boolean;
6
+ } | {
7
+ kind: 'blocked';
8
+ reason: string;
9
+ snippet?: string | null;
10
+ };
11
+ /**
12
+ * Whether a lifecycle control may act, and why not when it may not.
13
+ *
14
+ * <p>Two independent gates, in the order the operator can do something about:
15
+ *
16
+ * <ul>
17
+ * <li><b>Permission</b> — the caller's own grants. The server is the
18
+ * enforcement point; this only explains, and never hides.
19
+ * <li><b>Capability</b> — whether the broker connection can write at all. Only
20
+ * a known refusal blocks. Since ADR-0049 D5 an unproven capability is
21
+ * reported as unknown, and blocking on the absence of evidence would stop
22
+ * an operator using a broker that works perfectly well.
23
+ * </ul>
24
+ */
25
+ export declare function gateFor(permitted: boolean, permissionLabel: string, capability: CapabilityView | undefined,
26
+ /** True while the caller's grants are still being fetched. */
27
+ loading?: boolean): GateVerdict;
28
+ export {};
@@ -0,0 +1,12 @@
1
+ /** A viewport point the menu opens from: the pointer, or just under the control that opened it. */
2
+ export interface MenuAnchor {
3
+ x: number;
4
+ y: number;
5
+ }
6
+ /** The anchor just under an element, at its inline start, for a menu opened from the keyboard. */
7
+ export declare function anchorBelow(element: Element): MenuAnchor;
8
+ /**
9
+ * Keeps an anchor inside the viewport. A point outside it counts as a hidden reference, and the
10
+ * positioning engine then hides the menu outright rather than showing it at the edge.
11
+ */
12
+ export declare function clampToViewport(anchor: MenuAnchor): MenuAnchor;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The keyboard model of a data grid (ADR-0108): one tab stop, and the WAI-ARIA APG grid keys to
3
+ * move between cells. Pure, so the moves are tested apart from any rendering.
4
+ *
5
+ * <p>Row 0 is the header row; body rows are 1…`rows`. Columns are 0…`cols - 1` in visual order.
6
+ */
7
+ export interface GridPos {
8
+ row: number;
9
+ col: number;
10
+ }
11
+ export interface GridShape {
12
+ /** Body rows; the header row is added on top as row 0. */
13
+ rows: number;
14
+ cols: number;
15
+ /** Rows a Page Up / Page Down moves by. */
16
+ page: number;
17
+ /** In a right-to-left page, ArrowLeft moves toward the end of the row. */
18
+ rtl?: boolean;
19
+ }
20
+ export interface GridKey {
21
+ key: string;
22
+ ctrlKey?: boolean;
23
+ metaKey?: boolean;
24
+ }
25
+ /**
26
+ * Where a key moves focus from `pos`, or null when the key is not a movement key. A move past an
27
+ * edge stays at the edge rather than wrapping: a grid is not a ring, and wrapping from the last
28
+ * row to the header would read as the view having jumped.
29
+ */
30
+ export declare function nextCell(pos: GridPos, input: GridKey, shape: GridShape): GridPos | null;
31
+ /**
32
+ * The row index a remembered row key resolves to after the data changed: its new index when it is
33
+ * still there, otherwise the nearest surviving neighbour of where it was, so that removing the
34
+ * focused row hands focus to the row that took its place.
35
+ */
36
+ export declare function resolveRow(keys: readonly string[], key: string | null, lastIndex: number): number;