Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
684a7a5
feat: add @selfpatch/ros2-medkit-client-ts dependency
bburda Mar 22, 2026
ccd80c6
feat: extract response transforms from sovd-api.ts
bburda Mar 22, 2026
356729b
refactor: move utility functions from sovd-api to utils
bburda Mar 22, 2026
e759d62
refactor: add generated client type re-exports to types.ts
bburda Mar 22, 2026
6f65aae
feat: add entity-type dispatch helpers for generated client
bburda Mar 22, 2026
da3bc83
refactor: migrate store from SovdApiClient to MedkitClient
bburda Mar 22, 2026
f47722d
feat: add store actions to replace direct client usage in components
bburda Mar 22, 2026
99b6db0
refactor: update component imports and replace direct client usage wi…
bburda Mar 22, 2026
586f5a0
refactor: remove hand-maintained sovd-api.ts client
bburda Mar 22, 2026
c7f8997
fix: remove stale baseEndpoint references and fix downloadBulkData
bburda Mar 22, 2026
22f5c4d
fix: correct copyright headers from selfpatch.ai to bburda
bburda Mar 23, 2026
61490e9
fix: restore connection guard on publish section in DataPanel
bburda Mar 23, 2026
8d9f30d
fix: correct path parsing in entity refresh, topic selection, and fal…
bburda Mar 23, 2026
0685277
fix: manual testing fixes - entity types, topic schema, faults, SSE
bburda Mar 24, 2026
75bc749
fix: rename all SOVD Server references to ros2_medkit Gateway
bburda Mar 27, 2026
a4ab8af
fix: add 5s timeout to health check during connect
bburda Mar 28, 2026
99d9ee3
fix: switch to npmjs.com for @selfpatch client package
bburda Mar 31, 2026
8e336df
fix: remove .serena from repo and fix prettier formatting
bburda Mar 31, 2026
df90b04
fix: address deep review findings - bugs, error handling, SSE recovery
bburda Mar 31, 2026
f75d8dd
test: add tests for api-dispatch (85) and store helpers (51)
bburda Mar 31, 2026
0e4188a
chore: update @selfpatch/ros2-medkit-client-ts to 0.1.1 from npmjs
bburda Mar 31, 2026
5449013
feat: make areas optional - fall back to components when empty
bburda Mar 31, 2026
8a1fe4a
feat: load subcomponents alongside apps for component nodes
bburda Mar 31, 2026
f28f2cd
fix: address PR review comments - SSE safety, timestamp, topic transform
bburda Apr 1, 2026
ff77970
fix: address mfaferek93 review - dedup requests, entity_type, runtime…
bburda Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# Serena
.serena/
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SOVD Web UI</title>
<title>ros2_medkit Web UI</title>
</head>
<body>
<div id="root"></div>
Expand Down
98 changes: 33 additions & 65 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tooltip": "^1.2.8",
"@selfpatch/ros2-medkit-client-ts": "^0.1.1",
"@tailwindcss/vite": "^4.1.14",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"lucide-react": "^0.544.0",
"openapi-fetch": "^0.17.0",
"radix-ui": "^1.4.3",
"react": "^19.1.1",
"react-dom": "^19.1.1",
Expand All @@ -52,6 +54,7 @@
"devDependencies": {
"@eslint/js": "^9.36.0",
"@tailwindcss/postcss": "^4.1.14",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
Expand Down
18 changes: 10 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ import { useAppStore } from '@/lib/store';
type ViewMode = 'entity' | 'faults-dashboard';

function App() {
const { isConnected, serverUrl, baseEndpoint, connect, clearSelection, selectedPath } = useAppStore(
const { isConnected, serverUrl, connect, clearSelection, selectedPath } = useAppStore(
useShallow((state) => ({
isConnected: state.isConnected,
serverUrl: state.serverUrl,
baseEndpoint: state.baseEndpoint,
connect: state.connect,
clearSelection: state.clearSelection,
selectedPath: state.selectedPath,
Expand Down Expand Up @@ -63,17 +62,20 @@ function App() {
}
}, [selectedPath]);

// Auto-connect on mount if we have a stored URL
// Auto-connect on mount if we have a stored URL (once only)
useEffect(() => {
if (serverUrl && !isConnected && !autoConnectAttempted.current) {
autoConnectAttempted.current = true;
connect(serverUrl, baseEndpoint).then((success) => {
if (!serverUrl || isConnected || autoConnectAttempted.current) return;
autoConnectAttempted.current = true;

const timeoutId = setTimeout(() => {
connect(serverUrl).then((success) => {
if (!success) {
toast.error('Auto-connect failed. Please check your server settings.');
setShowConnectionDialog(true);
}
});
}
}, 0);

return () => clearTimeout(timeoutId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Expand Down
15 changes: 8 additions & 7 deletions src/components/AppsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,26 +53,27 @@ export function AppsPanel({ appId, appName, fqn, nodeName, namespace, componentI
const [faults, setFaults] = useState<Fault[]>([]);
const [isLoading, setIsLoading] = useState(false);

const { client, selectEntity, configurations } = useAppStore(
const { selectEntity, configurations, fetchEntityData, fetchEntityOperations, listEntityFaults } = useAppStore(
useShallow((state) => ({
client: state.client,
selectEntity: state.selectEntity,
configurations: state.configurations,
fetchEntityData: state.fetchEntityData,
fetchEntityOperations: state.fetchEntityOperations,
listEntityFaults: state.listEntityFaults,
}))
);

// Load app resources on mount (configurations are loaded by ConfigurationPanel)
useEffect(() => {
const loadAppData = async () => {
if (!client) return;
setIsLoading(true);

try {
// Load resources in parallel (configurations handled by ConfigurationPanel)
const [topicsData, opsData, faultsData] = await Promise.all([
client.getAppData(appId).catch(() => []),
client.listOperations(appId, 'apps').catch(() => []),
client.listEntityFaults('apps', appId).catch(() => ({ items: [] })),
fetchEntityData('apps', appId).catch(() => [] as ComponentTopic[]),
fetchEntityOperations('apps', appId).catch(() => [] as Operation[]),
listEntityFaults('apps', appId).catch(() => ({ items: [] as Fault[], count: 0 })),
]);

setTopics(topicsData);
Expand All @@ -86,7 +87,7 @@ export function AppsPanel({ appId, appName, fqn, nodeName, namespace, componentI
};

loadAppData();
}, [client, appId]);
}, [fetchEntityData, fetchEntityOperations, listEntityFaults, appId]);

const handleResourceClick = (resourcePath: string) => {
if (onNavigate) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/ConfigurationPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { useAppStore, type AppState } from '@/lib/store';
import type { Parameter, ParameterType } from '@/lib/types';
import type { SovdResourceEntityType } from '@/lib/sovd-api';
import type { SovdResourceEntityType } from '@/lib/types';

interface ConfigurationPanelProps {
entityId: string;
Expand Down
11 changes: 4 additions & 7 deletions src/components/DataPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/component
import { JsonFormViewer } from '@/components/JsonFormViewer';
import { TopicPublishForm } from '@/components/TopicPublishForm';
import type { ComponentTopic, TopicEndpoint, QosProfile, SovdResourceEntityType } from '@/lib/types';
import type { SovdApiClient } from '@/lib/sovd-api';
import { cn } from '@/lib/utils';
import { useAppStore } from '@/lib/store';

interface DataPanelProps {
/** Data item from the API */
Expand All @@ -17,8 +17,6 @@ interface DataPanelProps {
entityId: string;
/** Entity type for API endpoint */
entityType?: SovdResourceEntityType;
/** API client for publishing */
client: SovdApiClient | null;
/** Whether a refresh is in progress */
isRefreshing?: boolean;
/** Callback when refresh is requested */
Expand Down Expand Up @@ -196,14 +194,14 @@ export function DataPanel({
topic,
entityId,
entityType = 'components',
client,
isRefreshing = false,
onRefresh,
}: DataPanelProps) {
const [publishValue, setPublishValue] = useState<unknown>(topic.type_info?.default_value || topic.data || {});

const isConnected = useAppStore((state) => state.isConnected);
const hasData = topic.status === 'data' && topic.data !== null && topic.data !== undefined;
const canPublish = !!(topic.type || topic.type_info || topic.data);
const canPublish = isConnected && !!(topic.type || topic.type_info || topic.data);

const handleCopyFromLast = () => {
if (topic.data) {
Expand Down Expand Up @@ -273,14 +271,13 @@ export function DataPanel({
</div>

{/* Publish Section */}
{canPublish && client && (
{canPublish && (
<div className="border-t pt-4 space-y-2">
<span className="text-sm font-medium">Publish Message</span>
<TopicPublishForm
topic={topic}
entityId={entityId}
entityType={entityType}
client={client}
initialValue={publishValue}
onValueChange={setPublishValue}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/EmptyState.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('EmptyState', () => {
render(<EmptyState type="no-connection" />);

expect(screen.getByText('No Server Connected')).toBeInTheDocument();
expect(screen.getByText('Connect to a SOVD server to browse entities.')).toBeInTheDocument();
expect(screen.getByText('Connect to a ros2_medkit gateway to browse entities.')).toBeInTheDocument();
});

it('renders no-entities state', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/EmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function EmptyState({ type, onAction }: EmptyStateProps) {
'no-connection': {
icon: Server,
title: 'No Server Connected',
description: 'Connect to a SOVD server to browse entities.',
description: 'Connect to a ros2_medkit gateway to browse entities.',
actionLabel: 'Connect to Server',
},
'no-entities': {
Expand Down
Loading
Loading