dsh-quick-actions 0.1.0-rc.3

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +199 -0
  3. package/README.md +199 -0
  4. package/cordis.patch.yml +29 -0
  5. package/lib/client.js +3901 -0
  6. package/lib/client.js.map +1 -0
  7. package/lib/index.js +737 -0
  8. package/lib/types/client/controller.d.ts +207 -0
  9. package/lib/types/client/dsh.d.ts +171 -0
  10. package/lib/types/client/index.d.ts +43 -0
  11. package/lib/types/client/manager/ActionForm.d.ts +20 -0
  12. package/lib/types/client/manager/ActionPanel.d.ts +15 -0
  13. package/lib/types/client/manager/ManagedRow.d.ts +40 -0
  14. package/lib/types/client/manager/ManagerPanel.d.ts +9 -0
  15. package/lib/types/client/manager/press.d.ts +38 -0
  16. package/lib/types/client/manager/search.d.ts +56 -0
  17. package/lib/types/client/manager/status.d.ts +23 -0
  18. package/lib/types/client/modal.d.ts +55 -0
  19. package/lib/types/client/session/ConfirmPanel.d.ts +23 -0
  20. package/lib/types/client/session/availability.d.ts +19 -0
  21. package/lib/types/client/session/execution.d.ts +86 -0
  22. package/lib/types/client/session/guards.d.ts +59 -0
  23. package/lib/types/client/surfaces/ActionFace.d.ts +21 -0
  24. package/lib/types/client/surfaces/ErrorBoundary.d.ts +31 -0
  25. package/lib/types/client/surfaces/QuickActionsSurface.d.ts +17 -0
  26. package/lib/types/client/surfaces/entries.d.ts +27 -0
  27. package/lib/types/client/surfaces/layout.d.ts +49 -0
  28. package/lib/types/client/surfaces/residency.d.ts +65 -0
  29. package/lib/types/host/config.d.ts +18 -0
  30. package/lib/types/host/index.d.ts +38 -0
  31. package/lib/types/host/presets.d.ts +26 -0
  32. package/lib/types/host/settings.d.ts +113 -0
  33. package/lib/types/index.d.ts +35 -0
  34. package/lib/types/locales/index.d.ts +34 -0
  35. package/lib/types/model/catalog.d.ts +63 -0
  36. package/lib/types/model/index.d.ts +13 -0
  37. package/lib/types/model/json.d.ts +11 -0
  38. package/lib/types/model/mutations.d.ts +98 -0
  39. package/lib/types/model/normalize.d.ts +15 -0
  40. package/lib/types/model/projection.d.ts +49 -0
  41. package/lib/types/model/settings.d.ts +48 -0
  42. package/lib/types/model/text.d.ts +28 -0
  43. package/lib/types/model/types.d.ts +89 -0
  44. package/lib/types/model/validation.d.ts +40 -0
  45. package/lib/types/styles/index.d.ts +39 -0
  46. package/lib/types/types.d.ts +11 -0
  47. package/lib/types.js +1 -0
  48. package/package.json +83 -0
