dsh-quick-actions 0.1.0 → 0.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,38 +1,35 @@
1
1
  import type { CanonicalRewriteOutcome, SettingsRewriteProvider } from './settings.js';
2
2
  import type { PresetCatalog } from '../model/index.js';
3
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.
4
+ * The read-only catalog projection (spec 6.2). It is the catalog itself under
5
+ * the name the spec gives it — one shape, so the Host cannot publish something
6
+ * the model never validated.
7
7
  */
8
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
- }
9
+ /** The Host settings provider as this plugin uses it; the real `SettingsForms` satisfies it. */
10
+ export type QuickActionsSettingsProvider = SettingsRewriteProvider;
17
11
  /** The running Host, owned by the fiber that started it. */
18
12
  export interface QuickActionsHost {
19
- /** Settles when the startup canonical rewrite has been attempted. */
20
- readonly ready: Promise<CanonicalRewriteOutcome>;
21
13
  /**
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.
14
+ * The authoritative catalog as lossless JSON. Each call hands back a detached
15
+ * copy, so a consumer cannot reach the authoritative catalog through the value
16
+ * it was given.
25
17
  */
26
18
  describeCatalog(): Promise<CatalogSnapshot>;
19
+ /**
20
+ * Canonicalize the stored section against this catalog (spec 6.3). Only
21
+ * meaningful once the entry is active: before that the provider does not
22
+ * serve the form, and the answer is `unregistered`.
23
+ */
24
+ canonicalize(settings: QuickActionsSettingsProvider): Promise<CanonicalRewriteOutcome>;
27
25
  }
28
26
  /**
29
- * Load the catalog, register both namespaces and canonicalize the stored section.
27
+ * Load and validate the catalog.
30
28
  *
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).
29
+ * An invalid preset configuration throws: the catalog is author-owned, so a
30
+ * mistake fails plugin loading rather than silently shipping a partial catalog
31
+ * (spec 5.1).
34
32
  *
35
- * @param settings - the Host settings provider.
36
33
  * @param config - the Host composition entry's config.
37
34
  */
38
- export declare function startQuickActionsHost(settings: QuickActionsSettingsProvider, config: unknown): QuickActionsHost;
35
+ export declare function startQuickActionsHost(config: unknown): QuickActionsHost;
@@ -1,67 +1,82 @@
1
1
  /**
2
- * The single persisted Settings namespace and the Host's canonical rewrite
3
- * (spec 4.2, 6.1, 6.3).
2
+ * The plugin's Config — which since DSH 0.1.7 *is* its Settings form — and the
3
+ * Host's canonical rewrite (spec 4.2, 6.1, 6.3, 22).
4
4
  *
5
- * The Host is the sole validation and migration authority, so the registered
6
- * schema is deliberately permissive: it fixes the shape of the section and its
7
- * defaults, and nothing else. A strict schema would refuse registration for a
8
- * section a higher version wrote — which is exactly the data spec 5.3 requires
9
- * to survive a downgrade untouched. The shared model decodes and normalizes what
10
- * the schema lets through, and it never rewrites content it cannot render.
5
+ * The Host is the sole validation and migration authority, so the schema is
6
+ * deliberately permissive: it fixes the shape of the section and its defaults,
7
+ * and nothing else. A strict schema would refuse a section a higher version
8
+ * wrote — which is exactly the data spec 5.3 requires to survive a downgrade
9
+ * untouched. The shared model decodes and normalizes what the schema lets
10
+ * through, and it never rewrites content it cannot render.
11
11
  */
12
12
  import Schema from '@deepseek-ai/schemastery';
13
13
  import type { PresetCatalog, QuickActionSettingsV1 } from '../model/index.js';
