@stigmer/react 3.4.1 → 3.5.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.
Files changed (99) hide show
  1. package/agent/AgentDetailView.d.ts +14 -4
  2. package/agent/AgentDetailView.d.ts.map +1 -1
  3. package/agent/AgentDetailView.js +73 -11
  4. package/agent/AgentDetailView.js.map +1 -1
  5. package/agent/internal/agentToInput.d.ts +3 -0
  6. package/agent/internal/agentToInput.d.ts.map +1 -1
  7. package/agent/internal/agentToInput.js +21 -1
  8. package/agent/internal/agentToInput.js.map +1 -1
  9. package/api-key/ApiKeyListPanel.d.ts +8 -1
  10. package/api-key/ApiKeyListPanel.d.ts.map +1 -1
  11. package/api-key/ApiKeyListPanel.js +9 -7
  12. package/api-key/ApiKeyListPanel.js.map +1 -1
  13. package/dependency-graph/DependencyTreeNode.d.ts.map +1 -1
  14. package/dependency-graph/DependencyTreeNode.js +8 -0
  15. package/dependency-graph/DependencyTreeNode.js.map +1 -1
  16. package/dependency-graph/types.d.ts +20 -8
  17. package/dependency-graph/types.d.ts.map +1 -1
  18. package/dependency-graph/useDependencyGraph.d.ts.map +1 -1
  19. package/dependency-graph/useDependencyGraph.js +21 -1
  20. package/dependency-graph/useDependencyGraph.js.map +1 -1
  21. package/index.d.ts +2 -0
  22. package/index.d.ts.map +1 -1
  23. package/index.js +4 -0
  24. package/index.js.map +1 -1
  25. package/internal/scroll-area.d.ts +4 -0
  26. package/internal/scroll-area.d.ts.map +1 -0
  27. package/internal/scroll-area.js +15 -0
  28. package/internal/scroll-area.js.map +1 -0
  29. package/internal/tooltip.d.ts +7 -0
  30. package/internal/tooltip.d.ts.map +1 -0
  31. package/internal/tooltip.js +31 -0
  32. package/internal/tooltip.js.map +1 -0
  33. package/mcp-server/StdioSandboxNotice.d.ts +12 -11
  34. package/mcp-server/StdioSandboxNotice.d.ts.map +1 -1
  35. package/mcp-server/StdioSandboxNotice.js +13 -12
  36. package/mcp-server/StdioSandboxNotice.js.map +1 -1
  37. package/mcp-server/steps/IdentityTransportStep.d.ts.map +1 -1
  38. package/mcp-server/steps/IdentityTransportStep.js +1 -1
  39. package/mcp-server/steps/IdentityTransportStep.js.map +1 -1
  40. package/package.json +4 -4
  41. package/resource-creation/templates/mcp-server-templates.d.ts.map +1 -1
  42. package/resource-creation/templates/mcp-server-templates.js +17 -14
  43. package/resource-creation/templates/mcp-server-templates.js.map +1 -1
  44. package/session/useSessionExecutions.d.ts +14 -0
  45. package/session/useSessionExecutions.d.ts.map +1 -1
  46. package/session/useSessionExecutions.js +27 -1
  47. package/session/useSessionExecutions.js.map +1 -1
  48. package/sidebar/SettingsSidebar.d.ts +62 -0
  49. package/sidebar/SettingsSidebar.d.ts.map +1 -0
  50. package/sidebar/SettingsSidebar.js +60 -0
  51. package/sidebar/SettingsSidebar.js.map +1 -0
  52. package/sidebar/WorkspaceSidebar.d.ts +87 -0
  53. package/sidebar/WorkspaceSidebar.d.ts.map +1 -0
  54. package/sidebar/WorkspaceSidebar.js +95 -0
  55. package/sidebar/WorkspaceSidebar.js.map +1 -0
  56. package/sidebar/chrome.d.ts +48 -0
  57. package/sidebar/chrome.d.ts.map +1 -0
  58. package/sidebar/chrome.js +32 -0
  59. package/sidebar/chrome.js.map +1 -0
  60. package/sidebar/index.d.ts +6 -0
  61. package/sidebar/index.d.ts.map +1 -0
  62. package/sidebar/index.js +9 -0
  63. package/sidebar/index.js.map +1 -0
  64. package/sidebar/types.d.ts +54 -0
  65. package/sidebar/types.d.ts.map +1 -0
  66. package/sidebar/types.js +2 -0
  67. package/sidebar/types.js.map +1 -0
  68. package/src/agent/AgentDetailView.tsx +198 -11
  69. package/src/agent/__tests__/AgentDetailView.datastores.test.tsx +166 -0
  70. package/src/agent/internal/__tests__/agentToInput.test.ts +154 -0
  71. package/src/agent/internal/agentToInput.ts +25 -7
  72. package/src/api-key/ApiKeyListPanel.tsx +17 -4
  73. package/src/dependency-graph/DependencyTreeNode.tsx +8 -0
  74. package/src/dependency-graph/__tests__/useDependencyGraph.test.ts +108 -0
  75. package/src/dependency-graph/types.ts +20 -8
  76. package/src/dependency-graph/useDependencyGraph.ts +23 -1
  77. package/src/index.ts +13 -0
  78. package/src/internal/scroll-area.tsx +37 -0
  79. package/src/internal/tooltip.tsx +66 -0
  80. package/src/mcp-server/StdioSandboxNotice.tsx +16 -14
  81. package/src/mcp-server/__tests__/StdioSandboxNotice.test.tsx +3 -2
  82. package/src/mcp-server/steps/IdentityTransportStep.tsx +7 -0
  83. package/src/resource-creation/templates/mcp-server-templates.ts +17 -14
  84. package/src/session/__tests__/useSessionExecutions.test.ts +63 -0
  85. package/src/session/useSessionExecutions.ts +27 -1
  86. package/src/sidebar/SettingsSidebar.tsx +173 -0
  87. package/src/sidebar/WorkspaceSidebar.tsx +386 -0
  88. package/src/sidebar/__tests__/SettingsSidebar.test.tsx +107 -0
  89. package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +236 -0
  90. package/src/sidebar/chrome.tsx +129 -0
  91. package/src/sidebar/index.ts +16 -0
  92. package/src/sidebar/types.ts +55 -0
  93. package/src/test/__tests__/samples.test.ts +2 -0
  94. package/src/test/samples.ts +48 -0
  95. package/styles.css +1 -1
  96. package/test/samples.d.ts +21 -0
  97. package/test/samples.d.ts.map +1 -1
  98. package/test/samples.js +32 -0
  99. package/test/samples.js.map +1 -1
