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,55 @@
1
+ import type { KeyboardEvent as ReactKeyboardEvent, MutableRefObject } from 'react';
2
+ /** What {@link useModalKeys} hands a panel. */
3
+ export interface ModalKeys<T extends HTMLElement> {
4
+ /** Put on the panel element itself; the Tab boundary is computed from its subtree. */
5
+ readonly panelRef: MutableRefObject<T | null>;
6
+ /** Put on the same element; handles Escape and the Tab boundary. */
7
+ readonly onKeyDown: (event: ReactKeyboardEvent<T>) => void;
8
+ }
9
+ /**
10
+ * Escape cancels, and Tab stays inside.
11
+ *
12
+ * The focusable set is recomputed on every keystroke rather than cached: a
13
+ * management panel grows a form, a form's Save button goes inert while a write
14
+ * is in flight, and a stale boundary would trap focus on a control that is no
15
+ * longer there.
16
+ */
17
+ export declare function useModalKeys<T extends HTMLElement>(onCancel: () => void): ModalKeys<T>;
18
+ /**
19
+ * Return focus to whatever opened this panel, when it closes (spec 8.4).
20
+ *
21
+ * The opener is captured in a layout effect, so it must be declared *before* any
22
+ * effect that moves focus into the panel: effects run in declaration order
23
+ * within a component, and layout effects run before passive ones, so the capture
24
+ * always sees the control the user actually activated.
25
+ *
26
+ * An opener that has since been unmounted — an action picked from a list that
27
+ * closed with it — is skipped rather than focused, and the caller is free to
28
+ * offer a fallback of its own.
29
+ *
30
+ * @param scope - the closing context's own element, for an editing context
31
+ * nested inside a panel (the management form inside the management panel).
32
+ * When the outer panel closes, React runs the outer cleanup first, the inner
33
+ * cleanup next, and only then removes the outer DOM: by the time the nested
34
+ * context returns focus, the panel has already handed it to *its* opener, and
35
+ * the nested opener is a control about to leave the document. So a nested
36
+ * context returns focus only while focus is still inside it (or fell to the
37
+ * body); focus that has already left belongs to whoever moved it.
38
+ */
39
+ export declare function useFocusReturn(scope?: MutableRefObject<HTMLElement | null>): void;
40
+ /** Move focus into a panel once, on open (spec 8.4). */
41
+ export declare function useInitialFocus<T extends HTMLElement>(): MutableRefObject<T | null>;
42
+ /**
43
+ * Move focus into a panel once, on open, addressing the target through the
44
+ * panel rather than through a ref (spec 8.4).
45
+ *
46
+ * `@deepseek-ai/dsh-client-ui-primitives` publishes no `forwardRef` at all, so
47
+ * an official `Button` or `Input` cannot carry one. Addressing the opening
48
+ * target by marker attribute — the way the Tab boundary above already addresses
49
+ * the focusable set — keeps that limitation in this one module instead of
50
+ * pushing every panel back onto native controls.
51
+ *
52
+ * @param container - the panel whose subtree holds the target.
53
+ * @param selector - CSS selector for the control that should open focused.
54
+ */
55
+ export declare function useInitialFocusIn<T extends HTMLElement>(container: MutableRefObject<T | null>, selector: string): void;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The send confirmation panel (spec 9.3).
3
+ *
4
+ * It shows the action's label and the complete text that will be submitted, and
5
+ * for a Command Send Action it also carries the notice spec 9.3 and 16.2 make
6
+ * mandatory: the text goes to DSH's own command adjudication path, and no native
7
+ * `/` suggestion menu appears here, so what is on screen is exactly what will be
8
+ * submitted. Nothing in this panel imitates that menu.
9
+ *
10
+ * Opening it does not touch the draft; cancelling — the button, Escape, or a
11
+ * click on the backdrop — has no side effect at all. The re-verification that
12
+ * follows a confirmation lives in the execution engine, not here.
13
+ */
14
+ import type { ReactElement } from 'react';
15
+ import type { PendingQuickActionConfirmation } from './execution.js';
16
+ import type { Translate } from '../dsh.js';
17
+ export interface ConfirmPanelProps {
18
+ readonly pending: PendingQuickActionConfirmation;
19
+ readonly t: Translate;
20
+ readonly onConfirm: () => void;
21
+ readonly onCancel: () => void;
22
+ }
23
+ export declare function ConfirmPanel({ pending, t, onConfirm, onCancel }: ConfirmPanelProps): ReactElement;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Which control one Quick Action renders as, given its Session's execution
3
+ * state (spec 3, 9.2).
4
+ *
5
+ * Shared by every surface that offers an action for execution — the three
6
+ * layouts and the shared searchable action panel — so a control cannot explain
7
+ * itself one way in the ribbon and another way behind "more".
8
+ */
9
+ import type { QuickActionSessionState } from './execution.js';
10
+ import type { QuickActionUnavailableReason } from './guards.js';
11
+ import type { ProjectedQuickAction } from '../../model/index.js';
12
+ /**
13
+ * The reason this action cannot run right now, or `undefined` when it can.
14
+ *
15
+ * While a send holds the Session's single flight, only the action holding it
16
+ * explains itself as "sending"; the rest are simply unavailable while the
17
+ * Composer is busy with it (spec 9.5).
18
+ */
19
+ export declare function unavailableReasonFor(action: ProjectedQuickAction, session: QuickActionSessionState): QuickActionUnavailableReason | undefined;
@@ -0,0 +1,86 @@
1
+ import type { QuickActionUnavailableReason } from './guards.js';
2
+ import type { ComposerBlock, InputActions, InputState, SessionSnapshot } from '../dsh.js';
3
+ import type { ProjectedQuickAction, QuickActionRef } from '../../model/index.js';
4
+ /** The action awaiting the user's confirmation (spec 9.3). */
5
+ export interface PendingQuickActionConfirmation {
6
+ readonly ref: QuickActionRef;
7
+ readonly label: string;
8
+ /** The complete text that will be submitted; the panel shows it verbatim. */
9
+ readonly text: string;
10
+ /** Whether this is a Command Send Action, so the panel can carry its notice. */
11
+ readonly command: boolean;
12
+ }
13
+ /** What the surfaces report after an execution attempt (spec 9.3, 9.5). */
14
+ export type QuickActionFeedback =
15
+ /** A re-verification found the action, Session, draft or guard changed. */
16
+ {
17
+ readonly kind: 'state-changed';
18
+ }
19
+ /** The text was loaded but the machine did not take it; the draft keeps it. */
20
+ | {
21
+ readonly kind: 'retained';
22
+ }
23
+ /** Nothing was loaded; the draft is untouched and the action can be retried. */
24
+ | {
25
+ readonly kind: 'failed';
26
+ readonly message: string;
27
+ };
28
+ /** Everything the surfaces render about this Session's execution state. */
29
+ export interface QuickActionSessionState {
30
+ /** Why no action may run right now, or `undefined` when they may (spec 3). */
31
+ readonly unavailable: QuickActionUnavailableReason | undefined;
32
+ /** The confirmation panel's subject, when one is open. */
33
+ readonly confirming: PendingQuickActionConfirmation | undefined;
34
+ /** Whether a send holds this Session's single flight. */
35
+ readonly sending: boolean;
36
+ /** The action holding the flight, so its own control can show the state. */
37
+ readonly activeRef: QuickActionRef | undefined;
38
+ /** The last execution feedback, until dismissed or superseded. */
39
+ readonly feedback: QuickActionFeedback | undefined;
40
+ }
41
+ /** The per-Session engine as the surfaces consume it. */
42
+ export interface QuickActionSessionEngine {
43
+ getSnapshot(): QuickActionSessionState;
44
+ subscribe(listener: () => void): () => void;
45
+ /**
46
+ * Publish the current DSH state into the engine. Called on every commit of
47
+ * the entry that owns the layout, and the only way a snapshot reaches here —
48
+ * the engine never subscribes to a DSH store of its own.
49
+ */
50
+ observe(input: InputState, session: SessionSnapshot, block: ComposerBlock | undefined): void;
51
+ /** Bind the live `InputActions` and the live Composer projection (spec 7.2). */
52
+ bind(face: InputActions | undefined, composer: readonly ProjectedQuickAction[]): void;
53
+ /** Begin one Quick Action: claims the single flight, or is ignored. */
54
+ activate(action: ProjectedQuickAction): void;
55
+ /** Confirm the pending action after re-verifying everything (spec 9.3). */
56
+ confirm(): void;
57
+ /** Cancel the pending confirmation with no side effect (spec 9.3). */
58
+ cancel(): void;
59
+ dismissFeedback(): void;
60
+ /**
61
+ * Drop everything that has not entered the official state machine — the
62
+ * Session-switch, layout-unmount and Slot-replacement rule of spec 7.2. A send
63
+ * the machine already accepted is left alone: it belongs to its own Session.
64
+ */
65
+ cancelPending(): void;
66
+ dispose(): void;
67
+ }
68
+ /** Create the execution engine for one Session. */
69
+ export declare function createQuickActionSessionEngine(sessionId: string): QuickActionSessionEngine;
70
+ /**
71
+ * The fiber-owned registry of per-Session engines.
72
+ *
73
+ * The engine is looked up by Session id rather than constructed by the surface,
74
+ * which is what keeps one lock per Session across a layout switch: the ribbon
75
+ * unmounts as the bar mounts, and the engine in between is the same object
76
+ * (spec 7.2). Engines live until the Client fiber unloads; each one is a handful
77
+ * of fields, and tying their lifetime to the fiber avoids a reference count that
78
+ * a double-invoked render could get wrong.
79
+ */
80
+ export interface QuickActionSessionRegistry {
81
+ /** This Session's engine, created on first use. */
82
+ engineFor(sessionId: string): QuickActionSessionEngine;
83
+ /** Release every engine — the Client fiber's own disposer. */
84
+ dispose(): void;
85
+ }
86
+ export declare function createQuickActionSessionRegistry(): QuickActionSessionRegistry;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The send preconditions of spec 9.2, derived from nothing but the public
3
+ * per-Session snapshots a dock Slot entry receives.
4
+ *
5
+ * Both functions are total over the published `InputState` and the published
6
+ * `SessionSnapshot`: every field of the public Input snapshot is accounted for
7
+ * here or explained as deliberately not a precondition, because spec 9.5 makes
8
+ * that exact field set the only evidence this feature may judge a send by.
9
+ */
10
+ import type { ComposerBlock, InputState, SessionSnapshot } from '../dsh.js';
11
+ /** Why no Quick Action may execute in this Composer right now (spec 3, 9.2). */
12
+ export type QuickActionUnavailableReason =
13
+ /** The draft holds text, an attachment or a reference chip (Occupied Draft). */
14
+ 'occupied-draft'
15
+ /** The input machine is mid-adjudication, mid-submit, or in command mode. */
16
+ | 'composer-busy'
17
+ /** A feature-owned Composer block is raised for this Session. */
18
+ | 'composer-blocked'
19
+ /** The Session was removed. */
20
+ | 'session-removed'
21
+ /** A continuable subagent whose parent is not available: the composer is inert. */
22
+ | 'parent-offline'
23
+ /** This Session already has a send in flight (spec 9.5 single-flight). */
24
+ | 'sending';
25
+ /**
26
+ * Whether the draft is occupied (spec 9.2). Occupancy is any text — pure
27
+ * whitespace included, so the raw string is compared against `''` and never
28
+ * trimmed — any attachment, and any rich reference.
29
+ *
30
+ * The public snapshot exposes exactly `{ draft, attachmentIds, draftRev, phase,
31
+ * claim?, occurrences, queue }`. Occupancy reads three of them:
32
+ *
33
+ * - `draft` — the clipboard-text projection of the whole document. Compared
34
+ * against `''`, so a snapshot without it is occupied for the same reason
35
+ * {@link holdsContent} gives;
36
+ * - `attachmentIds` — the ordered draft attachments, and the only public
37
+ * attachment field there is. Since 0.1.5-rc.1 it admits any attachment kind,
38
+ * not just images;
39
+ * - `occurrences` — the reference chips. They are already expanded inside
40
+ * `draft`, so this test is redundant today; it is kept because a future chip
41
+ * whose clipboard projection is empty must still count as content the send
42
+ * action would carry away.
43
+ *
44
+ * The remaining fields are not occupancy: `draftRev` is a revision counter,
45
+ * `phase`/`claim` are the submit plane (handled by {@link composerGate}), and
46
+ * `queue` is the Session's transient inbox, which spec 9.2 explicitly allows a
47
+ * send to join.
48
+ */
49
+ export declare function isOccupiedDraft(input: InputState): boolean;
50
+ /**
51
+ * The composer-level guard, reproducing the shipped send button's own
52
+ * conditions from public state alone (spec 9.2).
53
+ *
54
+ * `disabled` in spec 9.2's list is the shipped bar's inert state, which is
55
+ * reached only without a Session or on the blank-session hero. Neither can
56
+ * occur here: both dock Slots are session-scoped, and the Quick Action surfaces
57
+ * render only where the Resident Composer predicate holds.
58
+ */
59
+ export declare function composerGate(input: InputState, session: SessionSnapshot, block: ComposerBlock | undefined): QuickActionUnavailableReason | undefined;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The visible face of one Quick Action — the shared action control of spec 14's
3
+ * `src/client/surfaces/` boundary.
4
+ *
5
+ * Three places offer an action to the user: a layout's control, the shared
6
+ * searchable action panel's list item, and the management list's row. All three
7
+ * must read the same, because the Command marker is only meaningful if it looks
8
+ * and means the same wherever it appears, and the emoji is only decoration if it
9
+ * is `aria-hidden` in every one of them (spec 8.4).
10
+ *
11
+ * It renders a fragment, not an element: each caller owns its own control — a
12
+ * `<button>`, a list row — and its own layout box.
13
+ */
14
+ import type { ReactElement } from 'react';
15
+ import type { ProjectedQuickAction } from '../../model/index.js';
16
+ import type { Translate } from '../dsh.js';
17
+ export interface ActionFaceProps {
18
+ readonly action: ProjectedQuickAction;
19
+ readonly t: Translate;
20
+ }
21
+ export declare function ActionFace({ action, t }: ActionFaceProps): ReactElement;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * One Slot entry's local error boundary (spec 7.3).
3
+ *
4
+ * A render error inside the Quick Action surfaces replaces the Quick Action
5
+ * area and nothing else: the Composer, its draft and its submit button keep
6
+ * working, and the user can ask for the surface back. Retrying remounts the
7
+ * subtree by changing its key, so a transient failure clears without a reload.
8
+ *
9
+ * DSH's own submission errors never reach here — they are reported by the
10
+ * Composer, and this feature must not repeat them (spec 9.5).
11
+ */
12
+ import { Component } from 'react';
13
+ import type { ErrorInfo, ReactNode } from 'react';
14
+ import type { Translate } from '../dsh.js';
15
+ export interface SurfaceErrorBoundaryProps {
16
+ readonly t: Translate;
17
+ readonly children: ReactNode;
18
+ }
19
+ interface SurfaceErrorBoundaryState {
20
+ readonly failed: boolean;
21
+ /** Bumped on retry so the failing subtree is rebuilt rather than reused. */
22
+ readonly attempt: number;
23
+ }
24
+ export declare class SurfaceErrorBoundary extends Component<SurfaceErrorBoundaryProps, SurfaceErrorBoundaryState> {
25
+ state: SurfaceErrorBoundaryState;
26
+ static getDerivedStateFromError(): Partial<SurfaceErrorBoundaryState>;
27
+ componentDidCatch(error: Error, info: ErrorInfo): void;
28
+ private readonly retry;
29
+ render(): ReactNode;
30
+ }
31
+ export {};
@@ -0,0 +1,17 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { QuickActionSessionState } from '../session/execution.js';
3
+ import type { ProjectedQuickAction, QuickActionLayout } from '../../model/index.js';
4
+ import type { Translate } from '../dsh.js';
5
+ export interface QuickActionsSurfaceProps {
6
+ readonly layout: QuickActionLayout;
7
+ /** The Composer projection, in the shared order (hidden actions already gone). */
8
+ readonly actions: readonly ProjectedQuickAction[];
9
+ readonly session: QuickActionSessionState;
10
+ readonly t: Translate;
11
+ readonly onActivate: (action: ProjectedQuickAction) => void;
12
+ readonly onConfirm: () => void;
13
+ readonly onCancelConfirm: () => void;
14
+ readonly onDismissFeedback: () => void;
15
+ readonly onManage: () => void;
16
+ }
17
+ export declare function QuickActionsSurface(props: QuickActionsSurfaceProps): ReactElement;
@@ -0,0 +1,27 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { ResidentComposerRegistry } from './residency.js';
3
+ import type { QuickActionSessionRegistry } from '../session/execution.js';
4
+ import type { QuickActionsController } from '../controller.js';
5
+ import type { ComposerBlocks, ComposerDockProps, InputDockProps } from '../dsh.js';
6
+ /** Everything the Slot entries reach outside React. */
7
+ export interface QuickActionSurfaceDeps {
8
+ readonly controller: QuickActionsController;
9
+ readonly sessions: QuickActionSessionRegistry;
10
+ readonly residency: ResidentComposerRegistry;
11
+ /**
12
+ * Resolves `ctx.conversation.blocks`, the registry face documented as "how a
13
+ * plugin the composer cannot import makes a session's input inert". It is the
14
+ * only public reading of the `blocked` guard of spec 9.2, and it is reached
15
+ * through `ctx.get`, which the Cordis reflection layer documents as a read
16
+ * "without the inject requirement" — so the Client's declared dependencies
17
+ * stay exactly the four of spec 7.3. Absent, the guard degrades to "not
18
+ * blocked" rather than guessing from the DOM.
19
+ */
20
+ readonly blocks: () => ComposerBlocks | undefined;
21
+ }
22
+ /** Build both Slot entry components over one set of fiber-owned dependencies. */
23
+ export declare function createQuickActionDockEntries(deps: QuickActionSurfaceDeps): {
24
+ readonly InputDock: (props: InputDockProps) => ReactElement;
25
+ readonly ComposerDock: (props: ComposerDockProps) => ReactElement;
26
+ readonly ManagerDock: (props: InputDockProps) => ReactElement;
27
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The width and density rules of spec 8.2, as pure functions.
3
+ *
4
+ * The geometry itself is CSS (see `src/styles/`), because the two docks sit in
5
+ * different boxes: the ribbon renders outside the InputBar, which owns the side
6
+ * clearance, so it must subtract that clearance itself; the bar renders inside
7
+ * the InputBar, whose padding already is the clearance, so it takes the full
8
+ * width. Both are then capped by the card's own max width and centred, which is
9
+ * what makes their outer edges line up with the input box.
10
+ *
11
+ * What is left over is the part that cannot be expressed in CSS: how many
12
+ * leading actions the `bar` layout shows before the rest fold into "more", and
13
+ * which density the surface renders at. Both are decided here so the rules can
14
+ * be pinned by tests rather than by a screenshot.
15
+ */
16
+ /** How the surface presents itself at the current width (spec 8.2). */
17
+ export type SurfaceDensity = 'wide' | 'narrow';
18
+ /**
19
+ * Below this surface width the layout drops secondary information — the section
20
+ * title — and tightens its spacing. It never changes the outer width, and it
21
+ * never drops a control's visible text label (spec 8.2, 8.4).
22
+ *
23
+ * Sized against the viewports spec 13.3 requires: a desktop or ~768px viewport
24
+ * leaves the composer far above this, and a ~360px viewport lands well below it.
25
+ */
26
+ export declare const NARROW_SURFACE_WIDTH = 480;
27
+ export declare function densityFor(width: number): SurfaceDensity;
28
+ /** One measured row of the `bar` layout. */
29
+ export interface BarFitInput {
30
+ /** Usable inner width of the action row. */
31
+ readonly available: number;
32
+ /** Measured width of each action control, in the shared order. */
33
+ readonly widths: readonly number[];
34
+ /** Width the always-visible controls ("more", "manage") need to keep. */
35
+ readonly reserved: number;
36
+ /** Gap between adjacent controls. */
37
+ readonly gap: number;
38
+ }
39
+ /**
40
+ * How many leading actions the `bar` layout shows (spec 8.1). The rest fold into
41
+ * the shared action panel behind "more", and the management entry stays visible
42
+ * whatever the answer is.
43
+ *
44
+ * Before the first measurement — a fresh mount, or a headless environment with
45
+ * no layout — every width reads as zero. That answers "all of them": showing the
46
+ * full row and letting the browser's own overflow handle it for one frame is
47
+ * better than flashing a "more" button that the next frame withdraws.
48
+ */
49
+ export declare function fitActionCount(input: BarFitInput): number;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The Resident Composer predicate (spec 2.3, 8.1; spec 15, decision 6).
3
+ *
4
+ * ## Why a predicate is needed at all
5
+ *
6
+ * The two dock Slots this feature registers into do not have the same reach:
7
+ *
8
+ * - `conversation.composer.dock` — "Ambient entries below the composer card" —
9
+ * is rendered by the shipped `InputBar` under
10
+ * `variant === 'composer' && input !== undefined && sessionId !== undefined`.
11
+ * The blank-session hero renders the same bar with `variant: 'hero'`, and that
12
+ * branch renders no composer dock at all.
13
+ * - `conversation.input.dock` — "Full-width entries above the composer card" —
14
+ * is rendered by `ConversationRoot` from the composer stack, which is shared
15
+ * by the hero and the resident composer. Its `zone` owner prop only requires
16
+ * a Session and an Input, so the entry *does* mount on the hero.
17
+ *
18
+ * So the input dock alone cannot tell a Resident Composer from a hero, and the
19
+ * first release must render in one and never the other.
20
+ *
21
+ * ## The predicate
22
+ *
23
+ * A `conversation.composer.dock` entry is mounted **if and only if** the shipped
24
+ * composer is in its resident, session-backed variant. That mount is therefore
25
+ * DSH's own public statement of residency, and this registry is how the input
26
+ * dock reads it: the composer-dock entry marks its Session resident while it is
27
+ * mounted, and the input-dock entry renders the current layout only while the
28
+ * mark stands.
29
+ *
30
+ * It is a contract-level signal, not a guess: no DOM is measured, no private
31
+ * Composer state is read, and the shipped hero branch is not re-derived here —
32
+ * which is what spec 8.1 forbids ("不得通过 DOM 或私有 Composer 状态猜测来规避").
33
+ * The mark is published from a layout effect, so a hero that becomes resident
34
+ * brings the layout up in the same commit rather than the next one; and if that
35
+ * ever slipped a frame, it would slip in the required direction — the hard rule
36
+ * is that nothing appears on the hero, never that it appears instantly.
37
+ *
38
+ * Takeover composers need no predicate of their own: when a `conversation.composer`
39
+ * chain entry wins, the renderer keeps the resident fallback mounted but hides
40
+ * its wrapper, so both docks — and everything in them — go with it.
41
+ */
42
+ /** Which Sessions currently have a Resident Composer on screen. */
43
+ export interface ResidentComposerRegistry {
44
+ /**
45
+ * Mark one Session's Composer resident. The disposer withdraws the mark, and
46
+ * marks are counted, so a remount that overlaps its own unmount cannot leave
47
+ * the Session looking non-resident.
48
+ */
49
+ mark(sessionId: string): () => void;
50
+ isResident(sessionId: string): boolean;
51
+ /**
52
+ * The Session that owns anything global, or `undefined` when none is resident.
53
+ *
54
+ * The centralized management panel is global state rendered from a
55
+ * session-scoped Slot (spec 8.1 asks for it to be registered independently,
56
+ * and this release has no Slot outside a Session to register into). So if two
57
+ * Resident Composers were ever on screen at once, both entries would render
58
+ * the same overlay. Electing one owner — the Session that has been resident
59
+ * longest, which is stable while it stays mounted — is what keeps the panel
60
+ * single.
61
+ */
62
+ primarySessionId(): string | undefined;
63
+ subscribe(listener: () => void): () => void;
64
+ }
65
+ export declare function createResidentComposerRegistry(): ResidentComposerRegistry;
@@ -0,0 +1,18 @@
1
+ import type { PresetCatalog } from '../model/index.js';
2
+ /** What a Host composition may declare for this plugin. */
3
+ export interface ComposerQuickActionsConfig {
4
+ /** Preset Quick Actions appended after the package's built-in manifest. */
5
+ readonly presets?: readonly unknown[];
6
+ }
7
+ export type ComposerQuickActionsConfigResult = {
8
+ readonly ok: true;
9
+ readonly catalog: PresetCatalog;
10
+ } | {
11
+ readonly ok: false;
12
+ readonly message: string;
13
+ };
14
+ /**
15
+ * Read one Host composition config into the authoritative catalog.
16
+ * @param config - the composition entry's config, absent when nothing was declared.
17
+ */
18
+ export declare function readComposerQuickActionsConfig(config: unknown): ComposerQuickActionsConfigResult;
@@ -0,0 +1,38 @@
1
+ import type { CanonicalRewriteOutcome, SettingsRewriteProvider } from './settings.js';
2
+ import type { PresetCatalog } from '../model/index.js';
3
+ /**
4
+ * The read-only catalog projection published to Clients (spec 6.2, 17.2). It is
5
+ * the catalog itself under the name the spec gives it — one shape, so the Host
6
+ * cannot publish something the model never validated.
7
+ */
8
+ export type CatalogSnapshot = PresetCatalog;
9
+ /**
10
+ * The Host settings provider as this plugin uses it: namespace registration plus
11
+ * the fenced write face. Declared structurally so the Host is testable against a
12
+ * controlled fake; the real `SettingsProvider` satisfies it.
13
+ */
14
+ export interface QuickActionsSettingsProvider extends SettingsRewriteProvider {
15
+ register(ns: string, schema: unknown, options?: unknown): unknown;
16
+ }
17
+ /** The running Host, owned by the fiber that started it. */
18
+ export interface QuickActionsHost {
19
+ /** Settles when the startup canonical rewrite has been attempted. */
20
+ readonly ready: Promise<CanonicalRewriteOutcome>;
21
+ /**
22
+ * The read-only Remote payload (spec 6.2): lossless JSON, no Settings CRUD.
23
+ * Each call hands back a detached copy, so a consumer cannot reach the
24
+ * authoritative catalog through the value it was given.
25
+ */
26
+ describeCatalog(): Promise<CatalogSnapshot>;
27
+ }
28
+ /**
29
+ * Load the catalog, register both namespaces and canonicalize the stored section.
30
+ *
31
+ * An invalid preset configuration throws before anything is registered: the
32
+ * catalog is author-owned, so a mistake fails plugin loading rather than
33
+ * silently shipping a partial catalog (spec 5.1).
34
+ *
35
+ * @param settings - the Host settings provider.
36
+ * @param config - the Host composition entry's config.
37
+ */
38
+ export declare function startQuickActionsHost(settings: QuickActionsSettingsProvider, config: unknown): QuickActionsHost;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The package's built-in Preset Quick Action manifest — component 1 of the
3
+ * Preset Catalog (spec 5.1), declared ahead of the Host composition's
4
+ * `Config.presets`.
5
+ *
6
+ * These are author-facing product copy in one language; changing a label or a
7
+ * text is a product decision, not an implementation one.
8
+ *
9
+ * Two rules bind any edit here:
10
+ *
11
+ * - A Preset Action ID is permanent. Label, icon and text may change under the
12
+ * same id, but `kind` and `confirm` are the immutable safety behaviour
13
+ * signature — changing either needs a new id (spec 5.1). Editing a text so it
14
+ * crosses the Command Send Action boundary counts as crossing that signature.
15
+ * - Adding an entry is additive: a new id appends to the end of every existing
16
+ * user's order and rewrites no stored data (spec 5.3).
17
+ *
18
+ * `summarize` and `explain` carry new ids rather than reusing
19
+ * `summarize-thread` and `explain-last-change`: those two shipped on the
20
+ * default `confirm: true`, and turning confirmation off is exactly the
21
+ * signature change the first rule reserves a new id for. The old ids simply
22
+ * leave the catalog, which the model already handles — a stored preference for
23
+ * an id no longer in the catalog is kept as a tombstone, neither shown nor
24
+ * counted (spec 5.3).
25
+ */
26
+ export declare const BUILT_IN_PRESETS: readonly unknown[];