14
14
  /**
15
- * Shape and defaults of the persisted section (spec 4.2). The three collections
16
- * stay unconstrained on purpose: a stricter schema would refuse registration for
17
- * a section this release cannot render, and refusing registration is how stored
18
- * data gets lost. The shared model is the validation gate — see the module note.
19
- */
20
- export declare const quickActionSettingsSchema: Schema<Schemastery.ObjectS<{
21
- schemaVersion: Schema<number, number>;
22
- layout: Schema<string, string>;
23
- userActionsById: Schema<any, any>;
24
- actionOrder: Schema<any, any>;
25
- presetStateById: Schema<any, any>;
26
- }>, Schemastery.ObjectT<{
27
- schemaVersion: Schema<number, number>;
28
- layout: Schema<string, string>;
29
- userActionsById: Schema<any, any>;
30
- actionOrder: Schema<any, any>;
31
- presetStateById: Schema<any, any>;
32
- }>>;
33
- /**
34
- * Shape of the catalog namespace. Its authoritative content is the composition
35
- * `base` layer the Host declares; a user layer is never written and, if one were
36
- * hand-written into the document, Clients would still read `base` (spec 17.2).
15
+ * The plugin Config (spec 22.2). Every field is `volatile`, because DSH lets a
16
+ * Settings form — and so the Client — reach volatile fields only.
17
+ *
18
+ * - The five user-state fields are top-level on purpose: the DSH legacy import
19
+ * moves an old `settings.yaml` section into the entry of the same id key by
20
+ * key, and refuses the whole section when any top-level key is not a volatile
21
+ * field. This exact shape is what lets that one-shot import land.
22
+ * - `presets` is the author's list merged behind the built-ins (spec 5.1). It
23
+ * is volatile only so the Client can read it — a non-volatile field never
24
+ * reaches the browser — and nothing in this plugin ever writes it.
25
+ *
26
+ * The collections stay unconstrained: a stricter schema would refuse a section
27
+ * this release cannot render, and refusing it is how stored data gets lost.
37
28
  */
38
- export declare const quickActionCatalogSchema: Schema<Schemastery.ObjectS<{
39
- schemaVersion: Schema<number, number>;
40
- revision: Schema<string, string>;
41
- presets: Schema<any, any>;
42
- }>, Schemastery.ObjectT<{
43
- schemaVersion: Schema<number, number>;
44
- revision: Schema<string, string>;
45
- presets: Schema<any, any>;
46
- }>>;
47
- /** One namespace as the provider describes it. */
29
+ export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
30
+ presets: Schema<any, any, "volatile-defined">;
31
+ schemaVersion: Schema<number, number, "volatile-defined">;
32
+ layout: Schema<string, string, "volatile-defined">;
33
+ userActionsById: Schema<any, any, "volatile-defined">;
34
+ actionOrder: Schema<any, any, "volatile-defined">;
35
+ presetStateById: Schema<any, any, "volatile-defined">;
36
+ }>>, Schemastery.ObjectT<NoInfer<{
37
+ presets: Schema<any, any, "volatile-defined">;
38
+ schemaVersion: Schema<number, number, "volatile-defined">;
39
+ layout: Schema<string, string, "volatile-defined">;
40
+ userActionsById: Schema<any, any, "volatile-defined">;
41
+ actionOrder: Schema<any, any, "volatile-defined">;
42
+ presetStateById: Schema<any, any, "volatile-defined">;
43
+ }>>, "plain">;
44
+ /** One path-addressed field write, as the settings provider takes it. */
45
+ export interface SettingsSetOp {
46
+ readonly op: 'set';
47
+ readonly path: readonly string[];
48
+ readonly value: unknown;
49
+ }
50
+ /** One form as the provider describes it. */
48
51
  export interface SettingsDescriptorLike {
49
52
  readonly ns: string;
50
- /** Monotonic revision of the raw user section; send it back to fence a write. */
53
+ /** Monotonic revision of the raw entry configuration; send it back to fence a write. */
51
54
  readonly revision: number;
52
- /** The raw stored user section, absent while nothing was ever written. */
55
+ /** The form fields the active profile patch sets. */
53
56
  readonly user?: unknown;
54
57
  }
55
58
  /**
56
59
  * The part of the Host settings provider the canonical rewrite reads and writes
57
60
  * through. Declared structurally so the rewrite is testable against a controlled
58
- * fake — the real `SettingsProvider` satisfies it as-is.
61
+ * fake — the real `SettingsForms` satisfies it as-is.
59
62
  */
60
63
  export interface SettingsRewriteProvider {
61
64
  readonly writable: boolean;
62
65
  describe(): readonly SettingsDescriptorLike[];
63
- replace(ns: string, section: object, expectedRevision?: number): Promise<void>;
66
+ mutate(ns: string, ops: readonly SettingsSetOp[], expectedRevision?: number): Promise<void>;
64
67
  }
