dsh-graft-plugin 0.1.2 → 0.2.0

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/README.md CHANGED
@@ -9,7 +9,7 @@ It does four things:
9
9
  - **A chip in the composer** naming the graft index the current session is actually serving, whether it still matches the code, and how many tokens graft has saved in that repo.
10
10
  - **Five tools for the model** — `graft_ask`, `graft_grep`, `graft_callers`, `graft_skeleton`, `graft_map` — resolved per session, so one dsh can serve several workspaces at once.
11
11
  - **`/graft`** to build or rebuild the index, and **auto-rebuild** after the model edits files.
12
- - **A `graft viz` button** that opens the graph as a tab in [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar).
12
+ - **A `graft viz` button** that opens the graph in dsh's built-in right sidebar (0.1.5+), in [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) if you still run it, and in a browser tab as the last resort.
13
13
 
14
14
  ## Install
15
15
 
@@ -50,15 +50,17 @@ graft build
50
50
 
51
51
  ### The sidebar tab
52
52
 
53
- The `graft viz` button needs `dsh-better-sidebar` **mounted**. It ships as a dependency of this plugin (0.18.x), so it is already downloaded — but dsh only mounts what the profile lists:
53
+ On dsh `0.1.5` and later the button opens the graph in **dsh's own right sidebar** — a tab titled Graft, also listed on the sidebar's guide page as "The graft graph for this workspace" — and a second click focuses the tab that is already open. No install needed for that path: the registration is soft, so an older dsh without the right sidebar falls through to `dsh-better-sidebar` when it is mounted, and to a browser tab when neither is there.
54
+
55
+ If your profile still runs `dsh-better-sidebar`, that path is tried **first** and behaves exactly as before. It ships as a dependency of this plugin (0.18.x), so it is already downloaded — but dsh only mounts what the profile lists:
54
56
 
55
57
  ```sh
56
58
  dsh plugin --profile web add dsh-better-sidebar
57
59
  ```
58
60
 
59
- > **Compatibility.** `dsh-better-sidebar` 0.18.x requires `@deepseek-ai/dsh-client-ui-primitives`, which dsh removed in `0.1.5-rc.2`. On that version or later the tab will not mount and the button reports the sidebar as unavailable. Everything else in this plugin — chip, tools, `/graft`, auto-rebuild — is unaffected.
61
+ > **Compatibility.** `dsh-better-sidebar` 0.18.x requires `@deepseek-ai/dsh-client-ui-primitives`, which dsh removed in `0.1.5-rc.2`. On that version or later it will not mount — which is exactly the profiles where the built-in right sidebar takes over instead. Everything else in this plugin — chip, tools, `/graft`, auto-rebuild — is unaffected.
60
62
 
61
- Skip it if you do not want the tab. The chip, the tools, `/graft` and the auto-rebuild all work without it; the button just reports that the sidebar is unavailable.
63
+ The chip, the tools, `/graft` and the auto-rebuild all work without any sidebar; the button simply falls back to a browser tab when neither one is present.
62
64
 
63
65
  > This plugin does **not** insert a `dsh-better-sidebar` row itself, on purpose. That package's own bundle row carries a guard that disables it when another enabled row already mounts the same package. A second row with a different id makes that guard circular, and the losing outcome is *your existing sidebar silently disabling itself*. One explicit command is worth more than that risk.
64
66
 
@@ -82,7 +84,7 @@ The root is resolved from **the session's own workspace**, not from wherever dsh
82
84
 
83
85
  ### The visualiser
84
86
 
85
- The `graft viz` button opens the graph as a tab in [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar). The context view lays out the whole graph:
87
+ The `graft viz` button opens the graph as a tab — in dsh's built-in right sidebar on `0.1.5`+, in [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) if that is what you run. The context view lays out the whole graph:
86
88
 
87
89
  ![The graph in the sidebar — context view](assets/viz-context.png)
88
90
 
@@ -131,8 +133,8 @@ All optional, all on the plugin's row in your profile's `cordis.patch.yml`:
131
133
 
132
134
  | dsh | status |
