| 2026-11-07 |
webclient: make the protocol proposal card a document, not a kit tile
...
GnCard is a 340px tile with an uppercased title and GnSteps uppercases
every step, so a protocol document came out as a narrow column of caps with
its body shown as raw markdown. The card is a chat domain card now: an
app-owned full-width frame (accented left edge for pending/accepted/
rejected) with the kit's buttons, alert, chips, badge and loader.
- shows what was missing: when to use it (triggers), preconditions, steps
with their action and check, post-checks, and the body as rendered
markdown under "Context";
- unwraps a step the model wrote as a YAML mapping, which the parser turns
into Python repr text, so the step reads instead of leaking {'id': ...};
- says "Add to catalog" and shows an "In the catalog" badge once decided.
Eugene Sukhodolskiy
committed
1 day ago
|
webclient: reach the socket from cards, and draw the proposal card with the kit
...
The skill-proposal card sent its decision through inject('wsSend'), but no
ancestor ever provided it — the click went nowhere and the card reported
"No connection". Form.vue carried the same latent bug since 9dfdb48.
- wsBridge.js: a module-level bridge. useWebSocket registers its send on
setup and clears it on unmount; cards call wsSend() and get false back
instead of silently dropping the payload.
- Form.vue uses the bridge and reports a refused send without marking the
form submitted.
- SkillProposal.vue is rebuilt on the kit (GnCard, GnAlert, GnChip, GnList,
GnSteps, GnDescriptionList, GnLoader, GnButton) and exports `bare`, so
UiComponentCard no longer wraps it in a second frame with a dev header.
- registry.js tracks bare components; UiComponentCard renders them as-is.
Eugene Sukhodolskiy
committed
1 day ago
|

skills: agent-written skills and protocol proposals with chat buttons
...
A skill the agent writes now lands in the catalog on its own; a protocol
never does. `skills(op="propose")` stores the document in `skill_proposals`
and returns a card through the tool result's `ui_component` metadata — no
`render_component` call, so profiles without `navi_ui` get the card too.
The decision is the server's: the browser sends `skill_proposal` over the
WebSocket, `SkillStore.decide_proposal` writes the catalog row from the
*stored* document, names the clicking user as the author, and answers with
`skill_proposal_decided`. The agent learns the outcome as a hidden user
message, the same path a `navi_ui` form submission takes.
`create`/`update` refuse protocol documents and point at `propose` with
`replaces_id`, so the version history of a protocol always starts from a
user's yes. The chat store now renders `ui_component` from any tool whose
result carries one, not only from `mcp__navi_ui__render_component`.
Eugene Sukhodolskiy
committed
1 day ago
|
| 2026-11-02 |
webclient: one audio player, so a published card sounds like the viewer
...
The published-content card fell back to the browser's own grey bar while the
full-screen viewer had a player with the track's name, times, seek and
volume. The card now shows that player too, tightened a little for a body
that sits under a summary row.
The player moved out of ContentLightbox unchanged: two copies would have
drifted, and it is the same file in both places.
Eugene Sukhodolskiy
committed
6 days ago
|
| 2026-11-01 |

webclient: a viewer for every content type, and a message sent mid-turn waits its course
...
The lightbox is now one dialog for every file the artifacts panel and the
content cards can show: audio and video players on the kit's own controls, a
text/source view with highlight.js, and a three.js viewer that replaced the
standalone stl.html frame with a model one. highlight.js emits .hljs-* classes
and ships no theme, so until now every highlighted block rendered in one flat
colour — app.scss now carries the palette.
While a turn runs, the send button no longer turns into Stop and vanishes the
draft with it: the message goes into the socket and is marked "queued" until
the stream_start of the turn that consumes it, the write-now-run-next model
navi-code uses. Send and Stop sit side by side, and the queue chip takes the
larger of the local and the server's counts.
The socket keeps its own liveness: a ping every 20 s, a watchdog that reopens a
socket silent for 45 s or stuck in CONNECTING, and a reconnect on resume from
background, network return or bfcache. On reopen it drops a half-written
message left by the dead socket, so the session_sync that follows is not hidden
behind a stream that will never finish.
Eugene Sukhodolskiy
committed
6 days ago
|
| 2026-10-10 |

