Skip to content

[go][js] update /api/capabilities#430

Open
Copilot wants to merge 7 commits into
mainfrom
copilot/update-capabilities-check-radar-mode
Open

[go][js] update /api/capabilities#430
Copilot wants to merge 7 commits into
mainfrom
copilot/update-capabilities-check-radar-mode

Conversation

Copilot AI commented Mar 24, 2026

Copy link
Copy Markdown
Contributor
  • Explore current capabilities API format (Go structs, TS types, frontend, tests)
  • Create docs/plans/api-multi-sensor-capabilities-plan.md with the agreed format (radar/lidar named objects, no _sensors suffix)
  • Implement Go backend changes (types, handler, provider)
  • Implement frontend changes (TS types, store, layout)
  • Update all tests (Go and web)
  • Smart polling: only poll when LiDAR hardware present; static when radar-only
  • Handle empty radar/lidar maps gracefully (dev, prod scenarios)
  • 100% test coverage on capabilities store (25 tests, all branches)
  • Go test coverage: empty-maps, multi-sensor, sweep-disabled edge cases
  • All 647 web tests pass, all Go capabilities tests pass
  • Code review feedback addressed

💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.

Copilot AI and others added 4 commits March 24, 2026 07:50
Replace the flat capabilities response shape with per-sensor-class
maps keyed by a stable sensor name (e.g. 'default').  This allows
multiple sensors of the same class (radar, lidar) to coexist in
future without breaking the API contract.

Wire changes:
- SensorStatus / LidarSensorStatus replace LidarCapability
- Capabilities.Radar is now map[string]SensorStatus
- Capabilities.Lidar is now map[string]LidarSensorStatus
- 'State' field renamed to 'Status'; top-level LidarSweep removed
- Default fallback in showCapabilities updated
- capabilitiesProvider.Capabilities() builds named-object maps
- All tests updated for the new shape
Align the Svelte frontend with the new /api/capabilities JSON shape
where radar and lidar are Record<string, SensorStatus> maps keyed by
sensor name, replacing the flat boolean/object fields.

- api.ts: replace LidarCapability with SensorStatus and
  LidarSensorStatus; Capabilities uses Record maps
- capabilities.ts: default to { radar: { default: ... }, lidar: {} };
  derive lidarEnabled via Object.values().some()
- +layout.svelte: gate LiDAR nav items on the new map shape
- api.test.ts, capabilities.test.ts: update all test data to the new
  shape
@ddol ddol marked this pull request as ready for review March 24, 2026 08:08
Copilot AI review requested due to automatic review settings March 24, 2026 08:08
@github-actions

github-actions Bot commented Mar 24, 2026

Copy link
Copy Markdown

🤖 Version Bump Advisory

Warnings

Web Frontend code changed but version unchanged - update web/package.json


📖 See CHANGELOG.md for detailed guidelines.

This is an automated advisory. Review the detected changes and update versions accordingly.

@codecov

codecov Bot commented Mar 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ddol ddol changed the title Multi-sensor capabilities API: named-object maps per sensor class [go][js] update /api/capabilities Mar 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Redesigns the /api/capabilities contract to support multiple named sensors per sensor class (radar/LiDAR) by switching from a flat single-sensor shape to map[string]T / Record<string, T> keyed by sensor name, and updates the frontend gating + tests accordingly.

Changes:

  • Backend: replace flat Radar bool / LidarCapability with Radar map[string]SensorStatus and Lidar map[string]LidarSensorStatus, plus updated defaults/providers/tests.
  • Frontend: update Capabilities typings and derived stores to operate on named-object maps; update nav gating.
  • Docs/tests: add a plan doc describing the new format; update Go/TS test fixtures for the new shape.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