133
135
  |---|---|
134
- | `0.1.2-rc.1` – `0.1.0-rc.7` | tested, full feature set |
135
- | `0.1.5-rc.2` | tested — chip, tools, `/graft` and auto-rebuild all work; the `graft viz` tab does not mount (see the sidebar note above) |
136
+ | `0.1.2-rc.1` – `0.1.0-rc.7` | tested, full feature set — the `graft viz` tab via `dsh-better-sidebar` |
137
+ | `0.1.5-rc.2` / `0.1.5` | tested — chip, tools, `/graft` and auto-rebuild all work; on `0.1.5` the `graft viz` button opens the graph in the built-in right sidebar |
136
138
 
137
139
  - Node 22+
138
140
  - the `graft` CLI on PATH (`npm install -g @nanonets/graft`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-graft-plugin",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Shows which graft index a dsh session is serving and whether it still matches the code, and gives the model graft_ask, graft_grep, graft_callers, graft_skeleton and graft_map as native tools.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -246,7 +246,15 @@ check(
246
246
  clientSource.includes('ctx.inject(["betterSidebar"]') && /const inject = \[[^\]]*\]/.exec(clientSource)?.[0].includes('betterSidebar') === false,
247
247
  /const inject = \[[^\]]*\]/.exec(clientSource)?.[0],
248
248
  )