68
+ /**
69
+ * One write per user-state field, never touching `presets`: the author's list
70
+ * shares the entry's config, and a write that restated it would pin whatever it
71
+ * read into the profile layer.
72
+ */
73
+ export declare function stateOps(section: QuickActionSettingsV1): readonly SettingsSetOp[];
74
+ /**
75
+ * The user-state fields the profile layer actually sets, or `undefined` when it
76
+ * sets none. The same layer may carry the author's `presets`; those are Config,
77
+ * not stored state, so a profile holding only presets has nothing stored.
78
+ */
79
+ export declare function storedState(user: unknown): Readonly<Record<string, unknown>> | undefined;
65
80
  export type CanonicalRewriteOutcome =
66
81
  /** The stored section already was canonical; nothing was written. */
67
82
  {
@@ -90,7 +105,7 @@ export type CanonicalRewriteOutcome =
90
105
  } | {
91
106
  readonly status: 'read-only';
92
107
  }
93
- /** The namespace is not registered yet, so there is nothing to fence a write to. */
108
+ /** The form is not served — the entry is not active — so there is nothing to fence a write to. */
94
109
  | {
95
110
  readonly status: 'unregistered';
96
111
  };
@@ -99,9 +114,9 @@ export type CanonicalRewriteOutcome =
99
114
  * persist it behind the revision it was read at (spec 6.3).
100
115
  *
101
116
  * Nothing is written when nothing is stored: materializing the defaults into the
102
- * user layer would shadow the composition `base` and destroy what `replace({})`
103
- * resets to. Nothing is written for a higher `schemaVersion` either — that data
104
- * belongs to the version that wrote it.
117
+ * profile layer would shadow every layer below it for good. Nothing is written
118
+ * for a higher `schemaVersion` either — that data belongs to the version that
119
+ * wrote it.
105
120
  *
106
121
  * A conflict means another writer won the race, so the rewrite refreshes the
107
122
  * authoritative snapshot and recomputes rather than replaying its own stale
@@ -1,31 +1,33 @@
1
1
  /**
2
2
  * Host composition entry for Composer Quick Actions.
3
3
  *
4
- * `settings` is a hard dependency: the concrete provider (`dsh-settings-file`)
5
- * *is* the service, so injecting it is how this plugin waits for the backend
6
- * instead of inventing an in-memory, browser or private-file store (spec 6.1).
7
- * A provider that is present but not writable is a supported state, not a
8
- * missing one — the stored section is left untouched and the surfaces go
9
- * read-only (spec 10).
4
+ * Since DSH 0.1.7 the stored section is this entry's own Config (spec 22): the
5
+ * exported `Config` declares it, the Loader hands it over, and DSH Settings
6
+ * reads and writes it through the active profile. There is no namespace to
7
+ * register and no second namespace for the catalog.
10
8
  *
11
- * Everything this entry registers — the Settings namespace and the startup
12
- * rewrite's reporting — is owned by this fiber and withdrawn when it unloads.
13
- * The user's stored section deliberately survives unload, so reinstalling
14
- * restores the user's actions.
9
+ * `settings` is therefore an optional dependency, as for every first-party
10
+ * plugin: the Quick Actions run without it, and while it is present this entry
11
+ * opts out of an auto-generated Settings page — the plugin ships its own
12
+ * management panel — and canonicalizes the stored section once every entry of
13
+ * the profile has settled.
14
+ *
15
+ * Everything this entry installs is owned by this fiber and withdrawn when it
16
+ * unloads. The user's stored section deliberately survives unload, so
17
+ * reinstalling restores the user's actions.
15
18
  *
16
19
  * The public surface is the plugin contract only. The shared model, the
17
20
  * Settings rewrite and the catalog assembly stay internal to the package
18
- * (spec 11.2); whether any of them becomes a published runtime export belongs
19
- * to the release-surface decision.
21
+ * (spec 11.2).
20
22
  */
21
23
  import type { Context } from '@deepseek-ai/cordis';
22
24
  import type { QuickActionsHost } from './host/index.js';
23
25
  export declare const name = "composer-quick-actions";
