dsh-plugin-shop 0.8.7 → 0.9.0-beta.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.
@@ -233,8 +233,9 @@ export interface InstallStatusShape {
233
233
  /** The install view state machine (§7.2). `phase` distinguishes the download
234
234
  * that runs ahead of the per-profile mutex from the install that holds it.
235
235
  * It rides the `running` kind rather than adding a kind of its own: every
236
- * `view.kind === 'running'` site — `useInstall.ts`'s poll collection among
237
- * them — must keep treating a downloading install as live. */
236
+ * `view.kind === 'running'` site — the flow registry's poll collection
237
+ * (flow-registry.ts) among them — must keep treating a downloading install
238
+ * as live. */
238
239
  export type InstallView = {
239
240
  kind: 'idle';
240
241
  } | {
@@ -328,12 +329,23 @@ export declare function restartMonitorVerdict(input: {
328
329
  * a host older than the field, the one that matters most.
329
330
  */
330
331
  export declare function answeredByAnotherProcess(previous: ShopVersionResult, next: ShopVersionResult): boolean;
332
+ /**
333
+ * Whether an update of the shop has landed and waits for a restart: the
334
+ * profile holds a version other than the one running. A host older than the
335
+ * field sends none, and that reads as waiting: a client newer than its host
336
+ * exists only because the disk holds a newer shop than the process running
337
+ * (design §7.3, 2026-09-27 amendment). The tab's restart offer and the
338
+ * hand-over after a hot swap (design 2026-10-16-shop-sidebar-page §6) read
339
+ * it alike.
340
+ */
341
+ export declare function updateAwaitsRestart(version: ShopVersionResult): boolean;
331
342
  /**
332
343
  * Whether dsh restarting under the page settles this receipt: a change that
333
- * landed and was waiting for exactly that. The tab clears these once the
334
- * answers come from another process, and nothing else — a reload is still
335
- * owed by a page nobody reloaded, a failure still happened, and a flow still
336
- * running settles by its own poll.
344
+ * landed and was waiting for exactly that. The page store clears these
345
+ * (page-store.ts, `noteRestarted`) once the answers come from another
346
+ * process, and nothing else — a reload is still owed by a page nobody
347
+ * reloaded, a failure still happened, and a flow still running settles by
348
+ * its own poll.
337
349
  */
338
350
  export declare function settledByRestart(view: InstallView): boolean;
339
351
  /** How long the check-update button reports "up to date" after a re-check
@@ -456,6 +468,14 @@ export declare function npmPageUrl(entry: CatalogEntry): string | null;
456
468
  * This mirrors the registry's identity verbatim; the two must not drift.
457
469
  */
458
470
  export declare function entryKey(entry: CatalogEntry): string;
471
+ /**
472
+ * Whether the shelf lists `entry`: a name the catalog's `notAShop` clears,
473
+ * or one whose name and repository `isShopLike` leaves alone. One rule for
474
+ * the shelf and for the Plugins page's "View in Plugin shop" (design
475
+ * 2026-10-16-shop-sidebar-page §7), so that button never sends a reader to a
476
+ * shelf that hides the plugin.
477
+ */
478
+ export declare function listedOnShelf(entry: CatalogEntry, cleared: ReadonlySet<string>): boolean;
459
479
  /**
460
480
  * One entry's star count from the sidecar, or undefined when it has none.
461
481
  *
@@ -72,6 +72,17 @@ export type RestartRequestOutcome = {
72
72
  } | {
73
73
  kind: 'unreachable';
74
74
  };
75
+ /**
76
+ * Whether a shop instance on this page has registered its restart path:
77
+ * every `apply` since 0.8.4 does so before anything can ask for a restart.
78
+ * Read before this instance's own `apply` registers one, a store here names
79
+ * a predecessor — a 0.8.x shop when no page store sits beside it
80
+ * (page-store.ts `readPredecessor`; design 2026-10-16-shop-sidebar-page §6).
81
+ * The store outlives a carrier's withdrawal: a predecessor's teardown
82
+ * withdraws its carrier and leaves the store, and the store is what names it.
83
+ * Only a read: unlike `monitorStore`, it never creates the store.
84
+ */
85
+ export declare function hasRestartMonitorStore(): boolean;
75
86
  /** The page's restart, or null when it has committed none. The same object
76
87
  * until the state changes, as `useSyncExternalStore` requires. */
77
88
  export declare function readRestartMonitor(): RestartMonitorState | null;
@@ -1,5 +1,6 @@
1
1
  /**
2
- * One keyed registry of install-shaped flows: start, poll to terminal, reset.
2
+ * React's view of a keyed registry of install-shaped flows (flow-registry.ts):
3
+ * start, poll to terminal, reset.
3
4
  *
4
5
  * Install and uninstall are the same machine. Both fold a
5
6
  * `ShopInstallStatusResult` through `reduceInstall` into an `InstallView`,
@@ -12,16 +13,22 @@
12
13
  * install refusal is a `rejected` view carrying the gate's code; an uninstall
13
14
  * refusal is a `failed` view carrying the host's published detail, because
14
15
  * `ShopUninstallResult`'s failure variant has no code to populate and those
15
- * codes belong to the install gate. That difference is the `begin` parameter,
16
- * and it is the whole of it.
16
+ * codes belong to the install gate. That difference is the registry's
17
+ * `begin`, and it is the whole of it.
17
18
  *
18
19
  * The two used to be hand-synced copies — 71 of 84 lines identical. The cost
19
20
  * was not theoretical: a single review round had to make the same edit twice
20
21
  * (the settle signature, and its call), and the `pending` set below was added
21
22
  * to one of them and not the other before this module existed.
23
+ *
24
+ * Since 2026-10-16 the registry is its own module, framework-free, and the
25
+ * polling belongs to it: a component's effect stopped the moment the shop left
26
+ * the screen (design 2026-10-16-shop-sidebar-page §5). This module only
27
+ * renders one, through `useRegistryFlows`.
22
28
  */
23
- import type { ShopInstallStatusResult } from '../host/index.ts';
24
- import { type InstallView } from './present.ts';
29
+ import type { InstallArgs } from '../host/index.ts';
30
+ import { type FlowRegistry } from './flow-registry.ts';
31
+ import type { InstallView } from './present.ts';
25
32
  /** One identity's flow, as the tab hands it to a panel. */
26
33
  export interface KeyedFlow<TArgs> {
27
34
  view: InstallView;
@@ -39,16 +46,13 @@ export interface UseKeyedFlows<TArgs> {
39
46
  * moves. The accessor's identity still tracks `views`; the result's does
40
47
  * not, and the result is what a card should be given. */
41
48
  flowFor: (key: string) => KeyedFlow<TArgs>;
42
- /** Return one identity to `idle` without building a flow for it. Stable
43
- * across renders (unlike `flowFor`, whose identity tracks `views`), so one
44
- * registry's settle callback can supersede the other's receipt for the same
45
- * key without taking a dependency on that registry's output. */
46
- resetFlow: (key: string) => void;
47
49
  /** Return every identity whose view `predicate` selects to `idle`, and
48
- * nothing else. Stable across renders like `resetFlow`; a call selecting
50
+ * nothing else. Stable across renders, unlike `flowFor`; a call selecting
49
51
  * nothing leaves the registry — and so every flow and `pending` — exactly
50
- * as it was. The tab clears the receipts dsh restarting under it settled
51
- * with this (design §8, 2026-10-06 amendment). */
52
+ * as it was. The receipts dsh restarting under the page settled are
53
+ * cleared the same way at the store, through each registry's own
54
+ * `resetWhere` (page-store.ts `noteRestarted`; design §8, 2026-10-06
55
+ * amendment). */
52
56
  resetWhere: (predicate: (view: InstallView) => boolean) => void;
53
57
  /**
54
58
  * The identities whose flow is not `idle` — asked by the shelf, which has
@@ -65,16 +69,20 @@ export interface UseKeyedFlows<TArgs> {
65
69
  */
66
70
  pending: ReadonlySet<string>;
67
71
  }
68
- /**
69
- * @param begin the starting RPC and its answer, mapped to the first view.
70
- * Never rejects: a transport throw is the caller's to fold into a view,
71
- * because only the caller knows which localized line stands in for a detail
72
- * too private to render.
73
- * @param onSettled called once per identity that reaches a terminal state,
74
- * with WHICH terminal state. The outcome is load-bearing: a failed flow
75
- * changed nothing on the server, so a caller acting on "settled" alone
76
- * discards outcomes that are still true.
77
- */
78
- export declare function useKeyedFlows<TArgs>(begin: (args: TArgs) => Promise<InstallView>, installStatus: (args: {
79
- installId: string;
80
- }) => Promise<ShopInstallStatusResult>, onSettled?: (key: string, outcome: 'done' | 'failed') => void): UseKeyedFlows<TArgs>;
72
+ /** One entry's install flow, as the tab hands it to a panel: the page
73
+ * store's install registry (page-store.ts), begun through flow-registry.ts's
74
+ * `beginInstall`. */
75
+ export type InstallFlow = KeyedFlow<InstallArgs>;
76
+ /** One entry's uninstall flow, as the tab hands it to a panel: the page
77
+ * store's uninstall registry, begun through `beginUninstall`. */
78
+ export type UninstallFlow = KeyedFlow<{
79
+ name: string;
80
+ }>;
81
+ /** The uninstall flows as the tab hands them to the shadowed rows. */
82
+ export type UseUninstallFlows = UseKeyedFlows<{
83
+ name: string;
84
+ }>;
85
+ /** The keyed-flow face over any registry: one of the page store's, which
86
+ * outlives the component, or one of the store a tab builds for itself when
87
+ * none is handed over (ShopTab). */
88
+ export declare function useRegistryFlows<TArgs>(registry: FlowRegistry<TArgs>): UseKeyedFlows<TArgs>;
@@ -1,25 +1,28 @@
1
- /** Self-update driving hook for the shop itself: start, poll to terminal.
2
- * Shares the install view machine and poll loop; the start mapping is the
3
- * uninstall one — a business refusal (a non-semver version) lands in the
4
- * `failed` view with the host's detail, never in the install `rejected`
5
- * codes. */
6
- import type { ShopInstallStatusResult, ShopUpdateResult } from '../host/index.ts';
7
- import { type InstallView } from './present.ts';
1
+ /** The shop's own update as the tab renders it: the face over the registry
2
+ * that holds it, a page store's `self` (page-store.ts). It shares the install
3
+ * view machine and the registry's polling; its start mapping is
4
+ * flow-registry.ts's `beginUpdate`, which shares the uninstall's refusal
5
+ * rule — a business refusal (a non-semver version) lands in the `failed`
6
+ * view with the host's detail, never in the install `rejected` codes. */
7
+ import type { FlowRegistry } from './flow-registry.ts';
8
+ import type { InstallView } from './present.ts';
8
9
  export interface UseUpdateSelfResult {
9
10
  view: InstallView;
10
11
  start: (args: {
11
12
  version: string;
12
13
  }) => Promise<void>;
13
14
  /** Return the view to `idle` when `predicate` selects it; stable across
14
- * renders. The keyed registries' `resetWhere`, for the one self-update:
15
- * dsh restarting under the page settles an update that was waiting for
16
- * exactly that (design §8, 2026-10-06 amendment). */
15
+ * renders. The failed panel's Dismiss uses it, so the version row decides
16
+ * again; an update waiting for a restart is settled at the store instead,
17
+ * when dsh restarts under the page (page-store.ts `noteRestarted`; design
18
+ * §8, 2026-10-06 amendment). */
17
19
  resetWhere: (predicate: (view: InstallView) => boolean) => void;
18
20
  }
19
- /** Drive one self-update: the host's business union, the shared polling
20
- * loop, and terminal states. The `rejected` install state never occurs. */
21
- export declare function useUpdateSelf(updateStart: (args: {
21
+ /** The one key the self-update registry holds: the shop has one update. */
22
+ export declare const SELF_UPDATE_KEY = "self";
23
+ /** The self-update face over a registry: the keyed-flow face
24
+ * (`useRegistryFlows`) at its one key, so the subscription and the flow's
25
+ * identity are that hook's and nothing here can drift from it. */
26
+ export declare function useRegistrySelf(registry: FlowRegistry<{
22
27
  version: string;
23
- }) => Promise<ShopUpdateResult>, installStatus: (args: {
24
- installId: string;
25
- }) => Promise<ShopInstallStatusResult>): UseUpdateSelfResult;
28
+ }>): UseUpdateSelfResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-shop",
3
- "version": "0.8.7",
3
+ "version": "0.9.0-beta.1",
4
4
  "description": "The DeepSeek Harness plugin shop: browse, install, enable, and update dsh plugins from a git-auditable catalog.",
5
5
  "icon": "./assets/icon.png",
6
6
  "keywords": [
@@ -1,10 +0,0 @@
1
- /**
2
- * The shop's Settings section (design §5.1, amended 2026-10-15): its own
3
- * entry in the Settings nav, directly after Built-in plugins, where it used
4
- * to be a tab inside that section. The page carries the title and the one
5
- * intro line the built-in Models and Built-in plugins pages carry, above the
6
- * shop body, which is `ShopTab` unchanged.
7
- */
8
- import type { ReactNode } from 'react';
9
- import { type ShopTabProps } from './ShopTab.tsx';
10
- export declare function ShopSection(props: ShopTabProps): ReactNode;
@@ -1,31 +0,0 @@
1
- /** Install driving hooks: the shared poll loop, and the tab's keyed registry. */
2
- import { type Dispatch, type SetStateAction } from 'react';
3
- import { type InstallView } from './present.ts';
4
- import { type KeyedFlow, type UseKeyedFlows } from './useFlows.ts';
5
- import type { InstallArgs, ShopInstallResult, ShopInstallStatusResult } from '../host/index.ts';
6
- /** The single-view poll loop: while the view is `running`, poll once per
7
- * second and fold each status through the reducer. A poll failure is
8
- * transient — the host retains the record, so the next tick finds it. The
9
- * rejection handler must be present: an unhandled rejection here would
10
- * escape the poll loop.
11
- *
12
- * Kept for the self-update (`useUpdateSelf`), which drives ONE flow with no
13
- * identity to key it by. Everything per-plugin goes through `useKeyedFlows`,
14
- * whose own loop polls every running identity from one interval. */
15
- export declare function usePollStatus(view: InstallView, setView: Dispatch<SetStateAction<InstallView>>, installStatus: (args: {
16
- installId: string;
17
- }) => Promise<ShopInstallStatusResult>): void;
18
- /** One entry's install flow, as the tab hands it to a panel. */
19
- export type InstallFlow = KeyedFlow<InstallArgs>;
20
- export type UseInstallFlows = UseKeyedFlows<InstallArgs>;
21
- /**
22
- * Install flows owned by the tab and keyed by install identity. Keeping the
23
- * state above individual cards preserves a running operation when filtering
24
- * unmounts its card, and makes the shelf and Outdated panels agree.
25
- *
26
- * The registry itself is `useKeyedFlows`; this supplies the one thing that is
27
- * install-specific — what the starting RPC's answer means.
28
- */
29
- export declare function useInstallFlows(install: (args: InstallArgs) => Promise<ShopInstallResult>, installStatus: (args: {
30
- installId: string;
31
- }) => Promise<ShopInstallStatusResult>, onSettled?: (key: string, outcome: 'done' | 'failed') => void): UseInstallFlows;
@@ -1,28 +0,0 @@
1
- /** Uninstall driving hook: the tab's keyed registry, one start mapping. */
2
- import type { ShopInstallStatusResult, ShopUninstallResult } from '../host/index.ts';
3
- import { type KeyedFlow, type UseKeyedFlows } from './useFlows.ts';
4
- /** One entry's uninstall flow, as the tab hands it to a panel. */
5
- export type UninstallFlow = KeyedFlow<{
6
- name: string;
7
- }>;
8
- export type UseUninstallFlows = UseKeyedFlows<{
9
- name: string;
10
- }>;
11
- /**
12
- * Uninstall flows owned by the tab and keyed by install identity. Lifting the
13
- * state above individual cards is what lets a completed uninstall's outcome
14
- * survive the installed-projection refresh that follows it — the row backing
15
- * the card disappears, but the flow keyed by identity does not.
16
- *
17
- * The registry itself is `useKeyedFlows`; this supplies the one thing that is
18
- * uninstall-specific — what the starting RPC's answer means. An uninstall
19
- * business failure (not in the catalog / not installed) lands in the `failed`
20
- * view with the host's published detail, never in the install `rejected`
21
- * codes: those belong to the install gate, and `ShopUninstallResult`'s
22
- * failure variant has no `code` field to populate.
23
- */
24
- export declare function useUninstallFlows(uninstall: (args: {
25
- name: string;
26
- }) => Promise<ShopUninstallResult>, installStatus: (args: {
27
- installId: string;
28
- }) => Promise<ShopInstallStatusResult>, onSettled?: (key: string, outcome: 'done' | 'failed') => void): UseUninstallFlows;