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.
- package/LICENSE +21 -0
- package/README.en.md +199 -0
- package/README.md +199 -0
- package/cordis.patch.yml +29 -0
- package/lib/client.js +3901 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +737 -0
- package/lib/types/client/controller.d.ts +207 -0
- package/lib/types/client/dsh.d.ts +171 -0
- package/lib/types/client/index.d.ts +43 -0
- package/lib/types/client/manager/ActionForm.d.ts +20 -0
- package/lib/types/client/manager/ActionPanel.d.ts +15 -0
- package/lib/types/client/manager/ManagedRow.d.ts +40 -0
- package/lib/types/client/manager/ManagerPanel.d.ts +9 -0
- package/lib/types/client/manager/press.d.ts +38 -0
- package/lib/types/client/manager/search.d.ts +56 -0
- package/lib/types/client/manager/status.d.ts +23 -0
- package/lib/types/client/modal.d.ts +55 -0
- package/lib/types/client/session/ConfirmPanel.d.ts +23 -0
- package/lib/types/client/session/availability.d.ts +19 -0
- package/lib/types/client/session/execution.d.ts +86 -0
- package/lib/types/client/session/guards.d.ts +59 -0
- package/lib/types/client/surfaces/ActionFace.d.ts +21 -0
- package/lib/types/client/surfaces/ErrorBoundary.d.ts +31 -0
- package/lib/types/client/surfaces/QuickActionsSurface.d.ts +17 -0
- package/lib/types/client/surfaces/entries.d.ts +27 -0
- package/lib/types/client/surfaces/layout.d.ts +49 -0
- package/lib/types/client/surfaces/residency.d.ts +65 -0
- package/lib/types/host/config.d.ts +18 -0
- package/lib/types/host/index.d.ts +38 -0
- package/lib/types/host/presets.d.ts +26 -0
- package/lib/types/host/settings.d.ts +113 -0
- package/lib/types/index.d.ts +35 -0
- package/lib/types/locales/index.d.ts +34 -0
- package/lib/types/model/catalog.d.ts +63 -0
- package/lib/types/model/index.d.ts +13 -0
- package/lib/types/model/json.d.ts +11 -0
- package/lib/types/model/mutations.d.ts +98 -0
- package/lib/types/model/normalize.d.ts +15 -0
- package/lib/types/model/projection.d.ts +49 -0
- package/lib/types/model/settings.d.ts +48 -0
- package/lib/types/model/text.d.ts +28 -0
- package/lib/types/model/types.d.ts +89 -0
- package/lib/types/model/validation.d.ts +40 -0
- package/lib/types/styles/index.d.ts +39 -0
- package/lib/types/types.d.ts +11 -0
- package/lib/types.js +1 -0
- 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[];
|