Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
91 changes: 46 additions & 45 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,50 +1,51 @@
{
"name": "SOVD Web UI Development",
"build": {
"dockerfile": "Dockerfile"
},
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": true,
"configureZshAsDefaultShell": true,
"installOhMyZsh": true
"name": "SOVD Web UI Development",
"build": {
"dockerfile": "Dockerfile"
},
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/node:1": {
"version": "22"
}
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"ms-vscode.vscode-typescript-next",
"GitHub.copilot",
"GitHub.copilot-chat",
"eamodio.gitlens",
"usernamehw.errorlens",
"christian-kohler.path-intellisense"
],
"settings": {
"terminal.integrated.defaultProfile.linux": "zsh",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": true,
"configureZshAsDefaultShell": true,
"installOhMyZsh": true
},
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/node:1": {
"version": "22"
}
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"ms-vscode.vscode-typescript-next",
"vitest.explorer",
"GitHub.copilot",
"GitHub.copilot-chat",
"eamodio.gitlens",
"usernamehw.errorlens",
"christian-kohler.path-intellisense"
],
"settings": {
"terminal.integrated.defaultProfile.linux": "zsh",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
}
},
"postCreateCommand": "npm install",
"forwardPorts": [5173],
"portsAttributes": {
"5173": {
"label": "Vite Dev Server",
"onAutoForward": "openBrowser"
}
}
}
},
"postCreateCommand": "npm install",
"forwardPorts": [5173],
"portsAttributes": {
"5173": {
"label": "Vite Dev Server",
"onAutoForward": "openBrowser"
}
}
}
99 changes: 99 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Copilot Instructions

## Project Overview

React 19 + Vite + TypeScript SPA for browsing SOVD (Service-Oriented Vehicle Diagnostics) entity trees from ros2_medkit gateway. The UI provides an entity browser for exploring ROS 2 diagnostics data exposed via the SOVD REST API.

## Architecture

```
src/
├── components/ # React components
│ ├── ui/ # shadcn/ui primitives (Button, Card, Dialog, etc.)
│ ├── EntityTreeSidebar.tsx # Main navigation tree
│ ├── EntityDetailPanel.tsx # Entity details view
│ ├── OperationsPanel.tsx # ROS 2 service/action invocation
│ ├── ConfigurationPanel.tsx # ROS 2 parameter management
│ └── DataFolderPanel.tsx # Topic subscriptions
├── lib/
│ ├── sovd-api.ts # Typed HTTP client for gateway REST API
│ ├── store.ts # Zustand state management
│ ├── types.ts # TypeScript interfaces for API types
│ ├── schema-utils.ts # JSON Schema utilities
│ └── utils.ts # Utility functions
└── test/
└── setup.ts # Vitest setup
```

## Key Patterns

### State Management (Zustand)

```typescript
// src/lib/store.ts
export const useStore = create<AppState>()(
persist(
(set, get) => ({
// Connection state
serverUrl: 'http://localhost:8080',
// Entity tree
entities: [],
selectedEntityPath: null,
// Actions
selectEntity: (path) => set({ selectedEntityPath: path }),
}),
{ name: 'sovd-ui-storage' }
)
);
```

### API Client

```typescript
// src/lib/sovd-api.ts
export class SovdApiClient {
constructor(private baseUrl: string) {}

async getComponents(): Promise<Component[]> {
const response = await fetch(`${this.baseUrl}/api/v1/components`);
return response.json();
}
}
```

## Conventions

- Use Zustand for client state
- All API types defined in `lib/types.ts`
- Use `@/` path alias for imports from src
- Prefer composition over inheritance
- Use shadcn/ui components from `components/ui/`
- Format with Prettier (automatic via husky pre-commit)

## Testing

- Unit tests: `*.test.ts` next to source files
- Integration tests: `src/test/integration/`
- Use `@testing-library/react` for component tests
- Run tests: `npm test`

## Gateway API Reference

Default base URL: `http://localhost:8080/api/v1`

| Method | Endpoint | Description |
| ------ | ---------------------------------------- | ------------------------------------ |
| GET | `/areas` | List all areas (namespace groupings) |
| GET | `/components` | List all components |
| GET | `/apps` | List all apps (ROS 2 nodes) |
| GET | `/components/{id}/data` | List data topics for component |
| GET | `/components/{id}/operations` | List operations (services/actions) |
| GET | `/components/{id}/configurations` | List configurations (parameters) |
| POST | `/components/{id}/operations/{name}` | Call operation |
| PUT | `/components/{id}/configurations/{name}` | Update configuration |

## Important Notes

- This UI connects to `ros2_medkit_gateway` running on port 8080
- Entity IDs are alphanumeric + underscore + hyphen only
- Virtual folders (data/, operations/, configurations/) are UI constructs, not API entities
65 changes: 44 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,54 @@
name: CI

on:
pull_request:
branches: [main]
push:
branches: [main]
pull_request:
branches: [main]
push:
branches: [main]

jobs:
build-and-test:
runs-on: ubuntu-latest
timeout-minutes: 15
check:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout repository
uses: actions/checkout@v4
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci
- name: Install dependencies
run: npm ci

- name: Run linting
run: npm run lint
- name: Check formatting
run: npm run format:check

- name: Build project
run: npm run build
- name: Run linting
run: npm run lint

- name: Type check
run: npm run typecheck

- name: Run tests
run: npm test -- --run

- name: Build project
run: npm run build

docker-build:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build Docker image
run: docker build -t sovd-web-ui:test -f Dockerfile .
108 changes: 54 additions & 54 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -1,66 +1,66 @@
name: Docker Publish

on:
pull_request:
branches:
- main
push:
branches:
- main
tags:
- 'v*'
pull_request:
branches:
- main
push:
branches:
- main
tags:
- 'v*'

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
# latest on main branch
type=raw,value=latest,enable={{is_default_branch}}
# version tag (v1.2.3 -> 1.2.3)
type=semver,pattern={{version}}
# major.minor (v1.2.3 -> 1.2)
type=semver,pattern={{major}}.{{minor}}
# major only (v1.2.3 -> 1)
type=semver,pattern={{major}}
# sha for traceability
type=sha,prefix=sha-,format=short
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
# latest on main branch
type=raw,value=latest,enable={{is_default_branch}}
# version tag (v1.2.3 -> 1.2.3)
type=semver,pattern={{version}}
# major.minor (v1.2.3 -> 1.2)
type=semver,pattern={{major}}.{{minor}}
# major only (v1.2.3 -> 1)
type=semver,pattern={{major}}
# sha for traceability
type=sha,prefix=sha-,format=short

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# amd64 only - arm64 fails due to QEMU emulation issues with npm ci
platforms: linux/amd64
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# amd64 only - arm64 fails due to QEMU emulation issues with npm ci
platforms: linux/amd64
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
Loading