internal/api/server.go Introduces SensorStatus/LidarSensorStatus and changes Capabilities fields to named maps.
internal/api/server_admin.go Updates the default /api/capabilities fallback response to the new map-based shape.
cmd/radar/capabilities.go Updates the runtime capabilities provider to emit radar.default and optional lidar.default.
internal/api/capabilities_test.go Updates API handler tests for the new JSON shape and map access.
cmd/radar/capabilities_test.go Updates provider tests for map-based capabilities.
web/src/lib/api.ts Updates TypeScript API types to map-based Capabilities with per-sensor status interfaces.
web/src/lib/api.test.ts Updates API client tests to the new capabilities response shape.
web/src/lib/stores/capabilities.ts Updates default capabilities + derived stores (lidarEnabled, lidarState) for map semantics.
web/src/lib/stores/capabilities.test.ts Updates store tests for map-based capabilities and derived logic.
web/src/routes/+layout.svelte Updates LiDAR nav gating to check Object.values($capabilities.lidar).
docs/plans/api-multi-sensor-capabilities-plan.md Adds a plan/spec doc for the multi-sensor capabilities redesign.

Comment on lines 43 to 48
caps := Capabilities{
Radar: true,
Lidar: LidarCapability{
Enabled: false,
State: "disabled",
Radar: map[string]SensorStatus{
"default": {Enabled: true, Status: "receiving"},
},
LidarSweep: false,
Lidar: map[string]LidarSensorStatus{},
}

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This default response now represents LiDAR absence as an empty map (lidar: {}), which is semantically "no LiDAR configured" rather than an explicit disabled sensor. The doc comment above showCapabilities still says "LiDAR as disabled"; consider updating it to reflect the empty-map contract to avoid confusion for API consumers.

Copilot uses AI. Check for mistakes.
Comment thread web/src/lib/api.ts
Comment thread internal/api/server.go
Comment thread docs/plans/api-multi-sensor-capabilities-plan.md Outdated
Comment on lines +33 to +39
"radar": {
"default": {
"enabled": true,
"status": "receiving",
"last_reading_at": "2026-03-24T06:45:12Z"
}
},

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan/examples and type definitions include a last_reading_at field, but the implemented Go/TypeScript SensorStatus types in this PR do not include it. Either implement last_reading_at end-to-end or remove it from the plan/spec so the documentation matches the shipped API.

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +97
| `enabled` | `bool` | Sensor channel was activated at startup |
| `status` | `string` | Runtime state: `disabled`, `starting`, `receiving`, `stale`, `error` |
| `last_reading_at` | `string \| null` | ISO 8601 timestamp of last data received; `null` = never |
| `sweep` | `bool` | (lidar only) Sweep/auto-tuner operational |

### State machine

```
disabled → starting → receiving ⇄ stale
↘ error
```

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan defines the status state machine/literals around "receiving"/"stale" and does not mention "ready", but the current implementation/provider emits "ready" for LiDAR. Please reconcile the documented status vocabulary/state machine with what the API actually returns (update the plan or adjust the implementation/tests).

Copilot uses AI. Check for mistakes.
Comment thread docs/plans/api-multi-sensor-capabilities-plan.md Outdated
Copilot AI requested a review from ddol March 24, 2026 08:34
Copilot finished work on behalf of ddol March 24, 2026 08:34
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: David Dolphin <445312+ddol@users.noreply.github.com>
ddol added a commit that referenced this pull request Apr 28, 2026
Both items were deferred from PR 480 because they are out of scope for
the embed-stub plumbing this branch fixes:

- #483 (v0.5.2, S): Node baseline decision for `docs_html` build.
- #482 (v0.5.3, S): surface offline docs URL from backend so the Web UI
  link tracks `--docs-listen`. Sits next to existing #430 capabilities
  redesign work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ddol added a commit that referenced this pull request Apr 29, 2026
Both items were deferred from PR 480 because they are out of scope for
the embed-stub plumbing this branch fixes:

- #483 (v0.5.2, S): Node baseline decision for `docs_html` build.
- #482 (v0.5.3, S): surface offline docs URL from backend so the Web UI
  link tracks `--docs-listen`. Sits next to existing #430 capabilities
  redesign work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ddol added a commit that referenced this pull request Apr 29, 2026
* [docs] Add offline documentation site structure and styles

- Created build.js to manage versioning and build metadata.
- Added sidebar.njk for navigation structure in the documentation.
- Implemented base layout in base.njk for consistent page structure.
- Introduced site.css for styling the documentation site.
- Created index.njk as the main entry point for the offline documentation.
- Added stub-index.html to inform users when documentation is not built.

* [js] Implement offlineDocsUrl function and corresponding tests

* [go] Implement embedded offline documentation server and validation

* [js] Add Docs navigation item and update offlineDocsUrl handling

* [make][sh] Add offline documentation build and management scripts

* Potential fix for pull request finding 'CodeQL / Incomplete multi-character sanitization'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Signed-off-by: David Dolphin <445312+ddol@users.noreply.github.com>

* [ai][ci] add offline docs embed stub step to Go CI jobs

The new `assets.go` embed pattern `all:docs_html/_site` requires the
directory to exist at compile time. CI was only running
`scripts/ensure-web-stub.sh`, so the build job failed with
`pattern all:docs_html/_site: no matching files found`.

Add `scripts/ensure-docs-stub.sh` to build, test-core, test-lidar, and
test-integration so all jobs that compile Go code see a populated stub.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][make][js] make offline docs lint non-mutating and arg-tolerant

