| 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
21 hours ago
|
webclient: a dialog that answers which model is serving, and from where
...
The header only named a model when the route had fallen back, so "what is
running this session, on which host, with how much context" had no answer
in the UI. The context percentage doubles as the second way in: a session
bar is a fair place to ask what the bar measures.
GET /sessions/{id}/info reads in-process state only — the active route
entry, the health chain, the profile's effective window, run and client
counts — and the dialog polls it while open. /meta and the session head
now report the route's window instead of the global ollama_num_ctx, which
had been wrong for every cloud-backed profile.
Eugene Sukhodolskiy
committed
21 hours ago
|
| 2026-11-01 |

llm: routes that name a host, a backup and a window, and a profile that reads them
...
`llm.json` replaces `OLLAMA_BACKENDS_FILE`. It has targets (host and key
environment variable) and routes, where a route is an ordered chain of
`model @ target` entries, each with its own `context_window` and options. The
`default` route now starts on `glm-5.3-flash:cloud` and backs it with
`qwen3.6:35b-128k` on the home host, both at 131072 tokens, so a failover no
longer changes the budget the compressor and `num_ctx` are accounted against.
Embeddings get their own `embedding` route instead of a pinned host.
The engine classifies a failure (network, quota, auth, server, missing model),
cools that entry and moves the call down the chain; a background probe watches
only degraded targets and lets the next turn return to the primary once it is
healthy — it never interrupts a running turn. A profile joins a route with
`llm_route`, and the loader now actually reads it: `llm_route` and
`context_window` were declared on `AgentProfile` and written into every
`config.json` while the loader skipped both, the same silent no-op that
`is_admin_only` used to be, so every profile ran the legacy model list at the
global window. The writer emits them too, or an admin edit would erase the
route from disk.
The turn's resolved entry travels to the UI: the first chunk is stamped with
model and target, `model_info` carries `fallback` when a backup answered, and
the chat header shows a chip only then. `serving_primary` compares the target
as well as the model name, because a route may run one model on two hosts.
`/health` gains an `llm` block with the active entry, target states and missing
models.
`deploy/ollama/` keeps the local model's definition honest: MTP off (slower at
every context on this host), `presence_penalty 0` (the base model's 1.5 breaks
agentic tool calls), and `num_gpu 40` — Ollama's automatic fit claims a 128k
f16 slot fits and then dies mid-prefill with CUDA OOM; 40 of 42 layers leaves
~400 MiB and decodes at ~77 tok/s, 38 layers collapses to 48, and 39 OOMs.
q8_0 KV measured 37 tok/s against 70 on f16 here, so the cache stays f16.
Eugene Sukhodolskiy
committed
23 hours ago
|
profiles: the Personal Secretary retires, and every reference goes with it
...
`secretary` had become a second general-purpose assistant: same models, same
tools, one temperature step apart from `assistant`, and admin-only for no
reason the user could see. The directory is gone and the eight remaining
profiles load clean.
A profile that no longer exists is not a harmless orphan — `Agent` resolves
`session.profile_id` on every run and raises `ProfileNotFound`, so any session
left behind would open and then fail on its first turn. Prod sessions were
moved to `assistant` first (115 rows, verified after), and the local clone
along with them (101).
The reaction fallback loses its role table: it existed because the admin
fallback was itself admin-only, which `assistant` never is. `spawn_agent`
falls back to `assistant` too, and the two prompts that recommended
`secretary` now name only `server_admin` and `developer`.
Eugene Sukhodolskiy
committed
1 day ago
|

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
1 day 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
23 days ago
|