249
- check('the browser tab survives only as the fallback', clientSource.includes('no dsh-better-sidebar'))
249
+ // The open order: better-sidebar if a profile still has it, then dsh's own
250
+ // right sidebar (0.1.5+), and a browser tab only when neither is there.
251
+ check('dsh right sidebar comes after better-sidebar', clientSource.indexOf('ctx.get("betterSidebar")') < clientSource.indexOf('ctx.get("sidebarRight")'))
252
+ check('it opens by kind, which also reveals the column', clientSource.includes('right.openTab(VIZ_KIND)'))
253
+ check('a kind nothing registered falls through, not dead', clientSource.includes('openTab throws for a kind nothing registered'))
254
+ check('the type and its body register under the same id', clientSource.includes('sidebarRightTabs.register({') && clientSource.includes('id: TAB_TYPE,') && clientSource.includes('name: "sidebar.right.pane.tab", key: TAB_TYPE'))
255
+ check('that dependency is soft too', clientSource.includes('ctx.inject(["sidebarRightTabs", "slots"]') && !/const inject = [[]/.test('') && clientSource.indexOf('const inject = ["slots", "remote"]') !== -1)
256
+ check('the body reads the session from either host', clientSource.includes('props?.sessionId ?? props?.scope?.sessionId'))
257
+ check('the browser tab survives only as the last resort', clientSource.includes('neither sidebar'))
250
258
 
251
259
  await rm(fixture, { recursive: true, force: true })
252
260
 
package/src/client.js CHANGED
@@ -324,9 +324,19 @@ window.__ModuleLoader__.load({
324
324
  ].join("");
325
325
  }
326
326
 
327
- /** The tab type contributed to dsh-better-sidebar. */
327
+ /** This viewer's identity: better-sidebar's tab type, and dsh's tab id. */
328
328
  const TAB_TYPE = "graft-status:viz";
329
329
 
330
+ /**
331
+ * The kind dsh's own right sidebar opens this page type by.
332
+ *
333
+ * dsh 0.1.5 splits a tab type in two: a static registration naming the kind
334
+ * (`ctx.sidebarRightTabs`), and the body under the registration's id in the
335
+ * keyed `sidebar.right.pane.tab` seat. `ctx.sidebarRight.openTab(kind)`
336
+ * then opens it AND expands a collapsed column in the same step.
337
+ */
338
+ const VIZ_KIND = "graft-viz";
339
+
330
340
  /**
331
341
  * The sidebar tab: `graft viz` in an iframe.
332
342
  *
@@ -370,10 +380,14 @@ window.__ModuleLoader__.load({
370
380
  const [status, setStatus] = React.useState(null);
371
381
  const [reload, setReload] = React.useState(0);
372
382
  const [busy, setBusy] = React.useState(false);
383
+ // Visible by default: better-sidebar passes `visible: false` for a tab
384
+ // that exists but is not focused (and asks live views to pause then);
385
+ // dsh's sidebar mounts only what it draws and passes nothing.
373
386
  const visible = props?.visible !== false;
374
- // The sidebar's own scope. Its `sessionId` is what tells the host WHICH
375
- // workspace to resolve — the same session whose cwd the sidebar shows.
376
- const sessionId = props?.scope?.sessionId;
387
+ // Which workspace to resolve. Both hosts supply the session, in their
388
+ // own shape: dsh passes `sessionId` straight to the body, while
389
+ // better-sidebar wraps it in the tab's `scope`.
390
+ const sessionId = props?.sessionId ?? props?.scope?.sessionId;
377
391
 
378
392
  React.useEffect(() => {
379
393
  if (!visible || viz !== null) return;
@@ -630,7 +644,25 @@ window.__ModuleLoader__.load({
630
644
  return;
631
645
  }
632
646
 
633
- // Fallback for a profile with no dsh-better-sidebar: a browser tab.
647
+ // Next: dsh's own right sidebar (0.1.5+). `openTab` names the KIND,
648
+ // reveals the column, and reuses the tab that is already open, so a
649
+ // second click focuses the graph rather than stacking another.
650
+ // The body starts the server itself on mount, so nothing is awaited
651
+ // here and no popup-blocker timing applies.
652
+ const right = ctx.get("sidebarRight");
653
+ if (right !== undefined && right !== null && typeof right.openTab === "function") {
654
+ try {
655
+ right.openTab(VIZ_KIND);
656
+ return;
657
+ } catch (error) {
658
+ // openTab throws for a kind nothing registered — which is the
659
+ // case when the tab registry was absent at load. Fall through to
660
+ // the browser tab rather than leaving the click dead.
661
+ console.warn("[graft-status] right sidebar refused the viz tab:", error);
662
+ }
663
+ }
664
+
665
+ // Last resort, for a profile with neither sidebar: a browser tab.
634
666
 
635
667
  // Opened SYNCHRONOUSLY, before any await. A tab opened from a
636
668
  // resolved promise is a popup as far as the browser is concerned and
@@ -812,6 +844,41 @@ window.__ModuleLoader__.load({
812
844
  // plugin — chip included — from activating in a profile that does not
813
845
  // have the sidebar installed. This way the tab appears when the service
814
846
  // does, and the button falls back to a browser tab when it never comes.
847
+ // dsh's own right sidebar (0.1.5+). Two registrations: the static type,
848
+ // and the body in the keyed seat under the SAME id. `ctx.inject` rather
849
+ // than the module-level list for the same reason as below — a profile
850
+ // without this sidebar must still get the chip and the button.
851
+ ctx.inject(["sidebarRightTabs", "slots"], (scope) => {
852
+ scope.effect(
853
+ () =>
854
+ scope.sidebarRightTabs.register({
855
+ id: TAB_TYPE,
856
+ kind: VIZ_KIND,
857
+ // A page type, opened by kind: it recognises no resource address,
858
+ // so it declares no patterns. "extension" is the band for a type
859
+ // from outside the product, and the default.
860
+ priority: "extension",
861
+ title: () => "Graft",
862
+ guide: [
863
+ {
864
+ order: 60,
865
+ title: () => "Graft",
866
+ description: () => "The graft graph for this workspace",
867
+ icon: GraphIcon,
868
+ },
869
+ ],
870
+ }),
871
+ "graft-status: right sidebar tab type",
872
+ );
873
+ scope.effect(
874
+ () =>
875
+ scope.slots.inject("sidebar.right.pane.tab", () =>
876
+ scope.slots.register({ name: "sidebar.right.pane.tab", key: TAB_TYPE }, makeVizTab(ctx)),
877
+ ),
878
+ "graft-status: right sidebar tab body",
879
+ );
880
+ });
881
+
815
882
  ctx.inject(["betterSidebar"], (scope) => {
816
883
  scope.effect(
817
884
  () =>