@@ -0,0 +1,207 @@
1
+ import type { CustomActionId, PresetActionId, PresetCatalog, QuickActionDraft, QuickActionLayout, QuickActionMutationRejection, QuickActionProjection, QuickActionRef, QuickActionSettingsV1 } from '../model/index.js';
2
+ /**
3
+ * One path-addressed edit inside a namespace section. The transport also accepts
4
+ * an `unset` form; this plugin writes the whole section field by field and never
5
+ * clears one, so only the form it submits is declared.
6
+ */
7
+ export interface SettingsSetOp {
8
+ readonly op: 'set';
9
+ readonly path: readonly string[];
10
+ readonly value: unknown;
11
+ }
12
+ /** One bound namespace as the scope publishes it. */
13
+ export interface SettingsScopeSnapshot {
14
+ /** `loading` until the shared document answers; `unavailable` when the namespace is not served. */
15
+ readonly status: 'loading' | 'ready' | 'unavailable';
16
+ /** Resolved section: schema defaults, then composition base, then the user layer. */
17
+ readonly value?: unknown;
18
+ /** Composition base layer, where an author-owned layer such as the catalog rides. */
19
+ readonly base?: unknown;
20
+ /** Revision of the raw user section, sent back to fence a write. */
21
+ readonly revision?: number;
22
+ /** Whether the provider accepts writes at all. */
23
+ readonly writable: boolean;
24
+ /** `memory` on a page this Client keeps process-local; no write ever crosses the wire. */
25
+ readonly mode: 'host' | 'memory';
26
+ }
27
+ /** The namespace contract handed to `bind`. */
28
+ export interface SettingsScopeSpec {
29
+ readonly namespace: string;
30
+ /** Narrows the resolved value; returning `undefined` publishes no value at all. */
31
+ readonly decode?: (value: unknown) => unknown;
32
+ }
33
+ /** One namespace scope: a derived read plus that namespace's serialized writes. */
34
+ export interface BoundSettingsScope {
35
+ getSnapshot(): SettingsScopeSnapshot;
36
+ subscribe(listener: () => void): () => void;
37
+ mutate(ops: readonly SettingsSetOp[], expectedRevision?: number): Promise<void>;
38
+ }
39
+ /** The shared settings document as the mirror holds it. */
40
+ export interface SettingsMirrorSnapshot {
41
+ /** Whether any document answer is held at all; absent while no read has succeeded. */
42
+ readonly view?: unknown;
43
+ /** The last read's failure message, or `null` when it answered. */
44
+ readonly error: string | null;
45
+ }
46
+ /**
47
+ * The shared describe mirror's read face. A bound scope publishes nothing at all
48
+ * while the document is unanswered, so a read that *failed* is only visible here
49
+ * — which is what tells a first catalog read failure apart from one still in
50
+ * flight (spec 10).
51
+ */
52
+ export interface SettingsMirror {
53
+ getSnapshot(): SettingsMirrorSnapshot;
54
+ subscribe(listener: () => void): () => void;
55
+ load(): Promise<void>;
56
+ }
57
+ /** `ctx.settingsScope`. */
58
+ export interface SettingsScopeService {
59
+ bind(spec: SettingsScopeSpec): BoundSettingsScope;
60
+ describe(): SettingsMirror;
61
+ }
62
+ /** Connection lifecycle as `ctx.connection` publishes it; `undefined` before the loop starts. */
63
+ export type ConnectionState = 'connected' | 'connecting' | 'disconnected' | undefined;
64
+ /** `ctx.connection`, narrowed to the observable state this controller reads. */
65
+ export interface ConnectionLike {
66
+ readonly state: {
67
+ getSnapshot(): ConnectionState;
68
+ subscribe(listener: () => void): () => void;
69
+ };
70
+ }
71
+ /** Why the authoritative catalog cannot be shown (spec 10). */
72
+ export type CatalogErrorReason =
73
+ /** The settings document could not be read at all; retrying is the remedy. */
74
+ 'unreadable'
75
+ /** The Host serves no catalog namespace, or published no `base` layer on it. */
76
+ | 'unavailable'
77
+ /** A `base` layer this release cannot read in full; never a truncated catalog (spec 5.1). */
78
+ | 'undecodable';
79
+ /** The authoritative Preset Catalog as the Client currently knows it. */
80
+ export type CatalogState = {
81
+ readonly status: 'loading';
82
+ } | {
83
+ readonly status: 'ready';
84
+ readonly catalog: PresetCatalog;
85
+ } | {
86
+ readonly status: 'error';
87
+ readonly reason: CatalogErrorReason;
88
+ };
89
+ /** The last Host-confirmed user state, and the fence a write against it must carry. */
90
+ export type SettingsState = {
91
+ readonly status: 'loading';
92
+ }
93
+ /** No namespace to read or write: unserved, or a page kept process-local. */
94
+ | {
95
+ readonly status: 'unavailable';
96
+ } | {
97
+ readonly status: 'ready';
98
+ readonly settings: QuickActionSettingsV1;
99
+ readonly revision: number;
100
+ readonly writable: boolean;
101
+ };
102
+ /** Why a write did not reach the stored state. */
103
+ export type QuickActionWriteFailure =
104
+ /** No catalog or no readable namespace yet, or the controller is disposed. */
105
+ {
106
+ readonly kind: 'not-ready';
107
+ }
108
+ /** The provider accepts no writes, or the connection is not current (spec 10). */
109
+ | {
110
+ readonly kind: 'read-only';
111
+ }
112
+ /** The shared model refused the plan; nothing crossed the wire. */
113
+ | {
114
+ readonly kind: 'rejected';
115
+ readonly rejection: QuickActionMutationRejection;
116
+ }
117
+ /** The Host refused the write itself; the authoritative snapshot was re-read. */
118
+ | {
119
+ readonly kind: 'refused';
120
+ }
121
+ /** The revision fence was lost: refresh, then ask the user to confirm again (spec 10). */
122
+ | {
123
+ readonly kind: 'conflict';
124
+ }
125
+ /** The write never settled against the Host. Never retried automatically. */
126
+ | {
127
+ readonly kind: 'failed';
128
+ readonly message: string;
129
+ };
130
+ /**
131
+ * The structured result every mutation answers with (spec 15, decision 4).
132
+ *
133
+ * The shipped `SettingsScope.mutate` resolves to `void`, and this release adds no
134
+ * DSH core interface (spec 16.4), so success, refusal and conflict are told apart
135
+ * here, from the authoritative snapshot the scope publishes after the write: the
136
+ * write committed when the stored state now reads back as the plan, the fence was
137
+ * lost when the namespace revision moved elsewhere, and anything else is a Host
138
+ * refusal. Each failing case leaves the caller's form content untouched — the
139
+ * caller owns the draft — and the recovery read has already happened by the time
140
+ * this resolves.
141
+ */
142
+ export type QuickActionWriteOutcome = {
143
+ readonly ok: true;
144
+ readonly changed: boolean;
145
+ } | {
146
+ readonly ok: false;
147
+ readonly failure: QuickActionWriteFailure;
148
+ };
149
+ /** The global management panel's own state (spec 7.1). */
150
+ export interface QuickActionManagerState {
151
+ readonly open: boolean;
152
+ }
153
+ /** Everything the Quick Action surfaces render from. */
154
+ export interface QuickActionsClientState {
155
+ readonly catalog: CatalogState;
156
+ readonly settings: SettingsState;
157
+ /**
158
+ * What the surfaces list, or `undefined` while no catalog is readable. With a
159
+ * catalog but no readable user state it projects the defaults, so the
160
+ * authoritative presets still render while management stays read-only (spec 10).
161
+ */
162
+ readonly projection: QuickActionProjection | undefined;
163
+ /** Whether any write may be attempted right now. */
164
+ readonly readOnly: boolean;
165
+ /** The connection is not current, so the held snapshot may have moved on (spec 10). */
166
+ readonly stale: boolean;
167
+ readonly manager: QuickActionManagerState;
168
+ /** A write is in flight; the panel disables its controls rather than queueing clicks. */
169
+ readonly writing: boolean;
170
+ /** The last failure, until it is dismissed or superseded by the next write. */
171
+ readonly failure?: QuickActionWriteFailure;
172
+ }
173
+ /** The controller as the surfaces consume it. */
174
+ export interface QuickActionsController {
175
+ /** Current state; the same reference until something actually changes. */
176
+ getSnapshot(): QuickActionsClientState;
177
+ subscribe(listener: () => void): () => void;
178
+ /** Re-read the shared settings document — the retry behind a catalog error (spec 10). */
179
+ refresh(): Promise<void>;
180
+ openManager(): void;
181
+ closeManager(): void;
182
+ dismissFailure(): void;
183
+ createCustomAction(draft: QuickActionDraft): Promise<QuickActionWriteOutcome>;
184
+ clonePreset(presetId: PresetActionId): Promise<QuickActionWriteOutcome>;
185
+ updateCustomAction(id: CustomActionId, draft: QuickActionDraft): Promise<QuickActionWriteOutcome>;
186
+ setCustomActionEnabled(id: CustomActionId, enabled: boolean): Promise<QuickActionWriteOutcome>;
187
+ deleteCustomAction(id: CustomActionId): Promise<QuickActionWriteOutcome>;
188
+ setPresetHidden(presetId: PresetActionId, hidden: boolean): Promise<QuickActionWriteOutcome>;
189
+ reorderActions(order: readonly QuickActionRef[]): Promise<QuickActionWriteOutcome>;
190
+ moveAction(ref: QuickActionRef, toIndex: number): Promise<QuickActionWriteOutcome>;
191
+ setLayout(layout: QuickActionLayout): Promise<QuickActionWriteOutcome>;
192
+ /** Release the scope subscriptions and the connection listener. */
193
+ dispose(): void;
194
+ }
195
+ /** What the controller is built over. */
196
+ export interface QuickActionsControllerOptions {
197
+ readonly settingsScope: SettingsScopeService;
198
+ readonly connection: ConnectionLike;
199
+ /** Mints one Custom Action ID. Defaults to `crypto.randomUUID()`. */
200
+ readonly mintCustomActionId?: () => string;
201
+ }
202
+ /**
203
+ * Create the controller. Both bindings are made on the calling fiber, so the
204
+ * scope disposers the binder registers are withdrawn with it; `dispose()`
205
+ * releases what this module owns on top of that.
206
+ */
207
+ export declare function createQuickActionsController(options: QuickActionsControllerOptions): QuickActionsController;
@@ -0,0 +1,171 @@
1
+ /**
2
+ * The DSH Client faces the Composer surfaces and the per-session execution layer
3
+ * consume, declared structurally and no wider than this plugin actually reads.
4
+ *
5
+ * Every shape here is transcribed from a published declaration of the target
6
+ * DSH release (0.1.5-rc.1) rather than guessed:
7
+ *
8
+ * - `InputState`, `InputActions`, `Occurrence`
9
+ * → `@deepseek-ai/dsh-client-ui-conversation/lib/types/client/contract/input.d.ts`
10
+ * - `SessionSnapshot`
11
+ * → `@deepseek-ai/dsh-api-session-controller/lib/types/client/contract/snapshot.d.ts`
12
+ * - the two dock Slot contracts and their standard props
13
+ * → `@deepseek-ai/dsh-client-ui-conversation/lib/types/client/contract/slots.d.ts`
14
+ * and the generated Slot ledger in `@deepseek-ai/dsh-cordis-client-runner`
15
+ * - `ComposerBlock` / `ComposerBlocks`
16
+ * → `@deepseek-ai/dsh-client-ui-conversation/lib/types/client/contract/composer-blocks.d.ts`
17
+ * - `slots.inject` / `slots.register` and `locale.register` / `locale.bind`
18
+ * → the generated Service ledger in `@deepseek-ai/dsh-cordis-client-runner`
19
+ *
20
+ * They are declared here instead of imported because the plugin ships against a
21
+ * DSH it does not depend on at build time: the Client bundle may only `require`
22
+ * specifiers the build adapter lists as externals (`react` and
23
+ * `react/jsx-runtime`), and every DSH face arrives as a Slot prop or a Cordis
24
+ * service on `ctx`. Declaring them narrowly also keeps the compiler honest about
25
+ * spec 9.1: nothing outside these shapes is reachable, so no DOM, Lexical,
26
+ * private shell, private event or private keyboard path can be typed into
27
+ * existence by accident.
28
+ */
29
+ import type { ComponentType, ReactNode } from 'react';
30
+ export type { ComponentType };
31
+ /** Browser-runtime identity of one unsent attachment draft. */
32
+ export type DraftAttachmentId = string;
33
+ /**
34
+ * One reference occurrence projected from the editor's chip nodes. Only the
35
+ * fields this plugin reads are declared; an occurrence's presence is what
36
+ * matters here, never its content.
37
+ */
38
+ export interface InputOccurrence {
39
+ readonly occurrenceId: number;
40
+ readonly source: string;
41
+ readonly ref: string;
42
+ }
43
+ /** One row of the read-only transient queue projection. */
44
+ export interface InputQueueRow {
45
+ readonly placement: 'queued' | 'steering' | 'context';
46
+ }
47
+ /**
48
+ * The published per-Session input state — the whole public snapshot, listed
49
+ * field by field because spec 9.5 makes this exact set the only evidence the
50
+ * send single-flight may be judged from.
51
+ */
52
+ export interface InputState {
53
+ /** Clipboard-text projection of the editor document (chips expanded). */
54
+ readonly draft: string;
55
+ /** Ordered runtime-only attachment ids. */
56
+ readonly attachmentIds: readonly DraftAttachmentId[];
57
+ /** Monotonic editor revision; bumps once per content-changing editor commit. */
58
+ readonly draftRev: number;
59
+ /** Submit-plane phase; `plain` is the only phase that accepts a new submission. */
60
+ readonly phase: 'plain' | 'adjudicating' | 'claimed' | 'submitting';
61
+ /** Present exactly while claimed/submitting. */
62
+ readonly claim?: {
63
+ readonly token: string;
64
+ readonly hint?: string;
65
+ readonly attachments?: boolean;
66
+ };
67
+ /** Reference chips currently in the draft, sorted by offset. */
68
+ readonly occurrences: readonly InputOccurrence[];
69
+ /** Read-only transient inbox projection from Session control. */
70
+ readonly queue: readonly InputQueueRow[];
71
+ }
72
+ /**
73
+ * The public input action face handed to every session-scope Slot component.
74
+ *
75
+ * Only the two members spec 9.4 allows are declared. The shipped object also
76
+ * carries `addAttachments`, `removeAttachment` and `pruneAttachments`; leaving
77
+ * them out is deliberate — this plugin owns no draft attachment and must never
78
+ * touch one.
79
+ */
80
+ export interface InputActions {
81
+ /** Replace the whole draft. */
82
+ setDraft(text: string): void;
83
+ /** Enter submission: the same official path the composer's send button takes. */
84
+ submit(): void;
85
+ }
86
+ /** The Session lifecycle fields the composer's own send guard reads. */
87
+ export interface SessionSnapshot {
88
+ readonly sessionId: string;
89
+ readonly removed: boolean;
90
+ readonly running: boolean;
91
+ readonly subagent: {
92
+ readonly address: {
93
+ readonly mode: string;
94
+ };
95
+ readonly parentAvailable?: boolean;
96
+ } | null;
97
+ }
98
+ /** Why one session's composer is inert (a feature-owned block). */
99
+ export interface ComposerBlock {
100
+ readonly reason: string;
101
+ }
102
+ /** An observable value as the DSH stores publish it. */
103
+ export interface ObservableSnapshot<T> {
104
+ getSnapshot(): T;
105
+ subscribe(listener: () => void): () => void;
106
+ }
107
+ /**
108
+ * `ctx.conversation.blocks` — documented as "the registry face other plugins
109
+ * reach": how a plugin the composer cannot import makes a session's input
110
+ * inert. Reading it is the only public way to honour the `blocked` guard of
111
+ * spec 9.2. The Client reaches it through `ctx.get`, the documented read
112
+ * "without the inject requirement", so the declared dependency list stays
113
+ * exactly the four of spec 7.3, and degrades to "not blocked" when absent.
114
+ */
115
+ export interface ComposerBlocks {
116
+ storeFor(sessionId: string): ObservableSnapshot<ComposerBlock | undefined>;
117
+ }
118
+ /** The narrow slice of `ctx.conversation` this plugin reads. */
119
+ export interface ConversationLike {
120
+ readonly blocks: ComposerBlocks;
121
+ }
122
+ /** `SnapshotSelectorHook<T>`: a `useSyncExternalStoreWithSelector` binding. */
123
+ export type SnapshotSelectorHook<T> = <S>(selector: (value: T) => S, equality?: (a: S, b: S) => boolean) => S;
124
+ /** Point-in-time owner values of `conversation.input.dock`. */
125
+ export interface InputZoneOwnerProps {
126
+ readonly session: SessionSnapshot;
127
+ readonly input: InputState;
128
+ }
129
+ /** Translation function bound to one locale namespace. */
130
+ export type Translate = (key: string, params?: Record<string, string | number>) => string;
131
+ /** The standard props both dock Slots hand every session-scoped entry. */
132
+ export interface SessionSlotProps {
133
+ readonly sessionId: string;
134
+ readonly useSession: SnapshotSelectorHook<SessionSnapshot>;
135
+ readonly useInput: SnapshotSelectorHook<InputState>;
136
+ readonly inputActions: InputActions;
137
+ readonly t: Translate;
138
+ }
139
+ /** Props of the `conversation.input.dock` entry (standard props plus the zone). */
140
+ export type InputDockProps = SessionSlotProps & InputZoneOwnerProps;
141
+ /** Props of the `conversation.composer.dock` entry (this Slot declares no owner props). */
142
+ export type ComposerDockProps = SessionSlotProps;
143
+ /** Registration options both dock Slots accept. */
144
+ export interface SlotRegisterOptions {
145
+ readonly name: 'conversation.input.dock' | 'conversation.composer.dock';
146
+ /** Cell key; a fresh id is added beside the shipped entries. */
147
+ readonly id: string;
148
+ /** Position among the entries, ascending. */
149
+ readonly order?: number;
150
+ /** Locale namespace backing the entry's `t` prop. */
151
+ readonly locale?: string;
152
+ }
153
+ /** One synchronous effect installed while an injected Slot declaration is live. */
154
+ export type SlotInjectionEffect = (() => void) | Iterable<() => void>;
155
+ /** `ctx.slots`, narrowed to the two calls this plugin makes. */
156
+ export interface SlotsService {
157
+ /**
158
+ * Install an effect for each declaration lifetime of a Slot. The controller
159
+ * belongs to the caller's fiber, so plugin unload removes the contribution.
160
+ */
161
+ inject(key: string, callback: () => SlotInjectionEffect): () => void;
162
+ /** Register one entry; disposal runs through the caller's `ctx.effect`. */
163
+ register(options: SlotRegisterOptions, component: ComponentType<never>): () => void;
164
+ }
165
+ /** `ctx.locale`, narrowed to dictionary registration and namespace binding. */
166
+ export interface LocaleService {
167
+ register(ns: string, dictionaries: Record<string, Record<string, string>>): () => void;
168
+ bind(ns: string): Translate;
169
+ }
170
+ /** A React element, kept as a type alias so surfaces need no React value import. */
171
+ export type SurfaceNode = ReactNode;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Client composition entry for Composer Quick Actions.
3
+ *
4
+ * This fiber owns everything the feature holds in the browser: one global
5
+ * `QuickActionsController` over the two Settings namespaces, the per-Session
6
+ * execution registry, the Resident Composer registry, the locale dictionaries,
7
+ * the stylesheet, and the two dock Slot registrations. All of it is installed
8
+ * through `ctx.effect` and `ctx.slots.inject`, so unloading the plugin leaves
9
+ * no listener, registration, namespace, style tag or subscription behind
10
+ * (spec 7.3).
11
+ *
12
+ * `settingsScope` is how this Client reaches Host-authoritative state at all — it
13
+ * never writes a file, never treats browser storage as a source of truth, and
14
+ * never reaches past Settings to a provider (spec 6.3). `connection` supplies the
15
+ * generation state the read-only-while-disconnected rule needs (spec 10).
16
+ */
17
+ import type { Context } from '@deepseek-ai/cordis';
18
+ import type { ConnectionLike, SettingsScopeService } from './controller.js';
19
+ import type { ConversationLike, LocaleService, SlotsService } from './dsh.js';
20
+ declare module '@deepseek-ai/cordis' {
21
+ interface Context {
22
+ settingsScope: SettingsScopeService;
23
+ connection: ConnectionLike;
24
+ slots: SlotsService;
25
+ locale: LocaleService;
26
+ conversation: ConversationLike;
27
+ }
28
+ }
29
+ export declare const name = "composer-quick-actions";
30
+ /**
31
+ * The services this Client requires (spec 7.3). `conversation` is deliberately
32
+ * absent: its Composer-block registry is read through `ctx.get`, the documented
33
+ * read "without the inject requirement", because the service is guaranteed
34
+ * present wherever the two dock Slots it declares are rendered.
35
+ */
36
+ export declare const inject: readonly string[];
37
+ /**
38
+ * Start the feature and tie every piece of it to this fiber.
39
+ *
40
+ * Nothing is returned and no internal type is re-exported: the controller, the
41
+ * execution engine and the surfaces are internal to this package (spec 14).
42
+ */
43
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,20 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { ManagerWriteGate } from './press.js';
3
+ import type { QuickActionDraft } from '../../model/index.js';
4
+ import type { Translate } from '../dsh.js';
5
+ export interface ActionFormProps {
6
+ /** `new` for a creation, `edit` for an existing Custom Quick Action. */
7
+ readonly mode: 'new' | 'edit';
8
+ readonly draft: QuickActionDraft;
9
+ /**
10
+ * Whether the user has already tried to save. Blank fields only report
11
+ * themselves after that, so a freshly opened form does not open shouting.
12
+ */
13
+ readonly attempted: boolean;
14
+ readonly gate: ManagerWriteGate;
15
+ readonly t: Translate;
16
+ readonly onChange: (draft: QuickActionDraft) => void;
17
+ readonly onSave: () => void;
18
+ readonly onCancel: () => void;
19
+ }
20
+ export declare function ActionForm(props: ActionFormProps): ReactElement;
@@ -0,0 +1,15 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { QuickActionSessionState } from '../session/execution.js';
3
+ import type { ProjectedQuickAction } from '../../model/index.js';
4
+ import type { Translate } from '../dsh.js';
5
+ export interface ActionPanelProps {
6
+ /** The actions this entry offers, already in the shared order. */
7
+ readonly actions: readonly ProjectedQuickAction[];
8
+ readonly session: QuickActionSessionState;
9
+ readonly t: Translate;
10
+ /** Id of the element naming this dialog. */
11
+ readonly labelledBy: string;
12
+ readonly onActivate: (action: ProjectedQuickAction) => void;
13
+ readonly onClose: () => void;
14
+ }
15
+ export declare function ActionPanel(props: ActionPanelProps): ReactElement;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * One row of the management list: what the action is, and everything the user
3
+ * may do to it (spec 3, 5.1, 5.2, 8.3).
4
+ *
5
+ * The two sources differ in exactly the way spec 5.1 says they must: a Preset
6
+ * Quick Action may be reordered, hidden, restored and cloned — never edited or
7
+ * deleted, because its definition is the author's — while a Custom Quick Action
8
+ * has its whole life cycle here.
9
+ *
10
+ * A hidden or disabled action is rendered, dimmed and labelled, never removed:
11
+ * removal from the Composer is the Hidden Quick Action projection, and this
12
+ * panel is where it stays recoverable (spec 3).
13
+ */
14
+ import type { ReactElement } from 'react';
15
+ import type { ManagerWriteGate } from './press.js';
16
+ import type { ProjectedQuickAction, QuickActionRef } from '../../model/index.js';
17
+ import type { Translate } from '../dsh.js';
18
+ /** Everything one row can ask the panel to do. */
19
+ export interface ManagedRowHandlers {
20
+ readonly onMove: (ref: QuickActionRef, toIndex: number) => void;
21
+ readonly onEdit: () => void;
22
+ readonly onClone: () => void;
23
+ readonly onToggleHidden: () => void;
24
+ readonly onToggleEnabled: () => void;
25
+ readonly onAskDelete: () => void;
26
+ readonly onCancelDelete: () => void;
27
+ readonly onConfirmDelete: () => void;
28
+ }
29
+ export interface ManagedRowProps {
30
+ readonly action: ProjectedQuickAction;
31
+ /** Position in the shared order, which is what the move controls address. */
32
+ readonly index: number;
33
+ readonly total: number;
34
+ readonly gate: ManagerWriteGate;
35
+ /** Whether this row has already been asked about deleting. */
36
+ readonly deleting: boolean;
37
+ readonly t: Translate;
38
+ readonly on: ManagedRowHandlers;
39
+ }
40
+ export declare function ManagedRow({ action, index, total, gate, deleting, t, on }: ManagedRowProps): ReactElement;
@@ -0,0 +1,9 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { QuickActionsClientState, QuickActionsController } from '../controller.js';
3
+ import type { Translate } from '../dsh.js';
4
+ export interface ManagerPanelProps {
5
+ readonly client: QuickActionsClientState;
6
+ readonly controller: QuickActionsController;
7
+ readonly t: Translate;
8
+ }
9
+ export declare function ManagerPanel({ client, controller, t }: ManagerPanelProps): ReactElement;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * When a mutating control in the management panel may be pressed, and how it
3
+ * says so (spec 5.4, 8.4, 10).
4
+ *
5
+ * ## Why two ways of saying "no"
6
+ *
7
+ * Only sustained, externally-imposed unavailability — a read-only namespace, or a
8
+ * connection that can no longer vouch for the held snapshot — uses a real
9
+ * `disabled`, which takes the control out of the tab order entirely.
10
+ *
11
+ * Everything else uses `aria-disabled` plus a guarded handler, because
12
+ * everything else can become true *as a result of the press itself*: the write
13
+ * this control just started, the row that just reached an end of the list, the
14
+ * layout it just selected becoming current, the clone that just filled the last
15
+ * slot. A real `disabled` there would drop the keyboard caret to the document
16
+ * body mid-reorder — and focus handling is a hard gate of spec 8.4, not a nicety.
17
+ */
18
+ /** What every mutating control in the panel is gated on. */
19
+ export interface ManagerWriteGate {
20
+ /** Sustained and external: the panel is genuinely inert and must not be tabbable. */
21
+ readonly readOnly: boolean;
22
+ /** A write is in flight; transient, and usually started by the focused control. */
23
+ readonly busy: boolean;
24
+ /** Whether creating or cloning is allowed at all right now (spec 5.4). */
25
+ readonly canAdd: boolean;
26
+ }
27
+ /** The props one mutating control spreads onto its `<button>`. */
28
+ export interface PressProps {
29
+ readonly disabled: boolean;
30
+ readonly 'aria-disabled': boolean;
31
+ readonly onClick: () => void;
32
+ }
33
+ /**
34
+ * Gate one control. `blocked` is its own extra condition — an end of the list,
35
+ * the ceiling, the choice already made; the gate's own `busy` is folded in here
36
+ * so no caller has to remember it.
37
+ */
38
+ export declare function pressProps(gate: ManagerWriteGate, blocked: boolean, onPress: () => void): PressProps;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The shared action panel's search rule (spec 8.1).
3
+ *
4
+ * Spec 8.1 requires the search to *actually* filter — the prototype's
5
+ * "shows everything" placeholder is explicitly forbidden — to keep matches in
6
+ * their `actionOrder` relative order, and to have one stated policy for which
7
+ * fields are searched, how case is handled and how Unicode is normalized.
8
+ *
9
+ * That policy is:
10
+ *
11
+ * - **Fields**: the label and the static text, and nothing else. Those are the
12
+ * two things the user wrote and can read; the icon is decoration and never an
13
+ * accessible or searchable name (spec 8.4), and the Command badge is derived
14
+ * from the text that is already searched.
15
+ * - **Case**: folded with `toLowerCase()`, which is the locale-independent
16
+ * Unicode default mapping. `toLocaleLowerCase()` is deliberately avoided: it
17
+ * would make the same query behave differently for a Turkish `i` depending on
18
+ * the UI language, and a filter that depends on the locale cannot be pinned.
19
+ * - **Unicode**: NFKC. It folds the compatibility forms a CJK IME actually
20
+ * produces — full-width Latin and full-width punctuation above all — so a
21
+ * query typed with the IME still finds half-width text.
22
+ * - **Whitespace**: every run collapses to one space, on both sides, so a
23
+ * one-line query can match a multi-line action text. The run definition is
24
+ * the regex `\s`, which is the same ECMAScript whitespace `trim()` uses, so
25
+ * this stays consistent with the blank rules of spec 4.3.
26
+ *
27
+ * Matching is per field and by substring: an action matches when the normalized
28
+ * query appears in its normalized label or in its normalized text. Fields are
29
+ * never concatenated first, which would let a query match across a boundary
30
+ * that does not exist.
31
+ *
32
+ * The comparison is deliberately not locale-collated (`Intl.Collator`): a
33
+ * collator answers "are these equal", not "does this contain that", and a
34
+ * substring search is what a filter box means.
35
+ */
36
+ import type { ProjectedQuickAction } from '../../model/index.js';
37
+ /**
38
+ * The one normalization both sides of a comparison go through. Applying it to
39
+ * the query and to the haystack through the same function is what keeps the two
40
+ * from drifting apart.
41
+ */
42
+ export declare function normalizeQuickActionSearchText(value: string): string;
43
+ /**
44
+ * Whether a query asks for anything at all. A whitespace-only box is an empty
45
+ * one, and telling the two apart is what lets a panel say "nothing matched"
46
+ * rather than "nothing to run".
47
+ */
48
+ export declare function hasQuickActionQuery(query: string): boolean;
49
+ /**
50
+ * The actions matching `query`, in their original relative order.
51
+ *
52
+ * An empty or whitespace-only query hands the same array back by identity: an
53
+ * unsearched panel is not a filtered panel, and returning a copy would remount
54
+ * every row for nothing.
55
+ */
56
+ export declare function filterQuickActions(actions: readonly ProjectedQuickAction[], query: string): readonly ProjectedQuickAction[];
@@ -0,0 +1,23 @@
1
+ import type { QuickActionsClientState, QuickActionWriteFailure } from '../controller.js';
2
+ import type { Translate } from '../dsh.js';
3
+ /** Why every write is refused right now, or `undefined` when none is. */
4
+ export type ManagerReadOnlyReason =
5
+ /** The connection cannot vouch for the held snapshot, so it must not be written over. */
6
+ 'offline'
7
+ /** No writable namespace: an unreadable first read, or a process-local page. */
8
+ | 'storage';
9
+ /**
10
+ * Report read-only state, and only read-only state.
11
+ *
12
+ * Spec 10 keeps two first-read failures apart: a catalog that could not be read
13
+ * shows a retryable catalog error, while a *readable* catalog whose user
14
+ * namespace failed shows "storage unavailable". Without the projection guard
15
+ * below, a loading or failed catalog also reads as read-only — the write gate
16
+ * refuses on `not-ready` — and the panel would print both notices at once,
17
+ * telling the user their storage is broken when it is the catalog that is
18
+ * missing. With no catalog there is nothing to be read-only *about*: the catalog
19
+ * notice is the whole story.
20
+ */
21
+ export declare function managerReadOnlyReason(client: QuickActionsClientState): ManagerReadOnlyReason | undefined;
22
+ /** One write failure as a sentence, with the recovery it implies (spec 10). */
23
+ export declare function managerFailureMessage(failure: QuickActionWriteFailure, t: Translate): string;