@@ -0,0 +1,236 @@
1
+ import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
2
+ import { render, screen, cleanup, within } from "@testing-library/react";
3
+ import type { ReactNode } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import { createRouterTransport } from "@connectrpc/connect";
6
+ import { Stigmer } from "@stigmer/sdk";
7
+ import { OrganizationSchema } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
8
+ import { OrganizationQueryController } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/query_pb";
9
+ import { OrganizationsSchema } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/io_pb";
10
+ import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
11
+ import { StigmerContext } from "../../context";
12
+ import { OrgProvider } from "../../organization/OrgProvider";
13
+ import { WorkspaceSidebar } from "../WorkspaceSidebar";
14
+ import type { RecentActivityEntry } from "../../activity/types";
15
+ import type { RenderSidebarLink } from "../types";
16
+
17
+ // Tooltips and the org switcher menu portal their content; without a
18
+ // StigmerProvider the portal container is null — pin it to document.body
19
+ // like sibling tests.
20
+ vi.mock("../../portal-container", () => ({
21
+ useStigmerPortalContainer: () => document.body,
22
+ }));
23
+
24
+ beforeAll(() => {
25
+ // happy-dom lacks ResizeObserver, which Base UI positioners observe.
26
+ if (!("ResizeObserver" in globalThis)) {
27
+ (globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver =
28
+ class {
29
+ observe() {}
30
+ unobserve() {}
31
+ disconnect() {}
32
+ };
33
+ }
34
+ });
35
+
36
+ afterEach(cleanup);
37
+
38
+ /** Frozen reference instant — fixtures must never read the real clock. */
39
+ const NOW = new Date("2026-07-20T11:00:00Z");
40
+
41
+ const hoursAgo = (h: number) => new Date(NOW.getTime() - h * 3_600_000);
42
+
43
+ const ENTRIES: readonly RecentActivityEntry[] = [
44
+ {
45
+ id: "ses_today",
46
+ type: "session",
47
+ subject: "Draft email copy for the Q3 launch",
48
+ updatedAt: hoursAgo(2),
49
+ },
50
+ {
51
+ id: "wex_yesterday",
52
+ type: "workflow_execution",
53
+ subject: "Nightly refund sweep",
54
+ updatedAt: hoursAgo(26),
55
+ status: "failed",
56
+ },
57
+ ];
58
+
59
+ /**
60
+ * The default consumer seam under test: an anchor per row, tagged with the
61
+ * row id so assertions can address rows the way real consumers do.
62
+ */
63
+ const renderAnchor: RenderSidebarLink = ({
64
+ id,
65
+ href,
66
+ className,
67
+ children,
68
+ "aria-current": ariaCurrent,
69
+ }) => (
70
+ <a href={href} data-row-id={id} aria-current={ariaCurrent} className={className}>
71
+ {children}
72
+ </a>
73
+ );
74
+
75
+ function renderSidebar(ui: ReactNode) {
76
+ const client = new Stigmer({
77
+ baseUrl: "/",
78
+ getAccessToken: () => "test-token",
79
+ customTransport: createRouterTransport((router) => {
80
+ router.service(OrganizationQueryController, {
81
+ findMyOrganizations: () =>
82
+ create(OrganizationsSchema, {
83
+ entries: [
84
+ create(OrganizationSchema, {
85
+ metadata: create(ApiResourceMetadataSchema, {
86
+ id: "org_acme",
87
+ slug: "acme",
88
+ name: "Acme Corp",
89
+ }),
90
+ }),
91
+ ],
92
+ }),
93
+ });
94
+ }),
95
+ });
96
+ return render(
97
+ <StigmerContext.Provider value={client}>
98
+ <OrgProvider>{ui}</OrgProvider>
99
+ </StigmerContext.Provider>,
100
+ );
101
+ }
102
+
103
+ function baseProps() {
104
+ return {
105
+ renderLink: renderAnchor,
106
+ recentActivity: { entries: ENTRIES },
107
+ footer: <span data-testid="footer-slot">footer</span>,
108
+ now: NOW,
109
+ };
110
+ }
111
+
112
+ describe("WorkspaceSidebar — primary navigation", () => {
113
+ it("renders the three primary rows through renderLink with stable ids and hrefs", () => {
114
+ const { container } = renderSidebar(<WorkspaceSidebar {...baseProps()} />);
115
+
116
+ const row = (id: string) =>
117
+ container.querySelector<HTMLAnchorElement>(`[data-row-id="${id}"]`);
118
+ expect(row("new-session")?.getAttribute("href")).toBe("/");
119
+ expect(row("dashboard")?.getAttribute("href")).toBe("/dashboard");
120
+ expect(row("library")?.getAttribute("href")).toBe("/library");
121
+ });
122
+
123
+ it("marks only the active row with aria-current and the accent classes", () => {
124
+ const { container } = renderSidebar(
125
+ <WorkspaceSidebar {...baseProps()} activeNav="library" />,
126
+ );
127
+
128
+ const library = container.querySelector('[data-row-id="library"]')!;
129
+ expect(library.getAttribute("aria-current")).toBe("page");
130
+ expect(library.className.split(" ")).toContain("bg-sidebar-accent");
131
+
132
+ const dashboard = container.querySelector('[data-row-id="dashboard"]')!;
133
+ expect(dashboard.getAttribute("aria-current")).toBeNull();
134
+ // (hover:bg-sidebar-accent is fine — only the resting accent means active)
135
+ expect(dashboard.className.split(" ")).not.toContain("bg-sidebar-accent");
136
+ });
137
+ });
138
+
139
+ describe("WorkspaceSidebar — recents", () => {
140
+ it("buckets entries against the frozen `now`, never the live clock", () => {
141
+ renderSidebar(<WorkspaceSidebar {...baseProps()} />);
142
+
143
+ // 2h ago is Today; 26h ago is Yesterday — only under the frozen NOW.
144
+ expect(screen.getByText("Today")).toBeTruthy();
145
+ expect(screen.getByText("Yesterday")).toBeTruthy();
146
+ expect(screen.getByText("2h")).toBeTruthy();
147
+ });
148
+
149
+ it("passes the entry to renderLink and derives the viewer route from its type", () => {
150
+ const seen: Record<string, string> = {};
151
+ const spyLink: RenderSidebarLink = (props) => {
152
+ if (props.entry) seen[props.entry.id] = props.href;
153
+ return renderAnchor(props);
154
+ };
155
+
156
+ renderSidebar(<WorkspaceSidebar {...baseProps()} renderLink={spyLink} />);
157
+
158
+ expect(seen).toEqual({
159
+ ses_today: "/sessions/ses_today",
160
+ wex_yesterday: "/executions/wex_yesterday",
161
+ });
162
+ });
163
+
164
+ it("highlights the active session row and renders the status badge", () => {
165
+ const { container } = renderSidebar(
166
+ <WorkspaceSidebar {...baseProps()} activeSessionId="ses_today" />,
167
+ );
168
+
169
+ const active = container.querySelector('[data-row-id="ses_today"]')!;
170
+ expect(active.className).toContain("bg-sidebar-accent");
171
+ expect(active.getAttribute("aria-current")).toBe("page");
172
+ // The failed execution row explains why it is in the list.
173
+ const failed = container.querySelector('[data-row-id="wex_yesterday"]')!;
174
+ expect(within(failed as HTMLElement).getByText("failed")).toBeTruthy();
175
+ });
176
+
177
+ it("renders the per-entry accessory inside its row", () => {
178
+ const { container } = renderSidebar(
179
+ <WorkspaceSidebar
180
+ {...baseProps()}
181
+ renderEntryAccessory={(entry) =>
182
+ entry.id === "ses_today" ? <span data-testid="run-dot" /> : null
183
+ }
184
+ />,
185
+ );
186
+
187
+ const row = container.querySelector('[data-row-id="ses_today"]')!;
188
+ expect(within(row as HTMLElement).getByTestId("run-dot")).toBeTruthy();
189
+ });
190
+ });
191
+
192
+ describe("WorkspaceSidebar — fetch states", () => {
193
+ it("shows skeletons while loading", () => {
194
+ renderSidebar(
195
+ <WorkspaceSidebar
196
+ {...baseProps()}
197
+ recentActivity={{ entries: [], isLoading: true }}
198
+ />,
199
+ );
200
+ expect(screen.getByLabelText("Loading sessions")).toBeTruthy();
201
+ });
202
+
203
+ it("shows the error alert plus the empty state on failure", () => {
204
+ renderSidebar(
205
+ <WorkspaceSidebar
206
+ {...baseProps()}
207
+ recentActivity={{ entries: [], error: { message: "backend down" } }}
208
+ />,
209
+ );
210
+ expect(screen.getByRole("alert").textContent).toBe("backend down");
211
+ expect(screen.getByText("No recent activity")).toBeTruthy();
212
+ });
213
+
214
+ it("shows the empty state when there are no entries", () => {
215
+ renderSidebar(
216
+ <WorkspaceSidebar {...baseProps()} recentActivity={{ entries: [] }} />,
217
+ );
218
+ expect(screen.getByText("No recent activity")).toBeTruthy();
219
+ });
220
+ });
221
+
222
+ describe("WorkspaceSidebar — chrome", () => {
223
+ it("renders the footer slot and the collapse toggle with aria state", () => {
224
+ renderSidebar(<WorkspaceSidebar {...baseProps()} isOpen={false} />);
225
+
226
+ expect(screen.getByTestId("footer-slot")).toBeTruthy();
227
+ const toggle = screen.getByLabelText("Collapse sidebar");
228
+ expect(toggle.getAttribute("aria-expanded")).toBe("false");
229
+ expect(toggle.getAttribute("aria-controls")).toBe("sidebar");
230
+ });
231
+
232
+ it("resolves the org switcher from the mocked transport", async () => {
233
+ renderSidebar(<WorkspaceSidebar {...baseProps()} />);
234
+ expect(await screen.findByText("Acme Corp")).toBeTruthy();
235
+ });
236
+ });
@@ -0,0 +1,129 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+ import { PanelLeft } from "lucide-react";
5
+ import { cn } from "@stigmer/theme";
6
+ import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
7
+ import { OrgSwitcher } from "../organization/OrgSwitcher.js";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Shared sidebar chrome — the pieces WorkspaceSidebar and SettingsSidebar
11
+ // have in common: the nav container, the top row (collapse toggle + org
12
+ // switcher), the hairline separator, the footer band, and the one nav-row
13
+ // class recipe. Internal to the sidebar domain; not exported from the SDK.
14
+ //
15
+ // Every class here is transcribed from the console's sidebars — this file
16
+ // IS the console's chrome now, so a change here changes the console, the
17
+ // desktop app, and every documentation tour in one place.
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /** Props shared by both sidebar variants' outer chrome. */
21
+ export interface SidebarChromeProps {
22
+ /** Accessible name for the `<nav>` landmark. */
23
+ readonly ariaLabel: string;
24
+ /**
25
+ * Whether the sidebar is currently expanded, reflected as
26
+ * `aria-expanded` on the collapse toggle. @default true
27
+ */
28
+ readonly isOpen?: boolean;
29
+ /**
30
+ * Called when the collapse toggle is pressed. The sidebar renders the
31
+ * toggle either way (it is part of the depicted chrome); hosts that
32
+ * cannot collapse (fixed embeds) simply omit the handler.
33
+ */
34
+ readonly onCollapse?: () => void;
35
+ /** Passed through to {@link OrgSwitcher}. */
36
+ readonly onOrgChanged?: (org: Organization) => void;
37
+ /**
38
+ * Footer content — typically the host app's `UserMenu` wrapper, which
39
+ * bridges auth, theming, and routing. A slot rather than a built-in
40
+ * because every host wires those systems differently (DD-004).
41
+ */
42
+ readonly footer: ReactNode;
43
+ readonly children: ReactNode;
44
+ }
45
+
46
+ /**
47
+ * Outer sidebar frame: `<nav>` container, top row, content, footer band.
48
+ *
49
+ * The `id="sidebar"` / `aria-controls` pairing matches the console's DOM
50
+ * contract (the app shell's floating "Open sidebar" button targets it);
51
+ * only one sidebar renders at a time, so the id cannot collide.
52
+ */
53
+ export function SidebarChrome({
54
+ ariaLabel,
55
+ isOpen = true,
56
+ onCollapse,
57
+ onOrgChanged,
58
+ footer,
59
+ children,
60
+ }: SidebarChromeProps) {
61
+ return (
62
+ <nav
63
+ id="sidebar"
64
+ aria-label={ariaLabel}
65
+ className="bg-sidebar text-sidebar-foreground flex h-full flex-col"
66
+ >
67
+ {/* Top row: collapse toggle + org context */}
68
+ <div className="flex flex-none items-center gap-1 px-2 py-2">
69
+ <button
70
+ type="button"
71
+ onClick={onCollapse}
72
+ aria-expanded={isOpen}
73
+ aria-controls="sidebar"
74
+ aria-label="Collapse sidebar"
75
+ className={cn(
76
+ "inline-flex size-7 shrink-0 items-center justify-center rounded-md",
77
+ "text-sidebar-foreground transition-colors motion-reduce:transition-none",
78
+ "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
79
+ "aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground",
80
+ "focus-visible:ring-sidebar-ring focus-visible:ring-2 focus-visible:outline-none",
81
+ )}
82
+ >
83
+ <PanelLeft className="size-4" />
84
+ </button>
85
+ <div className="min-w-0 flex-1">
86
+ <OrgSwitcher onOrgChanged={onOrgChanged} />
87
+ </div>
88
+ </div>
89
+
90
+ {children}
91
+
92
+ {/* Bottom: user menu */}
93
+ <div className="border-sidebar-border flex-none border-t px-3 py-2">
94
+ {footer}
95
+ </div>
96
+ </nav>
97
+ );
98
+ }
99
+
100
+ /** Hairline separator between sidebar sections. */
101
+ export function SidebarSeparator() {
102
+ return (
103
+ <div className="px-3 py-1">
104
+ <div className="bg-sidebar-border h-px" />
105
+ </div>
106
+ );
107
+ }
108
+
109
+ /**
110
+ * The one nav-row class recipe both sidebars share: 14px medium labels,
111
+ * 16px icons (sized by callers), 8px/6px padding, rounded row highlight.
112
+ *
113
+ * `muted` renders the resting label in the muted sidebar tone — used by
114
+ * "Back to Sessions", which is an exit, not a destination.
115
+ */
116
+ export function navRowClassName(
117
+ active: boolean,
118
+ { muted = false }: { muted?: boolean } = {},
119
+ ): string {
120
+ return cn(
121
+ "flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm font-medium transition-colors",
122
+ active
123
+ ? "bg-sidebar-accent text-sidebar-accent-foreground"
124
+ : cn(
125
+ muted ? "text-sidebar-muted-foreground" : "text-sidebar-foreground",
126
+ "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
127
+ ),
128
+ );
129
+ }
@@ -0,0 +1,16 @@
1
+ // Sidebar — the console's navigation chrome as shared SDK components.
2
+ //
3
+ // One source of truth for the sidebar the web console, desktop app, and
4
+ // documentation tours all render (stigmer/stigmer#317): hosts inject their
5
+ // router via `renderLink`, their data via props, and their user menu via
6
+ // the `footer` slot.
7
+
8
+ export { WorkspaceSidebar } from "./WorkspaceSidebar.js";
9
+ export type {
10
+ WorkspaceSidebarProps,
11
+ WorkspaceSidebarActivity,
12
+ WorkspaceNavId,
13
+ } from "./WorkspaceSidebar.js";
14
+ export { SettingsSidebar } from "./SettingsSidebar.js";
15
+ export type { SettingsSidebarProps } from "./SettingsSidebar.js";
16
+ export type { SidebarLinkRenderProps, RenderSidebarLink } from "./types.js";
@@ -0,0 +1,55 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import type { RecentActivityEntry } from "../activity/types.js";
3
+
4
+ /**
5
+ * Everything a sidebar hands to {@link RenderSidebarLink} to draw one
6
+ * interactive row.
7
+ *
8
+ * The sidebar owns the row's look (`className`, `children`) and state
9
+ * (`active`, `aria-current`); the consumer owns only the interactive
10
+ * element wrapping them. That split is what keeps one component serving
11
+ * three very different hosts: the web console returns `next/link`, the
12
+ * desktop app returns a `react-router` link, and documentation embeds
13
+ * return inert rows decorated with capture markers.
14
+ */
15
+ export interface SidebarLinkRenderProps {
16
+ /**
17
+ * Stable row identifier, for consumers that vary behavior per row:
18
+ * `"new-session"` / `"dashboard"` / `"library"` in the workspace
19
+ * sidebar, `"back-to-sessions"` or the settings route basename (e.g.
20
+ * `"api-keys"`) in the settings sidebar, and the entry id for recent
21
+ * activity rows.
22
+ */
23
+ readonly id: string;
24
+ /** Console route this row targets (e.g. `/library`, `/sessions/<id>`). */
25
+ readonly href: string;
26
+ /** Whether the row is the current location. */
27
+ readonly active: boolean;
28
+ /**
29
+ * Fully-styled row classes computed by the sidebar. Apply verbatim —
30
+ * the row's geometry and type scale are the product surface tours and
31
+ * embeds depict, so consumers must not restyle them.
32
+ */
33
+ readonly className: string;
34
+ /** `"page"` when active, for assistive tech. Spread onto the element. */
35
+ readonly "aria-current": "page" | undefined;
36
+ /** Row content (icon, label, timestamps). Render inside the element. */
37
+ readonly children: ReactNode;
38
+ /**
39
+ * The recent-activity entry behind this row. Present only for recents
40
+ * rows in the workspace sidebar — consumers use it to pick the right
41
+ * navigation flow (session viewer vs. execution viewer) without
42
+ * parsing `href`.
43
+ */
44
+ readonly entry?: RecentActivityEntry;
45
+ }
46
+
47
+ /**
48
+ * Renders one sidebar row as the consumer's interactive element.
49
+ *
50
+ * Must return a single element that spreads unknown props onto its
51
+ * underlying DOM node (all DOM elements, `next/link`, and `react-router`
52
+ * links qualify): the sidebar composes tooltip triggers around recents
53
+ * rows by cloning the returned element with merged event handlers.
54
+ */
55
+ export type RenderSidebarLink = (props: SidebarLinkRenderProps) => ReactElement;
@@ -198,6 +198,8 @@ describe("samples determinism", () => {
198
198
  environment: () => samples.environment(),
199
199
  agentInstance: () => samples.agentInstance(),
200
200
  apiKey: () => samples.apiKey(),
201
+ organization: () => samples.organization(),
202
+ organizationList: () => samples.organizationList(),
201
203
  humanMessage: () => samples.humanMessage("hello"),
202
204
  aiMessage: () => samples.aiMessage("hi"),
203
205
  toolCall: () => samples.toolCall("lookup_order", '{"orderId":"123"}'),
@@ -67,6 +67,15 @@ import {
67
67
  import { SessionSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/spec_pb";
68
68
  import { SkillSchema, type Skill } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
69
69
  import { SkillSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/spec_pb";
70
+ import {
71
+ OrganizationSchema,
72
+ type Organization,
73
+ } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
74
+ import {
75
+ OrganizationsSchema,
76
+ type Organizations,
77
+ } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/io_pb";
78
+ import { OrganizationSpecSchema } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/spec_pb";
70
79
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
71
80
  import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
72
81
  import {
@@ -221,6 +230,14 @@ export interface ApiKeyOverrides {
221
230
  readonly createdAt?: string;
222
231
  }
223
232
 
233
+ export interface OrganizationOverrides {
234
+ readonly id?: string;
235
+ readonly name?: string;
236
+ readonly slug?: string;
237
+ /** Personal orgs render a person icon in the switcher; teams a building. */
238
+ readonly isPersonal?: boolean;
239
+ }
240
+
224
241
  export interface SearchResultOverrides {
225
242
  readonly id?: string;
226
243
  readonly name?: string;
@@ -457,6 +474,37 @@ export const samples = {
457
474
  });
458
475
  },
459
476
 
477
+ /**
478
+ * An organization resource with metadata and spec.
479
+ * Default: team org `Acme Corp` (slug `acme`) — the tour world's
480
+ * depicted organization, matching `DEMO_ORG` in the demos workspace.
481
+ */
482
+ organization(o?: OrganizationOverrides): Organization {
483
+ return create(OrganizationSchema, {
484
+ apiVersion: "tenancy.stigmer.ai/v1",
485
+ kind: "Organization",
486
+ metadata: create(ApiResourceMetadataSchema, {
487
+ id: o?.id ?? "org-00000000-0000-0000-0000-000000000001",
488
+ name: o?.name ?? "Acme Corp",
489
+ slug: o?.slug ?? "acme",
490
+ }),
491
+ spec: create(OrganizationSpecSchema, {
492
+ isPersonal: o?.isPersonal ?? false,
493
+ }),
494
+ });
495
+ },
496
+
497
+ /**
498
+ * An `Organizations` response for `findMyOrganizations` — what
499
+ * `OrgProvider` fetches to drive the org switcher.
500
+ * Default: a single {@link samples.organization}.
501
+ */
502
+ organizationList(entries?: Organization[]): Organizations {
503
+ return create(OrganizationsSchema, {
504
+ entries: entries ?? [samples.organization()],
505
+ });
506
+ },
507
+
460
508
  // ---- Message & artifact primitives ----
461
509
 
462
510
  /** A human (user) message, stamped at {@link SAMPLE_INSTANT}. */