@stigmer/react 3.4.0 → 3.5.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/agent/AgentDetailView.d.ts +14 -4
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +73 -11
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/internal/agentToInput.d.ts +3 -0
- package/agent/internal/agentToInput.d.ts.map +1 -1
- package/agent/internal/agentToInput.js +21 -1
- package/agent/internal/agentToInput.js.map +1 -1
- package/agent/useAgentCount.d.ts +2 -0
- package/agent/useAgentCount.d.ts.map +1 -1
- package/agent/useAgentCount.js.map +1 -1
- package/api-key/ApiKeyListPanel.d.ts +8 -1
- package/api-key/ApiKeyListPanel.d.ts.map +1 -1
- package/api-key/ApiKeyListPanel.js +9 -7
- package/api-key/ApiKeyListPanel.js.map +1 -1
- package/api-key/CreateApiKeyForm.d.ts +7 -1
- package/api-key/CreateApiKeyForm.d.ts.map +1 -1
- package/api-key/CreateApiKeyForm.js +2 -2
- package/api-key/CreateApiKeyForm.js.map +1 -1
- package/datastore/useDatastoreCount.d.ts +2 -0
- package/datastore/useDatastoreCount.d.ts.map +1 -1
- package/datastore/useDatastoreCount.js.map +1 -1
- package/dependency-graph/DependencyTreeNode.d.ts.map +1 -1
- package/dependency-graph/DependencyTreeNode.js +8 -0
- package/dependency-graph/DependencyTreeNode.js.map +1 -1
- package/dependency-graph/types.d.ts +20 -8
- package/dependency-graph/types.d.ts.map +1 -1
- package/dependency-graph/useDependencyGraph.d.ts.map +1 -1
- package/dependency-graph/useDependencyGraph.js +21 -1
- package/dependency-graph/useDependencyGraph.js.map +1 -1
- package/index.d.ts +6 -4
- package/index.d.ts.map +1 -1
- package/index.js +6 -2
- package/index.js.map +1 -1
- package/internal/scroll-area.d.ts +4 -0
- package/internal/scroll-area.d.ts.map +1 -0
- package/internal/scroll-area.js +15 -0
- package/internal/scroll-area.js.map +1 -0
- package/internal/tooltip.d.ts +7 -0
- package/internal/tooltip.d.ts.map +1 -0
- package/internal/tooltip.js +31 -0
- package/internal/tooltip.js.map +1 -0
- package/manifest/ApplyManifestDialog.d.ts +10 -5
- package/manifest/ApplyManifestDialog.d.ts.map +1 -1
- package/manifest/ApplyManifestDialog.js +30 -13
- package/manifest/ApplyManifestDialog.js.map +1 -1
- package/manifest/useApplyManifest.d.ts +6 -2
- package/manifest/useApplyManifest.d.ts.map +1 -1
- package/manifest/useApplyManifest.js +5 -5
- package/manifest/useApplyManifest.js.map +1 -1
- package/mcp-server/McpServerDetailView.d.ts +38 -2
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +21 -5
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/StdioSandboxNotice.d.ts +12 -11
- package/mcp-server/StdioSandboxNotice.d.ts.map +1 -1
- package/mcp-server/StdioSandboxNotice.js +13 -12
- package/mcp-server/StdioSandboxNotice.js.map +1 -1
- package/mcp-server/index.d.ts +7 -0
- package/mcp-server/index.d.ts.map +1 -1
- package/mcp-server/index.js +9 -0
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/steps/EnvironmentAuthStep.d.ts +4 -0
- package/mcp-server/steps/EnvironmentAuthStep.d.ts.map +1 -1
- package/mcp-server/steps/EnvironmentAuthStep.js +4 -0
- package/mcp-server/steps/EnvironmentAuthStep.js.map +1 -1
- package/mcp-server/steps/IdentityTransportStep.d.ts +6 -0
- package/mcp-server/steps/IdentityTransportStep.d.ts.map +1 -1
- package/mcp-server/steps/IdentityTransportStep.js +7 -1
- package/mcp-server/steps/IdentityTransportStep.js.map +1 -1
- package/mcp-server/steps/ReviewStep.d.ts +5 -0
- package/mcp-server/steps/ReviewStep.d.ts.map +1 -1
- package/mcp-server/steps/ReviewStep.js +16 -2
- package/mcp-server/steps/ReviewStep.js.map +1 -1
- package/mcp-server/useMcpServerCount.d.ts +2 -0
- package/mcp-server/useMcpServerCount.d.ts.map +1 -1
- package/mcp-server/useMcpServerCount.js.map +1 -1
- package/package.json +4 -4
- package/resource-creation/CreationPicker.d.ts.map +1 -1
- package/resource-creation/CreationPicker.js +3 -3
- package/resource-creation/CreationPicker.js.map +1 -1
- package/resource-creation/WizardNav.d.ts.map +1 -1
- package/resource-creation/WizardNav.js +1 -1
- package/resource-creation/WizardNav.js.map +1 -1
- package/resource-creation/templates/mcp-server-templates.d.ts.map +1 -1
- package/resource-creation/templates/mcp-server-templates.js +17 -14
- package/resource-creation/templates/mcp-server-templates.js.map +1 -1
- package/resource-detail/Section.d.ts +8 -1
- package/resource-detail/Section.d.ts.map +1 -1
- package/resource-detail/Section.js +2 -2
- package/resource-detail/Section.js.map +1 -1
- package/resource-workbench/components/ResourceWorkbench.d.ts +9 -1
- package/resource-workbench/components/ResourceWorkbench.d.ts.map +1 -1
- package/resource-workbench/components/ResourceWorkbench.js +2 -1
- package/resource-workbench/components/ResourceWorkbench.js.map +1 -1
- package/resource-workbench/hooks/useResourceCollection.d.ts +9 -0
- package/resource-workbench/hooks/useResourceCollection.d.ts.map +1 -1
- package/resource-workbench/hooks/useResourceCollection.js +2 -2
- package/resource-workbench/hooks/useResourceCollection.js.map +1 -1
- package/search/useResourceCount.d.ts +7 -0
- package/search/useResourceCount.d.ts.map +1 -1
- package/search/useResourceCount.js +2 -1
- package/search/useResourceCount.js.map +1 -1
- package/session/NewSessionViewer.d.ts.map +1 -1
- package/session/NewSessionViewer.js +17 -3
- package/session/NewSessionViewer.js.map +1 -1
- package/session/SessionViewer.d.ts +2 -1
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +19 -11
- package/session/SessionViewer.js.map +1 -1
- package/session/SessionViewerLayout.d.ts +90 -0
- package/session/SessionViewerLayout.d.ts.map +1 -0
- package/session/SessionViewerLayout.js +32 -0
- package/session/SessionViewerLayout.js.map +1 -0
- package/session/index.d.ts +4 -0
- package/session/index.d.ts.map +1 -1
- package/session/index.js +10 -0
- package/session/index.js.map +1 -1
- package/session/useSessionRailViews.d.ts +2 -2
- package/sidebar/SettingsSidebar.d.ts +62 -0
- package/sidebar/SettingsSidebar.d.ts.map +1 -0
- package/sidebar/SettingsSidebar.js +60 -0
- package/sidebar/SettingsSidebar.js.map +1 -0
- package/sidebar/WorkspaceSidebar.d.ts +87 -0
- package/sidebar/WorkspaceSidebar.d.ts.map +1 -0
- package/sidebar/WorkspaceSidebar.js +95 -0
- package/sidebar/WorkspaceSidebar.js.map +1 -0
- package/sidebar/chrome.d.ts +48 -0
- package/sidebar/chrome.d.ts.map +1 -0
- package/sidebar/chrome.js +32 -0
- package/sidebar/chrome.js.map +1 -0
- package/sidebar/index.d.ts +6 -0
- package/sidebar/index.d.ts.map +1 -0
- package/sidebar/index.js +9 -0
- package/sidebar/index.js.map +1 -0
- package/sidebar/types.d.ts +54 -0
- package/sidebar/types.d.ts.map +1 -0
- package/sidebar/types.js +2 -0
- package/sidebar/types.js.map +1 -0
- package/skill/useSkillCount.d.ts +2 -0
- package/skill/useSkillCount.d.ts.map +1 -1
- package/skill/useSkillCount.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +198 -11
- package/src/agent/__tests__/AgentDetailView.datastores.test.tsx +166 -0
- package/src/agent/internal/__tests__/agentToInput.test.ts +154 -0
- package/src/agent/internal/agentToInput.ts +25 -7
- package/src/agent/useAgentCount.ts +2 -0
- package/src/api-key/ApiKeyListPanel.tsx +17 -4
- package/src/api-key/CreateApiKeyForm.tsx +8 -1
- package/src/api-key/__tests__/CreateApiKeyForm.test.tsx +58 -0
- package/src/datastore/useDatastoreCount.ts +2 -0
- package/src/dependency-graph/DependencyTreeNode.tsx +8 -0
- package/src/dependency-graph/__tests__/useDependencyGraph.test.ts +108 -0
- package/src/dependency-graph/types.ts +20 -8
- package/src/dependency-graph/useDependencyGraph.ts +23 -1
- package/src/index.ts +24 -0
- package/src/internal/scroll-area.tsx +37 -0
- package/src/internal/tooltip.tsx +66 -0
- package/src/manifest/ApplyManifestDialog.tsx +40 -16
- package/src/manifest/__tests__/ApplyManifestDialog.test.tsx +147 -0
- package/src/manifest/__tests__/useApplyManifest.test.tsx +6 -6
- package/src/manifest/useApplyManifest.ts +12 -8
- package/src/mcp-server/McpServerDetailView.tsx +52 -4
- package/src/mcp-server/StdioSandboxNotice.tsx +16 -14
- package/src/mcp-server/__tests__/McpServerDetailView.test.tsx +345 -0
- package/src/mcp-server/__tests__/StdioSandboxNotice.test.tsx +3 -2
- package/src/mcp-server/index.ts +13 -0
- package/src/mcp-server/steps/EnvironmentAuthStep.tsx +4 -0
- package/src/mcp-server/steps/IdentityTransportStep.tsx +16 -1
- package/src/mcp-server/steps/ReviewStep.tsx +18 -1
- package/src/mcp-server/useMcpServerCount.ts +2 -0
- package/src/resource-creation/CreationPicker.tsx +7 -0
- package/src/resource-creation/WizardNav.tsx +1 -0
- package/src/resource-creation/templates/mcp-server-templates.ts +17 -14
- package/src/resource-detail/Section.tsx +9 -1
- package/src/resource-workbench/components/ResourceWorkbench.tsx +13 -0
- package/src/resource-workbench/hooks/useResourceCollection.ts +11 -1
- package/src/search/useResourceCount.ts +9 -1
- package/src/session/NewSessionViewer.tsx +48 -61
- package/src/session/SessionViewer.tsx +71 -86
- package/src/session/SessionViewerLayout.tsx +131 -0
- package/src/session/__tests__/SessionViewerLayout.test.tsx +140 -0
- package/src/session/index.ts +14 -0
- package/src/session/useSessionRailViews.tsx +2 -2
- package/src/sidebar/SettingsSidebar.tsx +173 -0
- package/src/sidebar/WorkspaceSidebar.tsx +386 -0
- package/src/sidebar/__tests__/SettingsSidebar.test.tsx +107 -0
- package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +236 -0
- package/src/sidebar/chrome.tsx +129 -0
- package/src/sidebar/index.ts +16 -0
- package/src/sidebar/types.ts +55 -0
- package/src/skill/useSkillCount.ts +2 -0
- package/src/test/__tests__/samples.test.ts +161 -2
- package/src/test/index.ts +1 -1
- package/src/test/samples.ts +157 -14
- package/src/workflow/useWorkflowCount.ts +2 -0
- package/styles.css +1 -1
- package/test/index.d.ts +1 -1
- package/test/index.d.ts.map +1 -1
- package/test/index.js +1 -1
- package/test/index.js.map +1 -1
- package/test/samples.d.ts +100 -7
- package/test/samples.d.ts.map +1 -1
- package/test/samples.js +133 -14
- package/test/samples.js.map +1 -1
- package/workflow/useWorkflowCount.d.ts +2 -0
- package/workflow/useWorkflowCount.d.ts.map +1 -1
- package/workflow/useWorkflowCount.js.map +1 -1
package/src/session/index.ts
CHANGED
|
@@ -109,6 +109,12 @@ export type { SessionViewerProps } from "./SessionViewer.js";
|
|
|
109
109
|
export { NewSessionViewer } from "./NewSessionViewer.js";
|
|
110
110
|
export type { NewSessionViewerProps } from "./NewSessionViewer.js";
|
|
111
111
|
|
|
112
|
+
// SessionViewerLayout — the conversation-plus-panel frame both viewers
|
|
113
|
+
// share, exported for platform builders composing a custom session surface
|
|
114
|
+
// at the shipped console's geometry.
|
|
115
|
+
export { SessionViewerLayout } from "./SessionViewerLayout.js";
|
|
116
|
+
export type { SessionViewerLayoutProps } from "./SessionViewerLayout.js";
|
|
117
|
+
|
|
112
118
|
// Unified session-panel controller — drives the WorkspaceSurface open-editor
|
|
113
119
|
// group, the collapsed/open state, and the rail-view FSM (for platform
|
|
114
120
|
// builders embedding the surface directly).
|
|
@@ -118,6 +124,14 @@ export type {
|
|
|
118
124
|
UseSessionPanelOptions,
|
|
119
125
|
} from "./useSessionPanel.js";
|
|
120
126
|
|
|
127
|
+
// Session rail views — composes the session facets (Config / Changes /
|
|
128
|
+
// Artifacts / Usage) as the panel's rail, with the console's own labels,
|
|
129
|
+
// icons, badges, and contextual-visibility rules. Exported so custom
|
|
130
|
+
// session surfaces inherit the rail by construction instead of
|
|
131
|
+
// re-declaring it.
|
|
132
|
+
export { useSessionRailViews } from "./useSessionRailViews.js";
|
|
133
|
+
export type { UseSessionRailViewsOptions } from "./useSessionRailViews.js";
|
|
134
|
+
|
|
121
135
|
// Plan draft — viewer-owned in-place edit of the session's current plan
|
|
122
136
|
// (edit-as-input; the published plan.md artifact is never mutated).
|
|
123
137
|
export { usePlanDraft, planDraftKey } from "./usePlanDraft.js";
|
|
@@ -55,13 +55,13 @@ export interface UseSessionRailViewsOptions {
|
|
|
55
55
|
* (a cloud session with git workspace entries). When `true` the Changes
|
|
56
56
|
* facet is offered from the start — with an honest pre-push state — instead
|
|
57
57
|
* of only materializing once a write-back exists, so "where does my work
|
|
58
|
-
* go?" always has an answer
|
|
58
|
+
* go?" always has an answer (the facet's `expectsWriteBack` prop).
|
|
59
59
|
* @default false
|
|
60
60
|
*/
|
|
61
61
|
readonly expectsWriteBack?: boolean;
|
|
62
62
|
/**
|
|
63
63
|
* Whether the session's latest execution is settled (terminal) — forwarded
|
|
64
|
-
* to the Changes facet's pre-push states
|
|
64
|
+
* to the Changes facet's pre-push states (its `isSettled` prop).
|
|
65
65
|
* @default false
|
|
66
66
|
*/
|
|
67
67
|
readonly changesSettled?: boolean;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { ArrowLeft } from "lucide-react";
|
|
5
|
+
import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
6
|
+
import type { SettingsNavGroup } from "../settings/settings-nav.js";
|
|
7
|
+
import { SidebarChrome, SidebarSeparator, navRowClassName } from "./chrome.js";
|
|
8
|
+
import type { RenderSidebarLink } from "./types.js";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Public API
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/** Props for {@link SettingsSidebar}. */
|
|
15
|
+
export interface SettingsSidebarProps {
|
|
16
|
+
/**
|
|
17
|
+
* Grouped settings navigation to render. The console passes
|
|
18
|
+
* `useSettingsNavGroups()` (permission-aware); deterministic hosts
|
|
19
|
+
* pass `SETTINGS_NAV_GROUPS` or a fixture subset.
|
|
20
|
+
*/
|
|
21
|
+
readonly groups: readonly SettingsNavGroup[];
|
|
22
|
+
/**
|
|
23
|
+
* Current pathname for active-row matching: a row is active when the
|
|
24
|
+
* path equals its `href` or sits under `href + "/"`. Omit for hosts
|
|
25
|
+
* with no location (deterministic embeds pass the depicted route).
|
|
26
|
+
*/
|
|
27
|
+
readonly activePath?: string | null;
|
|
28
|
+
/** Renders each interactive row. See {@link RenderSidebarLink}. */
|
|
29
|
+
readonly renderLink: RenderSidebarLink;
|
|
30
|
+
/**
|
|
31
|
+
* "Back to Sessions" target — the last workspace-zone location, so
|
|
32
|
+
* leaving settings restores where the user was. @default "/"
|
|
33
|
+
*/
|
|
34
|
+
readonly backHref?: string;
|
|
35
|
+
/** Footer content — typically the host app's `UserMenu` wrapper. */
|
|
36
|
+
readonly footer: ReactNode;
|
|
37
|
+
/** Reflected as `aria-expanded` on the collapse toggle. @default true */
|
|
38
|
+
readonly isOpen?: boolean;
|
|
39
|
+
/** Called when the collapse toggle is pressed. */
|
|
40
|
+
readonly onCollapse?: () => void;
|
|
41
|
+
/** Passed through to the embedded `OrgSwitcher`. */
|
|
42
|
+
readonly onOrgChanged?: (org: Organization) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The console's settings-zone (management) sidebar: org switcher, a
|
|
47
|
+
* "Back to Sessions" exit, and grouped settings navigation driven by
|
|
48
|
+
* the SDK's settings-nav model, with a user footer.
|
|
49
|
+
*
|
|
50
|
+
* Like {@link WorkspaceSidebar}, this is the exact component the web
|
|
51
|
+
* console, desktop app, and documentation tours all render; hosts vary
|
|
52
|
+
* only `renderLink`, `groups`, and the `footer` slot.
|
|
53
|
+
*
|
|
54
|
+
* Row identifiers passed to `renderLink` are the settings route
|
|
55
|
+
* basenames (`"api-keys"`, `"members"`, …) plus `"back-to-sessions"`,
|
|
56
|
+
* so consumers can address specific rows without parsing hrefs.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* <SettingsSidebar
|
|
61
|
+
* groups={useSettingsNavGroups()}
|
|
62
|
+
* activePath={usePathname()}
|
|
63
|
+
* backHref={lastSessionZonePath ?? "/"}
|
|
64
|
+
* renderLink={({ href, children, ...rest }) => (
|
|
65
|
+
* <Link href={href} {...rest}>{children}</Link>
|
|
66
|
+
* )}
|
|
67
|
+
* footer={<UserMenu />}
|
|
68
|
+
* />
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function SettingsSidebar({
|
|
72
|
+
groups,
|
|
73
|
+
activePath = null,
|
|
74
|
+
renderLink,
|
|
75
|
+
backHref = "/",
|
|
76
|
+
footer,
|
|
77
|
+
isOpen = true,
|
|
78
|
+
onCollapse,
|
|
79
|
+
onOrgChanged,
|
|
80
|
+
}: SettingsSidebarProps) {
|
|
81
|
+
return (
|
|
82
|
+
<SidebarChrome
|
|
83
|
+
ariaLabel="Management navigation"
|
|
84
|
+
isOpen={isOpen}
|
|
85
|
+
onCollapse={onCollapse}
|
|
86
|
+
onOrgChanged={onOrgChanged}
|
|
87
|
+
footer={footer}
|
|
88
|
+
>
|
|
89
|
+
{/* Back to Sessions */}
|
|
90
|
+
<div className="flex-none px-3 py-1">
|
|
91
|
+
{renderLink({
|
|
92
|
+
id: "back-to-sessions",
|
|
93
|
+
href: backHref,
|
|
94
|
+
active: false,
|
|
95
|
+
className: navRowClassName(false, { muted: true }),
|
|
96
|
+
"aria-current": undefined,
|
|
97
|
+
children: (
|
|
98
|
+
<>
|
|
99
|
+
<ArrowLeft className="size-4 shrink-0" />
|
|
100
|
+
Back to Sessions
|
|
101
|
+
</>
|
|
102
|
+
),
|
|
103
|
+
})}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<SidebarSeparator />
|
|
107
|
+
|
|
108
|
+
{/* Settings nav links — grouped, permission-aware via `groups` */}
|
|
109
|
+
<div className="flex flex-col gap-4 px-3 py-1">
|
|
110
|
+
{groups.map((group) => (
|
|
111
|
+
<div key={group.label} className="flex flex-col gap-0.5">
|
|
112
|
+
<span className="text-sidebar-muted-foreground px-2 pb-1 text-[11px] font-medium tracking-wider uppercase">
|
|
113
|
+
{group.label}
|
|
114
|
+
</span>
|
|
115
|
+
{group.items.map((item) => {
|
|
116
|
+
const active =
|
|
117
|
+
activePath != null &&
|
|
118
|
+
(activePath === item.href ||
|
|
119
|
+
activePath.startsWith(`${item.href}/`));
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<SettingsNavRow
|
|
123
|
+
key={item.href}
|
|
124
|
+
href={item.href}
|
|
125
|
+
label={item.label}
|
|
126
|
+
icon={<item.icon className="size-4 shrink-0" />}
|
|
127
|
+
active={active}
|
|
128
|
+
renderLink={renderLink}
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
})}
|
|
132
|
+
</div>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{/* Spacer pushes the footer to the bottom (no scrollable middle). */}
|
|
137
|
+
<div className="flex-1" />
|
|
138
|
+
</SidebarChrome>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Internals
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
function SettingsNavRow({
|
|
147
|
+
href,
|
|
148
|
+
label,
|
|
149
|
+
icon,
|
|
150
|
+
active,
|
|
151
|
+
renderLink,
|
|
152
|
+
}: {
|
|
153
|
+
readonly href: string;
|
|
154
|
+
readonly label: string;
|
|
155
|
+
readonly icon: ReactNode;
|
|
156
|
+
readonly active: boolean;
|
|
157
|
+
readonly renderLink: RenderSidebarLink;
|
|
158
|
+
}) {
|
|
159
|
+
return renderLink({
|
|
160
|
+
// Route basename as the stable id: "/settings/api-keys" → "api-keys".
|
|
161
|
+
id: href.slice(href.lastIndexOf("/") + 1),
|
|
162
|
+
href,
|
|
163
|
+
active,
|
|
164
|
+
className: navRowClassName(active),
|
|
165
|
+
"aria-current": active ? "page" : undefined,
|
|
166
|
+
children: (
|
|
167
|
+
<>
|
|
168
|
+
{icon}
|
|
169
|
+
{label}
|
|
170
|
+
</>
|
|
171
|
+
),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { memo, useMemo, type ReactNode } from "react";
|
|
4
|
+
import {
|
|
5
|
+
Plus,
|
|
6
|
+
LayoutDashboard,
|
|
7
|
+
Library,
|
|
8
|
+
MessageSquare,
|
|
9
|
+
Workflow,
|
|
10
|
+
type LucideIcon,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
13
|
+
import type { RecentActivityEntry, RecentActivityGroup } from "../activity/types.js";
|
|
14
|
+
import { groupRecentActivityByTime } from "../activity/group-activity.js";
|
|
15
|
+
import { formatRelativeTime } from "../activity/format-relative-time.js";
|
|
16
|
+
import { recentActivityStatusBadge } from "../activity/entry-status-badge.js";
|
|
17
|
+
import { ScrollArea } from "../internal/scroll-area.js";
|
|
18
|
+
import {
|
|
19
|
+
Tooltip,
|
|
20
|
+
TooltipProvider,
|
|
21
|
+
TooltipTrigger,
|
|
22
|
+
TooltipContent,
|
|
23
|
+
} from "../internal/tooltip.js";
|
|
24
|
+
import { SidebarChrome, SidebarSeparator, navRowClassName } from "./chrome.js";
|
|
25
|
+
import type { RenderSidebarLink } from "./types.js";
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Public API
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/** Primary navigation destinations in the workspace sidebar. */
|
|
32
|
+
export type WorkspaceNavId = "new-session" | "dashboard" | "library";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Recent-activity data for the sidebar's Recents section — the return
|
|
36
|
+
* shape of `useRecentActivity`, narrowed to what the sidebar renders.
|
|
37
|
+
* Deterministic hosts pass frozen fixture entries instead.
|
|
38
|
+
*/
|
|
39
|
+
export interface WorkspaceSidebarActivity {
|
|
40
|
+
readonly entries: readonly RecentActivityEntry[];
|
|
41
|
+
/** True during the initial fetch; renders loading skeletons. */
|
|
42
|
+
readonly isLoading?: boolean;
|
|
43
|
+
/** Non-null when the fetch failed; renders an inline alert. */
|
|
44
|
+
readonly error?: { readonly message: string } | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Props for {@link WorkspaceSidebar}. */
|
|
48
|
+
export interface WorkspaceSidebarProps {
|
|
49
|
+
/**
|
|
50
|
+
* Which primary nav row is the current location, or `null`/omitted
|
|
51
|
+
* when none is (e.g. inside a session). The sidebar renders state
|
|
52
|
+
* only — the host decides what "active" means for its routes.
|
|
53
|
+
*/
|
|
54
|
+
readonly activeNav?: WorkspaceNavId | null;
|
|
55
|
+
/** Renders each interactive row. See {@link RenderSidebarLink}. */
|
|
56
|
+
readonly renderLink: RenderSidebarLink;
|
|
57
|
+
/** Recent activity entries plus fetch state. */
|
|
58
|
+
readonly recentActivity: WorkspaceSidebarActivity;
|
|
59
|
+
/** Session id highlighted in Recents, if a session is open. */
|
|
60
|
+
readonly activeSessionId?: string | null;
|
|
61
|
+
/** Workflow-execution id highlighted in Recents, if one is open. */
|
|
62
|
+
readonly activeExecutionId?: string | null;
|
|
63
|
+
/**
|
|
64
|
+
* Optional trailing accessory per recents row — e.g. the desktop
|
|
65
|
+
* app's "running in background" pulse dot. Rendered after the
|
|
66
|
+
* timestamp column, inside the row.
|
|
67
|
+
*/
|
|
68
|
+
readonly renderEntryAccessory?: (entry: RecentActivityEntry) => ReactNode;
|
|
69
|
+
/** Footer content — typically the host app's `UserMenu` wrapper. */
|
|
70
|
+
readonly footer: ReactNode;
|
|
71
|
+
/** Reflected as `aria-expanded` on the collapse toggle. @default true */
|
|
72
|
+
readonly isOpen?: boolean;
|
|
73
|
+
/** Called when the collapse toggle is pressed. */
|
|
74
|
+
readonly onCollapse?: () => void;
|
|
75
|
+
/** Passed through to the embedded `OrgSwitcher`. */
|
|
76
|
+
readonly onOrgChanged?: (org: Organization) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Reference instant for time bucketing ("Today"/"Yesterday") and
|
|
79
|
+
* relative stamps ("2h"). Defaults to the live clock. **Deterministic
|
|
80
|
+
* hosts (documentation embeds, video export) must pass a frozen
|
|
81
|
+
* instant** — otherwise the depicted times drift with every replay.
|
|
82
|
+
*/
|
|
83
|
+
readonly now?: Date;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The console's workspace-zone sidebar: org switcher, primary navigation
|
|
88
|
+
* (New Session / Dashboard / Library), time-bucketed recent activity,
|
|
89
|
+
* and a user footer.
|
|
90
|
+
*
|
|
91
|
+
* This is the same component the Stigmer web console and desktop app
|
|
92
|
+
* render — hosts differ only in what they return from `renderLink`
|
|
93
|
+
* (their router's link), what they pass as `recentActivity` (live hook
|
|
94
|
+
* data or frozen fixtures), and the `footer` slot. Documentation tours
|
|
95
|
+
* render it too, which is the point: the depicted chrome cannot drift
|
|
96
|
+
* from the product because it *is* the product (stigmer/stigmer#317).
|
|
97
|
+
*
|
|
98
|
+
* Layout note: the sidebar fills its container's height and defines no
|
|
99
|
+
* width — the console's app shell owns the `w-70` (280px) column.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```tsx
|
|
103
|
+
* <WorkspaceSidebar
|
|
104
|
+
* activeNav="library"
|
|
105
|
+
* renderLink={({ href, children, ...rest }) => (
|
|
106
|
+
* <Link href={href} {...rest}>{children}</Link>
|
|
107
|
+
* )}
|
|
108
|
+
* recentActivity={useRecentActivity()}
|
|
109
|
+
* footer={<UserMenu />}
|
|
110
|
+
* />
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function WorkspaceSidebar({
|
|
114
|
+
activeNav = null,
|
|
115
|
+
renderLink,
|
|
116
|
+
recentActivity,
|
|
117
|
+
activeSessionId = null,
|
|
118
|
+
activeExecutionId = null,
|
|
119
|
+
renderEntryAccessory,
|
|
120
|
+
footer,
|
|
121
|
+
isOpen = true,
|
|
122
|
+
onCollapse,
|
|
123
|
+
onOrgChanged,
|
|
124
|
+
now,
|
|
125
|
+
}: WorkspaceSidebarProps) {
|
|
126
|
+
const { entries, isLoading = false, error = null } = recentActivity;
|
|
127
|
+
|
|
128
|
+
const groups = useMemo(
|
|
129
|
+
() => groupRecentActivityByTime(entries, now),
|
|
130
|
+
[entries, now],
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<SidebarChrome
|
|
135
|
+
ariaLabel="Main navigation"
|
|
136
|
+
isOpen={isOpen}
|
|
137
|
+
onCollapse={onCollapse}
|
|
138
|
+
onOrgChanged={onOrgChanged}
|
|
139
|
+
footer={footer}
|
|
140
|
+
>
|
|
141
|
+
<PrimaryNavRow
|
|
142
|
+
id="new-session"
|
|
143
|
+
href="/"
|
|
144
|
+
label="New Session"
|
|
145
|
+
icon={Plus}
|
|
146
|
+
active={activeNav === "new-session"}
|
|
147
|
+
renderLink={renderLink}
|
|
148
|
+
/>
|
|
149
|
+
<PrimaryNavRow
|
|
150
|
+
id="dashboard"
|
|
151
|
+
href="/dashboard"
|
|
152
|
+
label="Dashboard"
|
|
153
|
+
icon={LayoutDashboard}
|
|
154
|
+
active={activeNav === "dashboard"}
|
|
155
|
+
renderLink={renderLink}
|
|
156
|
+
/>
|
|
157
|
+
<PrimaryNavRow
|
|
158
|
+
id="library"
|
|
159
|
+
href="/library"
|
|
160
|
+
label="Library"
|
|
161
|
+
icon={Library}
|
|
162
|
+
active={activeNav === "library"}
|
|
163
|
+
renderLink={renderLink}
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
<SidebarSeparator />
|
|
167
|
+
|
|
168
|
+
{/* Scrollable recents */}
|
|
169
|
+
<ScrollArea className="flex-1">
|
|
170
|
+
<div className="p-3">
|
|
171
|
+
<p className="text-sidebar-muted-foreground mb-2 px-1 text-[11px] font-semibold tracking-wider uppercase">
|
|
172
|
+
Recents
|
|
173
|
+
</p>
|
|
174
|
+
{isLoading ? (
|
|
175
|
+
<RecentsSkeletons />
|
|
176
|
+
) : error ? (
|
|
177
|
+
<RecentsError message={error.message} />
|
|
178
|
+
) : groups.length === 0 ? (
|
|
179
|
+
<RecentsEmptyState />
|
|
180
|
+
) : (
|
|
181
|
+
<ActivityGroupList
|
|
182
|
+
groups={groups}
|
|
183
|
+
activeSessionId={activeSessionId}
|
|
184
|
+
activeExecutionId={activeExecutionId}
|
|
185
|
+
renderLink={renderLink}
|
|
186
|
+
renderEntryAccessory={renderEntryAccessory}
|
|
187
|
+
now={now}
|
|
188
|
+
/>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
</ScrollArea>
|
|
192
|
+
</SidebarChrome>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Internals
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
function PrimaryNavRow({
|
|
201
|
+
id,
|
|
202
|
+
href,
|
|
203
|
+
label,
|
|
204
|
+
icon: Icon,
|
|
205
|
+
active,
|
|
206
|
+
renderLink,
|
|
207
|
+
}: {
|
|
208
|
+
readonly id: WorkspaceNavId;
|
|
209
|
+
readonly href: string;
|
|
210
|
+
readonly label: string;
|
|
211
|
+
readonly icon: LucideIcon;
|
|
212
|
+
readonly active: boolean;
|
|
213
|
+
readonly renderLink: RenderSidebarLink;
|
|
214
|
+
}) {
|
|
215
|
+
return (
|
|
216
|
+
<div className="flex-none px-3 py-1">
|
|
217
|
+
{renderLink({
|
|
218
|
+
id,
|
|
219
|
+
href,
|
|
220
|
+
active,
|
|
221
|
+
className: navRowClassName(active),
|
|
222
|
+
"aria-current": active ? "page" : undefined,
|
|
223
|
+
children: (
|
|
224
|
+
<>
|
|
225
|
+
<Icon className="size-4 shrink-0" />
|
|
226
|
+
{label}
|
|
227
|
+
</>
|
|
228
|
+
),
|
|
229
|
+
})}
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function ActivityGroupList({
|
|
235
|
+
groups,
|
|
236
|
+
activeSessionId,
|
|
237
|
+
activeExecutionId,
|
|
238
|
+
renderLink,
|
|
239
|
+
renderEntryAccessory,
|
|
240
|
+
now,
|
|
241
|
+
}: {
|
|
242
|
+
readonly groups: readonly RecentActivityGroup[];
|
|
243
|
+
readonly activeSessionId: string | null;
|
|
244
|
+
readonly activeExecutionId: string | null;
|
|
245
|
+
readonly renderLink: RenderSidebarLink;
|
|
246
|
+
readonly renderEntryAccessory?: (entry: RecentActivityEntry) => ReactNode;
|
|
247
|
+
readonly now?: Date;
|
|
248
|
+
}) {
|
|
249
|
+
return (
|
|
250
|
+
<TooltipProvider>
|
|
251
|
+
<div className="space-y-4">
|
|
252
|
+
{groups.map((group) => (
|
|
253
|
+
<div key={group.label}>
|
|
254
|
+
<p className="text-sidebar-muted-foreground mb-1 px-2 text-[10px] font-medium tracking-wider uppercase">
|
|
255
|
+
{group.label}
|
|
256
|
+
</p>
|
|
257
|
+
<ul className="space-y-0.5" role="list">
|
|
258
|
+
{group.entries.map((entry) => (
|
|
259
|
+
<ActivityEntry
|
|
260
|
+
key={entry.id}
|
|
261
|
+
entry={entry}
|
|
262
|
+
activeSessionId={activeSessionId}
|
|
263
|
+
activeExecutionId={activeExecutionId}
|
|
264
|
+
renderLink={renderLink}
|
|
265
|
+
renderEntryAccessory={renderEntryAccessory}
|
|
266
|
+
now={now}
|
|
267
|
+
/>
|
|
268
|
+
))}
|
|
269
|
+
</ul>
|
|
270
|
+
</div>
|
|
271
|
+
))}
|
|
272
|
+
</div>
|
|
273
|
+
</TooltipProvider>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const ActivityEntry = memo(function ActivityEntry({
|
|
278
|
+
entry,
|
|
279
|
+
activeSessionId,
|
|
280
|
+
activeExecutionId,
|
|
281
|
+
renderLink,
|
|
282
|
+
renderEntryAccessory,
|
|
283
|
+
now,
|
|
284
|
+
}: {
|
|
285
|
+
readonly entry: RecentActivityEntry;
|
|
286
|
+
readonly activeSessionId: string | null;
|
|
287
|
+
readonly activeExecutionId: string | null;
|
|
288
|
+
readonly renderLink: RenderSidebarLink;
|
|
289
|
+
readonly renderEntryAccessory?: (entry: RecentActivityEntry) => ReactNode;
|
|
290
|
+
readonly now?: Date;
|
|
291
|
+
}) {
|
|
292
|
+
const isSession = entry.type === "session";
|
|
293
|
+
const isActive = isSession
|
|
294
|
+
? entry.id === activeSessionId
|
|
295
|
+
: entry.id === activeExecutionId;
|
|
296
|
+
const href = isSession ? `/sessions/${entry.id}` : `/executions/${entry.id}`;
|
|
297
|
+
const TypeIcon = isSession ? MessageSquare : Workflow;
|
|
298
|
+
const statusBadge = recentActivityStatusBadge(entry);
|
|
299
|
+
|
|
300
|
+
const row = renderLink({
|
|
301
|
+
id: entry.id,
|
|
302
|
+
href,
|
|
303
|
+
active: isActive,
|
|
304
|
+
"aria-current": isActive ? "page" : undefined,
|
|
305
|
+
entry,
|
|
306
|
+
className: cnActivityRow(isActive),
|
|
307
|
+
children: (
|
|
308
|
+
<>
|
|
309
|
+
<TypeIcon className="mt-0.5 size-3 shrink-0 opacity-50" aria-hidden="true" />
|
|
310
|
+
<span className="line-clamp-2 flex-1">{entry.subject}</span>
|
|
311
|
+
{/* Last-activity stamp + noteworthy status: the list sorts by
|
|
312
|
+
activity while execution names embed creation time, so the row
|
|
313
|
+
must say WHY it is here ("failed · 2h"). */}
|
|
314
|
+
<span className="flex shrink-0 flex-col items-end gap-0.5 text-[10px] leading-tight">
|
|
315
|
+
<span className="text-sidebar-muted-foreground tabular-nums">
|
|
316
|
+
{formatRelativeTime(entry.updatedAt, now)}
|
|
317
|
+
</span>
|
|
318
|
+
{statusBadge && (
|
|
319
|
+
<span
|
|
320
|
+
className={
|
|
321
|
+
statusBadge.tone === "destructive"
|
|
322
|
+
? "text-destructive"
|
|
323
|
+
: "text-sidebar-muted-foreground"
|
|
324
|
+
}
|
|
325
|
+
>
|
|
326
|
+
{statusBadge.label}
|
|
327
|
+
</span>
|
|
328
|
+
)}
|
|
329
|
+
</span>
|
|
330
|
+
{renderEntryAccessory?.(entry)}
|
|
331
|
+
</>
|
|
332
|
+
),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<li>
|
|
337
|
+
<Tooltip>
|
|
338
|
+
<TooltipTrigger render={row} />
|
|
339
|
+
<TooltipContent side="right" sideOffset={12}>
|
|
340
|
+
{entry.subject}
|
|
341
|
+
</TooltipContent>
|
|
342
|
+
</Tooltip>
|
|
343
|
+
</li>
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
/** Recents rows are denser than primary nav: 12px text, top-aligned icon. */
|
|
348
|
+
function cnActivityRow(active: boolean): string {
|
|
349
|
+
return active
|
|
350
|
+
? "flex items-start gap-2 rounded-lg px-2 py-1.5 text-xs transition-colors bg-sidebar-accent text-sidebar-accent-foreground font-medium"
|
|
351
|
+
: "flex items-start gap-2 rounded-lg px-2 py-1.5 text-xs transition-colors text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground";
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function RecentsSkeletons() {
|
|
355
|
+
return (
|
|
356
|
+
<div className="space-y-2 px-2" aria-busy="true" aria-label="Loading sessions">
|
|
357
|
+
{Array.from({ length: 5 }, (_, i) => (
|
|
358
|
+
<div
|
|
359
|
+
key={i}
|
|
360
|
+
className="bg-sidebar-muted h-5 animate-pulse rounded"
|
|
361
|
+
style={{ width: `${70 + Math.sin(i * 1.5) * 20}%` }}
|
|
362
|
+
/>
|
|
363
|
+
))}
|
|
364
|
+
</div>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function RecentsError({ message }: { readonly message: string }) {
|
|
369
|
+
return (
|
|
370
|
+
<>
|
|
371
|
+
<p className="text-destructive mb-4 px-2 text-xs" role="alert">
|
|
372
|
+
{message}
|
|
373
|
+
</p>
|
|
374
|
+
<RecentsEmptyState />
|
|
375
|
+
</>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function RecentsEmptyState() {
|
|
380
|
+
return (
|
|
381
|
+
<div className="flex flex-col items-center gap-2 py-8 text-center">
|
|
382
|
+
<MessageSquare className="text-sidebar-muted-foreground size-8" />
|
|
383
|
+
<p className="text-sidebar-muted-foreground text-xs">No recent activity</p>
|
|
384
|
+
</div>
|
|
385
|
+
);
|
|
386
|
+
}
|