Three related changes addressing PR review:

- `make test-go` and `make test-go-cov` now invoke
  `scripts/ensure-web-stub.sh` and `scripts/ensure-docs-stub.sh` first,
  so the documented `make test` quality gate works on a clean checkout.

- `lint-docs-offline` no longer depends on `build-docs-offline`. It now
  delegates to `check-docs-offline-links`, keeping `make lint` purely
  non-mutating. Use `make build-docs-offline` (which still calls
  `check-docs-offline-links` under the hood) to regenerate the rendered
  site before linting if desired.

- `scripts/check-docs-offline-links.js` parses argv flag-tolerantly: a
  flag like `--strict-anchors` no longer gets misread as the positional
  `siteRoot`. The cheerio require is deferred so a stub-only site (or a
  fresh checkout without `make install-docs-offline`) skips gracefully
  rather than crashing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][docs] render offline docs index as Markdown

The homepage was authored as Markdown (`# Offline Documentation`,
Markdown links) but lived in `index.njk`. Eleventy is configured with
`htmlTemplateEngine: "njk"` and `markdownTemplateEngine: false`, so
`.njk` files were rendered as Nunjucks/HTML and the Markdown surfaced
verbatim in the generated page.

Rename to `index.md` so Eleventy runs the file through markdown-it. The
existing global layout (`base.njk`) still applies via
`addGlobalData("layout", "base.njk")`, so visual output is unchanged
apart from the headings and links now rendering correctly.

`.gitignore` previously ignored every `src/*.md` (the symlinked tree
generated by `scripts/docs-offline-symlinks.sh`). Add a negation for
`src/index.md` so the committed homepage is tracked.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][docs] track follow-ups from PR 480 review in backlog

Both items were deferred from PR 480 because they are out of scope for
the embed-stub plumbing this branch fixes:

- #483 (v0.5.2, S): Node baseline decision for `docs_html` build.
- #482 (v0.5.3, S): surface offline docs URL from backend so the Web UI
  link tracks `--docs-listen`. Sits next to existing #430 capabilities
  redesign work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][js] copy mermaid ESM chunks into offline docs site

The mermaid ESM bundle is split into ~80 chunks under
`node_modules/mermaid/dist/chunks/mermaid.esm.min/`. The eleventy
passthrough copy only forwarded the entry file `mermaid.esm.min.mjs`,
so the browser loaded the entry, then 404'd on every relative
`./chunks/mermaid.esm.min/chunk-*.mjs` import: no diagrams rendered.

Add an `eleventy.after` hook that copies each `.mjs` chunk into
`_site/assets/chunks/mermaid.esm.min/`. Skip the matching `.map`
sourcemap files (~11 MB) so they don't inflate the embedded binary.

Validated locally with the dev server: 81 chunks resolve over HTTP and
the chunk import graph is closed (0 unresolved imports).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][docs] add dark mode toggle and sticky sidebar header