webclient: the small UI components in en, uk and ru
...
The sixth and last area of the interface translation. The components in
components/ui/ now build their text through t(): the login screen, the welcome
screen, the confirmation dialog, the offline banner, the image lightbox, the
selection toolbar, the card grid and the form, plus the sidebar's close button,
which is one of the strings that moved.
24 new slugs in all three dictionaries, and one moved:
- 24 in ui.* — the login and welcome screens (the heading, the tagline they
both show, the GNEXUS button), the confirmation dialog, the offline banner,
the lightbox (its heading, its close button, the failed-to-load message), the
selection toolbar's Reply and its tooltip, the card grid's three section
headings and its "+{n} more", and the form's submit button, its submitted
note and its ten validation messages;
- common.close, moved out of sidebar.*: the sidebar's close button and the
lightbox's say the same word, so it sits in the vocabulary area now.
Four details worth knowing:
- OfflineBanner's sentence was hard-coded Russian inside an otherwise English
file — for a reader in English or Ukrainian it was the one Russian sentence
in the app. It is a slug now, and the Russian value is that same sentence
verbatim, so nothing changes for the reader who could already read it.
- The form's validation messages interpolate: "{field} is required" carries the
field's own label, and the numeric limits carry {n}. The two length messages
are dictionary plurals now, so English says "Minimum length is 1 character"
where it used to say "1 characters", and Russian and Ukrainian get the three
forms a numeral needs — "1 символ", "2 символа", "5 символов".
- The card grid's "+4 more" is one slug with a count, not a sentence glued
together in the template.
- ui.tagline is written once and read by both the login and the welcome screen,
which show the same words.
The English values are verbatim the literals the code hard-coded, so the
English interface is unchanged and no test needed editing.
Checked live in Chromium against the built bundle on the dev backend, all three
languages: the login screen (reached with no account at all, its language
coming from navi.locale), the offline banner with the context put offline under
it, the selection toolbar over a real selection in a message, and the
confirmation dialog opened from a session row's Delete. Two further passes open
a session whose tool output carries a card grid and one that carries images, for
the card grid's section headings and the lightbox's heading and close button.
The form is the one component no saved session renders — nothing in the dev
database carries a form payload — so its messages were printed through t()
instead, including the plural ones: "Поле «Email» обязательно", "Минимальная
длина — 2 символа", "Мінімальна довжина — 5 символів", "Minimum length is 1
character".
Eugene Sukhodolskiy
committed
23 days ago
|
| 2026-10-09 |

i18n: en/uk/ru, and the interface language of the account
...
The client hard-coded English in every template. It now goes through t() from
src/i18n/ — a locale ref, {param} interpolation, plural dictionaries driven by
n, and flat per-area dictionaries in src/i18n/messages/ carrying the same slugs
in all three languages. vue-i18n is deliberately not pulled in: the platform
handbook rules it out, and what it would buy — plural forms, a reactive locale,
an English fallback — is about eighty lines here. A slug nobody carries falls
back to English, so a screen already translated and one still speaking
hard-coded English can coexist while the work goes area by area.
The language comes from locale_effective in GET /auth/me: the choice made in
Settings (navi_users.locale_override, a new column) over the gnexus-auth
account locale (navi_users.locale, a straight mirror the user.profile_updated
webhook keeps writing — the override sits in its own column precisely so that
mirror cannot wipe it), over en. PATCH /auth/me writes the override, and null
means "follow the account" again. With NAVI_AUTH_ENABLED=false there is no
account to follow, so the choice is kept in localStorage under navi.locale.
The picker is the first block of Settings → Account. Each language is named in
its own language, which is why those three strings are identical in all three
dictionaries. Lists of labels are computed rather than module-level constants:
one built at import would keep the language that was active then and ignore a
switch.
Checked in Chromium against the built bundle: choosing Русский rewrites
<html lang> and the document title, survives a reload, and Auto brings the
account's own language back.
Eugene Sukhodolskiy
committed
24 days ago
|
webclient: one icon per profile, and one height for every card
...
Six of the ten profiles rendered the same robot, because the map in
WelcomeScreen only knew four ids. The map moves to utils/profileIcons.js,
covers all ten — including the close pairs (`coder`/`developer`, the two
3D profiles) — and falls back to a hashed glyph from a pool rather than
one robot, so custom profiles differ from each other and stay stable
across renders. Covered by five unit tests.
Cards were ragged because each row took the height of its own content:
`grid-auto-rows: 1fr` makes every row as tall as the tallest card. A
fixed pixel height was the obvious alternative and is worse — "Server
Administrator" wraps on a phone. A three-line clamp on the desktop
description keeps one wordy profile from inflating every row.
Eugene Sukhodolskiy
committed
24 days ago
|
webclient: let the welcome pane scroll, and pin the mobile menu toggle
...
The profile grid grows with the profile list, and on a phone it is taller than
the viewport once there are more than a couple of profiles. Nothing could
scroll: `#app` and `.app-main` both hide overflow, and `.welcome-screen` had no
overflow of its own, so the cards were clipped rather than pushed down.
The pane now scrolls, and its vertical centering is `safe center` — with a plain
`center` the overflow goes past the flex start edge and cannot be scrolled back
to. The sidebar toggle becomes `position: fixed` so it does not scroll away with
the content it sits above.
Eugene Sukhodolskiy
committed
24 days ago
|
| 2026-10-08 |