webclient: the push permission is asked once a session, after signing in
...
The permission was only ever asked from the switch in Settings, and a call
without a user gesture is ignored by Firefox and Safari and answered with a
quiet icon by Chrome — so the app now offers a bar whose button click is that
gesture. It is asked once per browser session (there is no marker that tells a
fresh sign-in from a reload, so the session is the honest bound), and a
refusal ends it for good: only an explicit block counts, and after that the
switch in Settings is the single way back to it.
The policy lives in useNotificationPrompt.js as a layer over usePush, which
is untouched: the granted-but-unsubscribed trap is what makes the eligibility
gate "permission is still default", otherwise the bar would silently
re-subscribe whoever turned notifications off by hand. The banner itself is
presentation only, and is the component proposed to gnexus-ui-kit
(docs/tasks/gn-permission-banner.md there) — the kit takes the gesture, the
view and the contract, never the storage keys or the timing.
Eugene Sukhodolskiy
committed
29 days ago
|
| 2026-10-05 |
webclient: W4 — chat empty state -> GnEmptyState, card modal -> GnModal
...
- ChatArea: .empty-chat custom icon+text -> GnEmptyState card
(keep .empty-chat wrapper only for centering)
- CardGrid: inline Teleport modal (overlay/close/scroll bespoke chrome
removed) -> GnModal; .card-modal/.card-modal-title class names kept
inside the new DOM (test contract); kit header slot renders the title
- WelcomeScreen: typography aligned to kit (30px h1, 14px base),
profile cards get -radius-md, dead .profile-icon style dropped
- CardGrid test: mount with the kit plugin (GnModal resolves),
two nextTicks for the post-flush open watcher
Verified: vitest 111 passed, build OK, welcome screenshot unchanged
(modal/empty-state visually checked after final build)
Eugene Sukhodolskiy
committed
on 5 Oct
|
| 2026-07-09 |

feat: integrate navi_ui MCP server (card_grid + form) into master
...
Port the internal navi_ui MCP server from the vmkdemo branch (it never
landed on master). The server exposes render_component, which returns a
structured JSON envelope; navi/mcp/tools.py extracts metadata.ui_component
onto the role="tool" message, and the webclient renders the component
(card_grid, form) inline inside the assistant turn.
Backend
- navi/mcp/ui_server/: FastMCP server + component registry with card_grid
and form components (pydantic-validated payloads, LLM-friendly schema docs)
- mcp_servers.d/navi_ui.json: streamable_http config, group "ui"
- config.py: navi_ui_mcp_enabled/host/port flags
- main.py: start UI server in lifespan (task + wait-for-ready + cancel)
- mcp/tools.py: navi_ui envelope parsing; "Error:" results surface as
failed tool calls so the UI card is not green
- orchestrator.py + agent.py: run_stream(hidden=) for form submissions
(single is_display=False, is_context=True user message)
- api/websocket.py: extract _start_agent_run helper, add form_submit
branch that delivers submitted form values as a hidden user message
- profiles/secretary: enable navi_ui "ui" group (agent + subagent)
- .env.example: NAVI_UI_MCP_* flags
- tests/unit/mcp/test_ui_server.py
Webclient
- components/ui/{registry.js,CardGrid.vue,Form.vue}: auto-discovered
renderers (snake_case <-> PascalCase aliasing)
- components/messages/UiComponentCard.vue: wrapper rendered in
AssistantMessage when entry.kind === 'ui_component'
- stores/chat.js: extract ui_component from tool_call metadata in both
live stream and history-replay paths
- composables/useWebSocket.js: note that ui_component rides tool_call
- tests/unit/components/ui/* (17 tests)
- dist rebuilt
Excluded from the vmkdemo port: the stale single-file navi/mcp/ui_server.py
duplicate, the realtor profile, and the vmk_data server (unrelated real
estate work that was interleaved with navi_ui on vmkdemo).
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 Jul
|