- Define a dark token set under `[data-theme="dark"]` and convert the
  hardcoded mermaid block background to use the panel token. Default
  is light; explicit choice persists in localStorage as `docs-theme`,
  otherwise falls back to `prefers-color-scheme`.
- Add a no-flash inline script in `<head>` that sets `data-theme` on
  `<html>` before the body paints, so dark-mode users never see a
  light-mode flash on navigation.
- Wrap brand + theme toggle in `.sidebar-header` with
  `position: sticky; top: 0` inside the scrollable sidebar, so the
  toggle always sits at the top of the left column even as the nav
  scrolls. Negative horizontal margin extends the header background
  to the sidebar's borders.
- Sun/moon SVG icons (no emoji per repo style); button has
  aria-label/title for screen readers and exposes focus-visible
  styling.
- Mermaid is initialised with `startOnLoad: false` and re-rendered via
  `mermaid.run()` on theme change, switching between the `neutral`
  and `dark` mermaid themes so diagram colours track the page theme.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][js][docs] render LaTeX math in offline docs via KaTeX

Equations like $v_{\text{max}}$ and $\mathbf{x}$ in `data/maths/`
previously surfaced as raw `$...$` and `$$...$$` in the rendered
offline docs. Add KaTeX server-side rendering so the maths displays as
proper notation without requiring internet at runtime.

- Add `markdown-it-texmath` (KaTeX engine, `dollars` delimiters) to
  the markdown-it pipeline in `.eleventy.js`. `throwOnError: false`
  keeps a single malformed equation from breaking the whole build —
  the offending block falls back to the source text.
- Pass through `katex.min.css` and the full `dist/fonts/` directory
  (woff2 + woff + ttf, ~1.1 MB) so the rendered glyphs work offline
  for any browser the operator might be using.
- Link `katex.min.css` from `base.njk` before `site.css` so site
  styling can override KaTeX defaults if needed later.

Validated locally: the page at `/data/maths/classification-maths/`
emits 56 inline `katex` spans + 8 `katex-display` blocks and 0
`katex-error` spans; `\mathbf` and `\text` render to the expected
`mord mathbf` and `mord text mtight` classes; the offline link checker
still passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][docs] update offline docs plan with PR #480 implementation status

Bump the status header from "Draft" to track current state and add a
new §9 ("Implementation status, 2026-04-28") covering:

- What landed in PR #480 against each phase, including divergences
  from the plan's recommendations (notably client-side Mermaid render
  instead of build-time SVG pre-render — trade-off explained inline).
- Resolved §7 open questions (Q2/Q3/Q4/Q5/Q6/Q8).
- Open follow-ups deliberately not in PR #480, with links to the
  tracking issues #482 (capabilities-sourced docs URL) and #483 (Node
  baseline decision), plus the Phase 4/5 items still outstanding.

The plan stays as the authored design record; §9 captures actual
state without rewriting the original sections.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][js][docs] tree-view sidebar with collapse/expand and breadcrumb topbar

Three related navigation changes that travel together — the tree
relies on the new filter, the breadcrumb relies on the new filter,
and the styling/JS only make sense once both filters exist.

Sidebar tree view (replaces the flat grouped list):