profiles: tool_developer folds into developer
...
The profile was a duplicate on every axis we could measure. 22 of its 26
native tools were already developer's; the four it alone held —
reload_tools, create_mcp_server, test_mcp_tool, mcp_status — are 2.9 KB of
schema. Its model chain was the same seven models. Of its 14 KB prompt,
about 8 KB was copied verbatim from developer's (the whole `## Orchestration
model` block and everything from `## Editing policy` down), and most of the
remainder restated manuals/create_mcp_server.md, which already carried the
same ten-step workflow in more detail. It was not a specialisation, it was a
snapshot: `git log -S '"reload_tools"'` shows the tool lived in developer
until 61fa370 rewrote that profile around MCP and cut it off.
What kept it alive was a premise that no longer holds — that Navi's own
capabilities would be written as in-repo tools. They are MCP servers now,
and an MCP server is not a file in this repository with a life of its own:
it is an isolated process registered from mcp_servers.d/. So there is no
reason left for a profile whose only distinct feature is a toolset a general
developer profile can hold, and every reason to stop maintaining a second
prompt that drifts against the first.
- developer: + reload_tools, create_mcp_server, test_mcp_tool, mcp_status
(24 → 28 native). Its sub-agent gains tool_manual, which is what it
actually needed to reach the manual while writing a server — the previous
tool_developer sub-agent had it, developer's did not.
- server_admin: + reload_tools only (24 → 25). Adding a third-party MCP
server is something this profile does as often as developer does.
Deliberately not to its sub-agent: reconnecting the MCP manager is a
process-wide operation belonging to the main agent.
- The prompt and the manual took on what the deleted profile knew and
create_mcp_server.md did not: reload_tools before the first test_mcp_tool
(a freshly registered server is not connected, so the test fails and the
iteration is wasted), the smoke test read by exit code — 124 means timeout
killed a server still running, 0 means it exited on its own, usually a
main() without parentheses — absolute command/cwd, mcp_status as discovery
only, and the steps that stay inline instead of going to a sub-agent.
mcp_status and test_mcp_tool were built without an MCP manager, and their
fallback did `from navi.api.deps import _mcp_manager` — a name that does not
exist, so a live call raised ImportError rather than the intended "MCP
manager not available". The tools always passed a manager in tests, which is
why nothing caught it. Both now receive the manager at construction and fall
back to the live one lazily.
Sessions and profile_overrides are reassigned before the restart: agent.py
resolves the session's profile without a guard, so a deleted profile turns
every session that referenced it into an uncaught ProfileNotFound. Nothing
in the test suite pins the profile inventory, and profiles are read once at
import time — reload_tools does not re-read them — so this ships as a
restart, and the restart is also what makes it take effect.
Eugene Sukhodolskiy
committed
25 days ago
|
| 2026-10-06 |
webclient: revert LoginScreen to the custom card
...
The kit login-card markup read heavier (uppercase header, wide chrome
border) than the original centered card — restoring the previous
version per user preference.
Eugene Sukhodolskiy
committed
27 days ago
|
webclient: LoginScreen rebuilt on kit markup
...
The SSO-only login doesn't fit GnLoginCard's username/password form (the
adapter always renders both fields), so the escape-hatch card is replaced
with the kit's own login-card markup (article.card.login-card + header +
card-content + GnButton) — the same contract kit.css ships for
GnLoginCard, minus the form. Custom card border/title/subtitle styles are
gone; overlay keeps positioning only.
Eugene Sukhodolskiy
committed
27 days ago
|
webclient: profile chips in sidebar; New Chat opens welcome; profile tap starts session; welcome grid 4-up
Eugene Sukhodolskiy
committed
27 days ago
|
webclient: polish batch — caps off, kit badge/chip in header+input, search clear closes, mobile full-bleed input, settings centered
...
- chat-header-title and sidebar logo span: remove forced uppercase
- profile badge -> GnBadge (variant per profile: secretary secondary,
server_admin warning, developer info); custom box styles dropped
- pending file pill -> GnChip (icon + removable); image thumb stays
app-owned (no kit image-preview component) but on kit surface look
- sidebar search clear button now also closes the field
- input-row: no yellow focus ring; on mobile full-screen width, no
borders, inner padding carries the breathing room
- sidebar footer: without-hover removed from gear/admin/logout buttons
— kit hover returns
- settings: header and body centered, max-width 1200px
Eugene Sukhodolskiy
committed
27 days ago
|
webclient: center lightbox photo in the modal panel
...
Kit's .modal .modal-dialog .modal-panel min-height rule (0,3,0) beat the
lightbox's own override, so shorter photos sat at the top of an empty
panel and the dialog hugged them horizontally. Drop the shrink-wrap, let
the dialog keep the standard kit width and center the image in the panel
body both ways.
Eugene Sukhodolskiy
committed
27 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
28 days ago
|

webclient: retokenize styles onto gn-ui-kit v1.0 CSS variables
...
All ~165 legacy var() usages (--color-*, --surface*, --accent*, --border,
--text*) with hex fallbacks and ~150 raw hex literals across app.scss and
24 scoped style blocks are swapped to the kit's --gn-* tokens per the
intent of the old fallback values:
- accent/primary/teal (#7aa2f7/#4ec9b0) -> --gn-color-secondary
- amber/orange highlights -> --gn-color-accent
- text greys -> --gn-color-text-{light,medium,dark}
- panels -> --gn-surface-panel; elevated tints -> color-mix over panel
- translucent overlays -> color-mix(in srgb, ..., transparent)
Deliberately literal: terminal deep bg #0f0f14 (darker than any token)
and the content-card image badge teal #89dceb (kit has no teal). File
type badges keep a categorical palette via nearest kit tokens.
Dead "UI kit overrides" block halved: kit 1.0 dropped hover icon
rotation, so only the app-owned chevron open-state flip remains.
Verified: vitest 111 passed, build OK, dev render matches W1 baseline
Eugene Sukhodolskiy
committed
28 days ago
|
webclient: session-list polish from review pass
...
- documentTitle: persistent nameById map — names for sessions beyond the
loaded list page (list only holds ~30); map filled by fetches and
loadSession meta, never wiped by later refetches
- createSession: placeholder inserted at the position the server list
would give it (after pinned run, last_active desc) so refetch no
longer visibly moves the new row
- SessionItem: skip empty .session-icons wrapper when no icons
- drawer breakpoint made exclusive (-sidebar-drawer 1280 -> 1279.98):
viewport of exactly 1280px now gets the desktop sidebar
Eugene Sukhodolskiy
committed
28 days ago
|
| 2026-09-26 |
webclient: lightbox dialog shrink-wraps the photo, img without crop
Eugene Sukhodolskiy
committed
on 26 Sep
|

PWA: installable webclient, offline shell, web push
...
Installability:
- public/manifest.webmanifest (standalone, theme #16161E) + PNG icons
generated from logo.svg (regular + maskable, served via /images mount)
- index.html: manifest link, theme-color, apple-touch-icon
Offline shell:
- hand-rolled sw.js (no workbox): navigation = network-first (3s race)
with cached-shell fallback + background refresh (a stale cached shell
would 404 on entry chunks after a deploy); /assets/* cache-first
(content-hashed); /images/* cache-first capped; /api,/ws,/auth,/push,
/content pass-through
- vite closeBundle plugin stamps __NAVI_BUILD_VERSION__ (digest of
index.html + asset names) into dist/sw.js; sw.js served no-store so
every deploy reactivates the SW and activation evicts old caches
- SW registration in main.js, PROD only (dev HMR untouched)
- OfflineBanner (useOnline composable) over the app shell
Web push (VAPID, pywebpush):
- navi/push/ package: push_subscriptions table (postgres, boot-time DDL),
PushSubscriptionStore, PushService (async fan-out, to_thread sends,
404/410 prunes dead endpoints, per-session cooldown)
- routes: GET /push/vapid-key, POST/DELETE /push/subscribe (auth-gated)
- trigger in orchestrator run_agent + run_recall: push on StreamEnd when
no WebSocket client watches the session; fire-and-forget, never
disturbs the run; anonymous fallback only when auth is off
- client: usePush composable + Notifications settings panel (enable/
disable via PushManager.subscribe with the server VAPID key)
- notification click focuses the app at /#<session_id> (hash routing
opens the right chat); payload body is a markdown-stripped <=140-char
preview
NAVIVAPID keys empty = push fully disabled (graceful, like other optional
integrations). dist/ artifacts committed per repo convention.
Tests: pytest push store/service/routes/trigger (+23), vitest usePush
(83 webclient tests green). Full suite 1143 passed.
Eugene Sukhodolskiy
committed
on 26 Sep
|
| 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
|
| 2026-05-14 |
Markdown images: adaptive 3-column grid, lightbox modal, and broken-image handling
...
- useMarkdown.js: custom marked renderer wraps images in clickable links with
inline onerror for broken-image detection
- app.scss: .msg-assistant-content becomes flex-wrap container; image paragraphs
sized to 3-per-row via calc((100% - 16px) / 3); images are square via
aspect-ratio: 1 / 1 and object-fit: cover
- ImageLightbox.vue: rewritten with proper gnexus-ui-kit .modal/.modal-dialog
DOM structure; broken images show error placeholder inside modal
- AssistantMessage.vue: attachImageLightbox() wired after v-html render
- Broken images: .is-broken class prevents lightbox click; CSS shows 🖼️ icon
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 14 May
|
| 2026-05-13 |
Persist uploaded files in messages, live file tree updates, and UI polish
...
Backend:
- Add `files` field to `Message` model so uploaded file metadata survives page refresh
- Pass `files` through websocket handler → `agent.run_stream` / `agent.run`
- `list_tools`: make `profile_id` required; return error instead of all-tools fallback
Webclient:
- Call `fetchFiles()` after successful file upload for immediate Files tab update
- Live refresh file tree on filesystem (write/edit/append/mkdir/rm/cp/mv), terminal, and code_exec tool calls
- Add manual refresh button (desktop) and pull-to-refresh (mobile) to Files tab
- Fix live link updates: move regex creation inside per-message loop to avoid lastIndex state leak
- Restore full profile name text next to avatar in ChatHeader; hide avatar in header
- Fix mobile ArtifactsPanel: collapse tab text labels so close button fits with 3 tabs
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|
Fix profile icon class in WelcomeScreen — prepend 'ph' prefix
...
GnAvatar's :icon prop expects the full Phosphor class (e.g. 'ph ph-briefcase'),
not just the icon name. profileIcon() now returns 'ph ${cls}'.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|
Fix GnAvatar usage: use props (src/icon/initials) instead of default slot
...
GnAvatar render function ignores default slot content; it only reads
src, icon, and initials props. Fixes broken user avatar image display.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|
Migrate avatars to GnAvatar component from gnexus-ui-kit
...
- ProfileBadge.vue: GnBadge → GnAvatar with profile initials
- AppSidebar.vue: custom user-avatar/img → GnAvatar with image or fallback initial
- WelcomeScreen.vue: profile-icon → GnAvatar with Phosphor icon + secondary variant
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|
| 2026-05-12 |
Rename login button text from gnexus-auth to GNEXUS.
...
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 12 May
|
Fix Phosphor icon class on GnButton/GnIconButton usages.
...
gnexus-ui-kit's iconNode() does not prepend the base `ph` class
when the icon string starts with `ph-`; it only keeps the string
as-is. All icon props must include the base class explicitly:
`icon="ph ph-sidebar"` instead of `icon="ph-sidebar"`.
Updated 15 icon bindings across 9 components.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 12 May
|
Wire gnexus-ui-kit v0.2.0 Vue 3 adapter and migrate Wave 1 components.
...
- Update vendor/gnexus-ui-kit to v0.2.0 (Vue components + composables)
- Add Vite aliases for gnexus-ui-kit/vue and gnexus-ui-kit/css
- Install GnexusUiVue plugin in main.js + GnToastProvider in App.vue
- Migrate ProfileBadge → GnBadge
- Migrate ErrorMessage → GnAlert
- Migrate ConfirmDialog → GnConfirmDialog
- Migrate raw <button> elements → GnButton / GnIconButton in:
WelcomeScreen, LoginScreen, ChatArea, ChatHeader, AppSidebar,
ArtifactsPanel, UserMessage, AssistantMessage, SelectionToolbar
Build and tests pass (51/51).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 12 May
|