24
- export declare const inject: readonly string[];
26
+ export { Config } from './host/settings.js';
25
27
  export type { ComposerQuickActionsConfig } from './host/config.js';
26
28
  export type { CatalogSnapshot, QuickActionsHost } from './host/index.js';
27
29
  /**
28
- * Load the Preset Catalog, register the Settings namespace and canonicalize the
30
+ * Load the Preset Catalog and, while Settings is present, canonicalize the
29
31
  * stored section. An invalid preset configuration throws here, failing plugin
30
32
  * loading loudly rather than shipping a partial catalog (spec 5.1).
31
33
  *
@@ -3,11 +3,11 @@ import type { PresetQuickAction, QuickActionField, QuickActionIssueReason } from
3
3
  export declare const QUICK_ACTION_CATALOG_LIMIT = 50;
4
4
  /** Where a catalog entry was declared. */
5
5
  export type PresetSource = 'builtin' | 'config';
6
- /** What the Host feeds the model: raw JSON, validated here rather than upstream. */
6
+ /** What either face feeds the model: raw JSON, validated here rather than upstream. */
7
7
  export interface PresetCatalogInput {
8
8
  /** The package's built-in manifest, in declaration order. */
9
9
  readonly builtins: readonly unknown[];
10
- /** `Config.presets` from the Host composition, in declaration order. */
10
+ /** The `presets` Config field, in declaration order. */
11
11
  readonly configured: readonly unknown[];
12
12
  }
13
13
  /** One reason the plugin config must fail to load. */
@@ -25,7 +25,7 @@ export type PresetCatalogIssue = {
25
25
  readonly count: number;
26
26
  readonly limit: number;
27
27
  };
28
- /** The authoritative, read-only catalog snapshot the Remote publishes (spec 6.2). */
28
+ /** The authoritative, read-only Preset Catalog (spec 6.2): what the Host validates and the Client rebuilds (spec 22.3). */
29
29
  export interface PresetCatalog {
30
30
  readonly schemaVersion: 1;
31
31
  /** Determined by the catalog projection; any change to it changes this value. */
@@ -46,18 +46,3 @@ export type PresetCatalogResult = {
46
46
  * author got wrong, so the mistake surfaces at startup rather than at send time.
47
47
  */
48
48
  export declare function buildPresetCatalog(input: PresetCatalogInput): PresetCatalogResult;
49
- /**
50
- * Read one published catalog snapshot back (spec 17.2). The Host is the catalog's
51
- * single validation authority, so this is a defensive decode of an already
52
- * confirmed snapshot rather than a second authority — but it is a decode, not a
53
- * cast: a snapshot this release cannot read in full yields no catalog at all.
54
- *
55
- * All-or-nothing on purpose. Dropping the entries it cannot read would show the
56
- * user a silently truncated action list, which is exactly what spec 5.1 forbids
57
- * the Host to do; the consumer reports a catalog error instead (spec 10).
58
- *
59
- * The Host's `revision` is carried through rather than recomputed. It is the
60
- * catalog's published identity, and a Client that recomputed it would be
61
- * asserting an authority it does not have.
62
- */
63
- export declare function decodeCatalogSnapshot(raw: unknown): PresetCatalog | undefined;
@@ -7,6 +7,7 @@ export * from './json.js';
7
7
  export * from './text.js';
8
8
  export * from './validation.js';
9
9
  export * from './catalog.js';
10
+ export * from './presets.js';
10
11
  export * from './settings.js';
11
12
  export * from './normalize.js';
12
13
  export * from './projection.js';
@@ -3,6 +3,11 @@
3
3
  * Preset Catalog (spec 5.1), declared ahead of the Host composition's
4
4
  * `Config.presets`.
5
5
  *
6
+ * It lives in the shared model because both faces merge it: the Host validates
7
+ * the catalog at load, and the Client rebuilds the same catalog from this list
8
+ * plus the `presets` form field it reads (spec 22.3). One list, so the two can
9
+ * never disagree about what the built-ins are.
10
+ *
6
11
  * These are author-facing product copy in one language; changing a label or a
7
12
  * text is a product decision, not an implementation one.
8
13
  *
@@ -1,16 +1,20 @@
1
1
  import type { CustomQuickActionValue, PresetQuickActionState, QuickActionSettingsV1, QuickActionTombstone, StoredQuickActionValue } from './types.js';
2
2
  /**
3
- * The one namespace holding user data (spec 4.2); renaming it orphans every
4
- * stored section. It lives in the shared model because both faces address it:
5
- * the Host registers it, the Client binds the same name.
3
+ * The one namespace holding user data (spec 4.2, 22); renaming it orphans every
4
+ * stored section. Since DSH 0.1.7 a Settings namespace *is* a profile entry id,
5
+ * so this is also the Cordis loader entry id the bundle patch inserts — the two
6
+ * can no longer drift apart. It lives in the shared model because both faces
7
+ * address it: the Host describes and fences its canonical rewrite against it,
8
+ * the Client reads and writes the same form.
6
9
  */
7
10
  export declare const QUICK_ACTIONS_SETTINGS_NAMESPACE = "composer-quick-actions";
8
11
  /**
9
- * The read-only namespace carrying the Preset Catalog to Clients (spec 17.2).
10
- * The plugin never writes its user layer, so it holds no persisted section —
11
- * `composer-quick-actions` remains the only persisted namespace (spec 4.2).
12
+ * The top-level fields of the stored section, in canonical order (spec 4.2).
13
+ * Each is a separate volatile Config field (spec 22.2): the DSH legacy import
14
+ * addresses a section by these exact top-level keys, and every write sets them
15
+ * field by field so a write never reaches the author's `presets`.
12
16
  */
13
- export declare const QUICK_ACTIONS_CATALOG_NAMESPACE = "composer-quick-actions-catalog";
17
+ export declare const QUICK_ACTION_STATE_FIELDS: readonly ["schemaVersion", "layout", "userActionsById", "actionOrder", "presetStateById"];
14
18
  /** The state a fresh install starts from (spec 4.2). */
15
19
  export declare const DEFAULT_QUICK_ACTION_SETTINGS: QuickActionSettingsV1;
16
20
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-quick-actions",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-rc.1",
4
4
  "description": "Persistent Quick Actions for the DSH message composer",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -60,23 +60,28 @@
60
60
  }
61
61
  },