- New `buildDocsTree` walks the docsPages collection by URL segments,
  producing a hierarchical tree where each node carries its own URL
  (when a page exists at that path, e.g. a folder's README.md) plus
  its children. Nodes with no corresponding page render as
  expand-only labels.
- `sidebar.njk` renders the tree with a recursive Nunjucks macro,
  using native `<details>`/`<summary>` for expand/collapse so it
  works without JS. A small chevron pseudo-element rotates 90°
  when the folder is open.
- The branch leading to the current page is server-side flagged
  via `hasCurrent` so those `<details>` start `open` on first load.
- Manual expand/collapse state persists in `localStorage`
  (`docs-tree-open`, keyed by tree path) so navigating between pages
  doesn't lose the operator's reading context.

Topbar breadcrumbs (replaces "Offline documentation" + README link):

- New `buildBreadcrumbs` filter splits the current page URL into
  segments, links each parent folder iff a page exists at that path
  (so `/docs/` links if the docs README is rendered, otherwise the
  segment shows as plain text), and leaves the last segment as the
  unlinked current crumb.
- The topbar emits the trail with `/` separators and a leading
  "Offline docs" link to /. Styling is added in `site.css`; the
  topbar's `justify-content` switches from `space-between` to
  `flex-start` since the right-hand README link has been removed
  in favour of the breadcrumb.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [fs] add symlink for data/explore/README.md

* [ai][docs] suppress stale relative-link reports for runtime-resolved hrefs

Two unrelated places trip `scripts/check-relative-links.py` even
though the links are correct in their respective contexts:

- `docs_html/src/index.md` lists Markdown links into the symlinked
  content tree (README.md, ARCHITECTURE.md, etc.). Eleventy follows
  the symlinks at build time and the .md → / rewriter rewrites them
  to served URLs, but the source-tree link checker doesn't see the
  symlinks. Mark each line with `<!-- link-ignore -->`.

- `docs/plans/embedded-offline-docs-site.md:376` quotes the strings
  `[x](../foo)` and `[x](dir/)` as illustrative examples of what the
  rewriter handles. The checker treats them as real links. Mark the
  line with `<!-- link-ignore -->`.

`python3 scripts/check-relative-links.py` now passes locally.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][js] compute Docs nav URL synchronously from page.url

`docsUrl` was initialised to `'/'` and only updated inside `onMount`,
so on first paint the Docs nav item briefly pointed at the app root.
Clicking quickly (or with a delayed JS load) navigated to the
dashboard instead of the offline docs.

Switch to a `$derived` value sourced from `page.url.href`, which is
populated both server-side and at component init on the client. The
URL is now correct on the very first render with no flicker, and
SvelteKit's reactive `page` store keeps it in sync across navigation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][docs] dynamic aria-label on theme toggle reflects intended action

The theme toggle's aria-label/title was hard-coded to "Toggle dark
mode" but the button switches in either direction. Misleading for
assistive tech and tooltips.

Switch to a dynamic label managed by the existing toggle script:
"Switch to dark mode" when the current theme is light, and "Switch
to light mode" when the current theme is dark. The static fallback
in the markup is "Toggle theme" so users without JS see a neutral
label rather than a wrong one.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][go] split docsite.Start into Run(listener) to fix test port race

The original `TestStartShutdown` did `net.Listen(":0") → addr →
listener.Close() → Start(addr)` to discover an ephemeral port. There
is a TOCTOU race in that sequence: another process can claim the
freed port before `Start` re-binds, making the test flaky.

Refactor:

- `docsite.Start(ctx, listen, source, diskDir)` is now a thin wrapper
  that calls `net.Listen` and forwards to `Run`. Existing call site
  in `cmd/radar/radar.go` is unchanged.
- `docsite.Run(ctx, listener, source, diskDir)` is the new
  test-friendly entry point: it takes ownership of a pre-bound
  listener and serves on it. No close-then-rebind race.
- `TestStartShutdown` is renamed to `TestRunShutdown` and uses `Run`
  with the same listener it created — no Close call between bind
  and serve.

`go test ./internal/docsite/...` passes; lint clean; binary builds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [ai][docs] address PR 480 review follow-ups

* [docs] run 8083

* [docs] add embed stub for offline docs site and update related scripts

* [docs] enhance offline documentation integration and routing

* [docs] move from :8030 to /docs

* [ai] update CLAUDE.md with new build and development commands for offline docs

Co-authored-by: Copilot <copilot@github.com>

* [ver] bump version to 0.5.1-pre12 across all relevant files

* [docs] update CLAUDE.md system diagram

* [docs] clarify embedded offline docs section in .dockerignore

Co-authored-by: Copilot <copilot@github.com>

* [docs] update .dockerignore and .eleventy.js to exclude non-essential files and raw PDFs from embedded offline docs

Co-authored-by: Copilot <copilot@github.com>

* [docs] update embedded offline docs site status and clarify Milestone 2 plans

Co-authored-by: Copilot <copilot@github.com>

---------

Signed-off-by: David Dolphin <445312+ddol@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Copilot <copilot@github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants