* feat(ui): add global command palette (Ctrl+K) for fast navigation and search
* fix(ui): address review feedback for shortcut listener, i18n parity, and search deep links
* fix(ui): resolve search routing, translation keys, and palette state reset
* fix(ui): improve command palette styling and sidebar transitions
* fix(ui): address review feedback for typecheck, codegen, debouncing, and state reset
* fix(ui): resolve effect state update warning and debounce reset in command palette
* fix(ui): address review feedback for stale client search results and theme action
* style(ui): apply oxfmt formatting to command palette and tests
* fix(deps): update js-yaml override to resolve audit advisory
* docs(api): sync the docs OpenAPI copy with the new InboundOption fields
Adding Network/Security to InboundOption regenerated
frontend/public/openapi.json, but docs/public/openapi.json is a
hand-kept copy of that file and nothing checks it: make verify never
reaches docs/, and docs-ci.yml fires only on docs/**. The two files were
byte-identical on main and had diverged here, so the published API
reference described a response shape the panel no longer returns.
Regenerating the MDX under docs/content/docs/en/reference/api/ produced
no change — the schema is read from the JSON at render time.
* fix(ui): unnest the command palette row control and label its shortcut
The palette row was a <button> wrapping the copy-subscription <button>.
Nested interactive content is invalid HTML and React 19 logs two errors
for it on every client result. The row is now a role="button" div using
activateOnKey, the pattern the rest of the panel already uses, with
line-height pinned so dropping the UA button style does not grow every
row. Its keydown handler ignores events bubbling from the nested button:
activateOnKey preventDefaults Enter, which would otherwise cancel the
browser's Enter-to-click on the copy button and navigate instead.
The sidebar chip hardcoded the Mac glyph while the handler accepts Ctrl
as well, so Linux and Windows operators were shown a key they do not
have; it now picks the modifier from the platform.
Also restores the comment on ClientsPage's debouncedSearch that the
deep-link change removed — the code it explains is unchanged.
When a client belongs to multiple AmneziaWG or WireGuard inbounds (e.g. across
remote nodes), findAmneziaWGInbounds and findWireguardInbounds only returned the
first matching inbound. Consequently, ClientInfoModal and ClientQrModal rendered
only one config block, making other inbounds' configs unreachable.
- Add findAmneziaWGInbounds and findWireguardInbounds returning all matching inbounds
- Add formatTunnelConfigMeta helper to unify label, fileName, and qrRemark resolution
- Support addressOverride in buildWireguardClientConfig from tunnelAllowedIPs
- Render all tunnel configs in ClientInfoModal and ClientQrModal with node remarks
- Distinguish download filenames with inbound remark suffix to avoid collisions
- Add component integration tests covering multi-inbound modal rendering