62
62
  "peerDependencies": {
63
- "@deepseek-ai/cordis": "^4.0.2",
64
- "@deepseek-ai/dsh-client-connection": ">=0.1.5-rc.1",
65
- "@deepseek-ai/dsh-client-locale": ">=0.1.5-rc.1",
66
- "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.5-rc.1",
67
- "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.5-rc.1",
68
- "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.5-rc.1",
69
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.5-rc.1",
70
- "@deepseek-ai/dsh-settings": ">=0.1.5-rc.1",
71
- "@deepseek-ai/schemastery": "^3.18.2",
63
+ "@deepseek-ai/cordis": "^4.0.4",
64
+ "@deepseek-ai/dsh-client-connection": ">=0.1.7-alpha.2",
65
+ "@deepseek-ai/dsh-client-locale": ">=0.1.7-alpha.2",
66
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.7-alpha.2",
67
+ "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-alpha.2",
68
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.1.7-alpha.2",
69
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-alpha.2",
70
+ "@deepseek-ai/dsh-settings": ">=0.1.7-alpha.2",
71
+ "@deepseek-ai/schemastery": "^3.18.4",
72
72
  "react": "^18.3.1",
73
73
  "react-dom": "^18.3.1"
74
74
  },
75
75
  "devDependencies": {
76
- "@deepseek-ai/cordis": "^4.0.2",
76
+ "@deepseek-ai/cordis": "^4.0.4",
77
+ "@deepseek-ai/dsh-client-connection": "0.1.7-rc.1",
78
+ "@deepseek-ai/dsh-client-locale": "0.1.7-rc.1",
79
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.1",
77
80
  "@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1",
78
- "@deepseek-ai/dsh-settings": "^0.1.5-rc.1",
79
- "@deepseek-ai/schemastery": "^3.18.2",
81
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.1",
82
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.1",
83
+ "@deepseek-ai/dsh-settings": "0.1.7-rc.1",
84
+ "@deepseek-ai/schemastery": "^3.18.4",
80
85
  "@types/react": "~18.3.31",
81
86
  "@types/react-dom": "~18.3.7",
82
87
  "react": "^18.3.1",