| 2026-10-10 |

webclient: the chat area in en, uk and ru
...
The fifth area of the interface translation. The chat screen now builds its
text through t(): the header (the rename tooltip, the sidebar toggle, the
artifacts toggle, and the recall banner with its Cancel and Skip next
buttons), the list around it (the connection-lost banner and its Retry, and —
unreachable for now — the empty state), the input bar (the sending notice, the
paperclip, the placeholder, Send and Stop generation), the attachment strip
(its alt text and its remove button), the message list (Loading older
messages… and the scroll-to-bottom tooltip) and the queued-messages chip. Also
four toasts the WebSocket composable raises — background task started /
finished, MCP server connected / disconnected — and the compression notice's
own pending text.
26 new slugs in all three dictionaries:
- 20 in chat.* — the header and recall banner, the input bar, the attachment
strip, the queued chip and the empty state;
- 4 in toast.* — the two background-task and the two MCP toasts;
- 2 in common.* — task and toolsCount.
Four details worth knowing:
- The recall banner used to read the server's call_type enum out loud
("Scheduled once recall at …"). The word is now a slug
(chat.recallOnce / chat.recallRecurring / chat.recallImmediate), so it agrees
with the sentence around it — Russian and Ukrainian both change the
adjective's gender and the preposition, which a bare enum cannot.
- The banner's time was formatted with the browser's locale; it now formats
against locale.value, so the date follows the interface language rather than
the machine.
- The queued chip counted with `count === 1 ? '' : 's'`. It is now a dictionary
plural driven by {n}, which is what Russian and Ukrainian need — "1
сообщение", "2 сообщения", "5 сообщений".
- toolsCount moved from messages.* to common.*: the tool-count badge in the
assistant message and the MCP-connected toast both render it, so the same
word lives in the vocabulary area instead of twice under two areas.
docs/i18n.md records the move.
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 header title, the input placeholder, both input-row
buttons, the recall banner and its buttons, twelve message rows and the
attachment's alt and remove — read fresh for each language by picking it in
Settings first — with a scan of the page's text, titles, aria-labels, alt texts
and placeholders for a slug that leaked into the interface as the pass
condition.
Two states a saved session cannot be put into are covered by a throwaway mount
instead, and the file was removed again: the streaming input bar (Sending…,
Stop generation) and the queued chip, plus the recall words and the toast
titles, printed in Russian — "1 сообщение в очереди" / "2 сообщения" /
"5 сообщений".
The connection-lost banner never appears in this harness: with no socket token
the server refuses the socket and the client never exhausts its retries inside
the test's window, on the current build and on HEAD's alike. It is covered by
the existing tests/unit/composables/useWebSocket.test.js and by reading
ChatArea.vue; the same goes for chat.nothingOpen / chat.startConversation,
which App.vue's WelcomeScreen shadows before ChatArea can render its own empty
state — the branch is dead today and keeps its slugs for when it is not.
Eugene Sukhodolskiy
committed
1 day ago
|
| 2026-10-09 |

webclient: the message area in en, uk and ru
...
The fourth area of the interface translation. Everything a conversation is made
of now builds its text through t(): the thinking card and the status line it
replaces while the model reasons, the plan card, the tool cards (both the plain
one and its sub-agent steps), the published-content card, the UI-component card
and the compression notice, plus the two new chat bubbles' copy button and the
"Scheduled recall" mark on an injected user message.
25 new slugs in all three dictionaries:
- 23 in messages.* — the cards' own headers and labels (Thinking, Plan,
Arguments, Show all / Hide older tools, Useful / Not useful, Open file,
Content, Unknown UI component: {name}, Context summary, Compressing context…,
Context compressed), the running and elapsed labels (Running {tool}…,
Preparing next action…, running, starting) and the two counted ones that a
compact JSON preview renders, "{n} items" and "{n} keys";
- 2 in common.* — Copy and Copied!, which the assistant and the user message
both need.
Three details worth knowing:
- The English values are verbatim the literals the code hard-coded, with one
exception: "{n} tools" and "({before} → {after} messages)" are now dictionary
plurals, so English says "1 tool" and "1 message" where it used to say
"1 tools" and "1 messages". No test asserted either string.
- The braces around the two compact-JSON counts stay in the template
(`[${t('messages.jsonItems', { n })}]`), so a translator never has to keep a
bracket it cannot see the other half of.
- The message list's ContentCard shows the same published-file widget as the
artifacts drawer, so it reads the six artifacts.* slugs of that widget rather
than carrying six duplicates under messages.*; and its two tool-output labels
— Result and Live output — are the words the tool card already uses, so they
sit in common.* and the drawer's copies moved there too. docs/i18n.md records
both.
Checked live in Chromium against the built bundle on the dev backend: five
saved sessions chosen so that between them they render every component of the
area (thinking cards, plan cards, 55 tool cards, published-content cards,
UI-component cards and a compression notice), six loads per language, with
every collapsed block expanded first and a scan for a slug leaking into the
rendered text as the pass condition. The sub-agent step cannot be reached from
a saved session — the runtime does not persist tool steps — so SubagentStep was
rendered on its own in a throwaway mount instead: "выполняется", "Аргументы",
"Результат", "[3 элемента]".
Eugene Sukhodolskiy
committed
1 day ago
|

webclient: the sidebar in en, uk and ru
...
The third area of the interface translation. AppSidebar, SessionList and
SessionItem now build their text through t(): New Chat, the profile strip and
its filter buttons, the list label (Conversations / Service sessions, and
"30 results" while searching), the empty and loading states, the per-session
icons' tooltips (pinned, scheduled recall, service session), the row actions
(pin / unpin / cancel recall / skip next / delete and its confirm dialog), and
the footer identity (Local mode / Log out / Log in / Settings / Admin).
Also the shared time labels in composables/useTime.js — "just now" and
"5 min ago" — which the messages area renders too; they go to common.*
because both screens say the same words. The label is a cached string, not a
template that re-renders, so the composable now watches locale and rewrites
itself when the language changes. The absolute fallbacks format against
locale.value instead of the browser locale.
31 new slugs: 28 in sidebar.*, plus common.delete, common.minutesAgo (plural)
and confirm.deleteSession. English values are verbatim the old literals, so
the English interface is unchanged and no test needed editing.
Checked live in Chromium against the built bundle on the dev backend: the
sidebar was read in all three languages both in the plain list and with the
search field open, including every title / aria-label / placeholder / alt, with
a slug-leak scan as the pass condition.
One English string stays put on purpose: GnSearchField's aria-label
="Clear search" is baked into the UI kit component, not passed in, so it is
English on every gnexus client; docs/i18n.md now says so.
Eugene Sukhodolskiy
committed
1 day ago
|

webclient: the artifacts drawer in en, uk and ru
...
The second area of the interface translation. Every user-visible string in
ArtifactsPanel.vue now goes through t(): the four tab chips, the empty states,
the action labels (open preview / open raw file / download / copy link), the
pull-to-refresh captions, the background-task badges and ages, the terminal
detail rows and the live-output header.
60 new slugs, in all three dictionaries:
- 39 in artifacts.*;
- 11 in the shared col.* vocabulary (Tool, Status, Started, Finished, PID,
Command, CWD, Uptime, Background, Reason, Sub-agent tokens) — the drawer is
where these table headers first appear;
- 10 in common.* — Download, Yes, No, N/A, the tool-output labels Result and
Live output, and the four the sidebar needs as well: the three
pull-to-refresh captions and "just now". These are the same words on both
screens, so they sit in the shared vocabulary area and the drawer keeps only
what is about the drawer.
Four details worth knowing:
- The English values are verbatim the literals the code hard-coded, so the
English interface is byte-identical and the existing tests stay untouched.
- Counted strings use dictionary plurals: '{n} links in session' is
one/few/many in ru and uk, one/other in en.
- Times (formatTime / formatDate) and ages now format against locale.value
instead of the browser locale, so a Russian interface shows 16:56 where
English shows 04:56 PM.
- Unit suffixes in formatUptime (s/m/h) are left alone: they are symbols
inside a number, not words.
Checked live in Chromium against the built bundle on the dev backend: the
drawer was driven through all four tabs in all three languages on two real
sessions — one with published STL files and a viewed-images group, one with
eight extracted links — with a slug-leak scan as the pass condition, and the
time/age formatting difference confirmed in the rendering.
Eugene Sukhodolskiy
committed
1 day ago
|

webclient: the settings screen in en, uk and ru
...
Settings is the first area off hard-coded English: 122 strings for the shell and
its five tabs — App, Account, Notifications, Synapse and MCP — plus the 43 the
shared vocabulary contributes (common.*, col.*, confirm.*, toast.*).
The English dictionary carries the very literals the templates used, so the
interface a test reads is byte-for-byte what it was and the 56 assertions on
English UI text stay green; only a slug that is genuinely counted had to be
restructured into a plural. In ru and uk the counted strings get the three
Slavic forms driven by n. Product names are never translated: Navi, Synapse,
MCP, Ollama, Tool call, Subagent and Token read the same in all three
languages, and the settings screen keeps one settings.* area with
panel-prefixed slugs rather than a slug per panel, as the other gnexus clients
do.
Strings carrying markup are split around it instead of hiding tags in the
dictionary: the install steps keep their inline icons and <code>http://</code>
in the template, with the words of each sentence on either side. Four lists of
labels — the settings tabs, two table headers, and the reaction options — are
now computed rather than module-level constants, since a list built at import
keeps the language that was active then and ignores a switch. Dates in the
panels go through the interface language as well, not the browser's.
Checked in Chromium against the built bundle: all three languages on all five
tabs, with a scan for a slug leaking into the rendered text as the pass
condition, and the account left with no override afterwards.
Eugene Sukhodolskiy
committed
1 day ago
|

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

webclient: an App section, and a link to any settings section
...
Settings gains an App section: how to install Navi in the browser — a real
Install button where the browser offers beforeinstallprompt, per-platform
steps where it cannot, since Safari and Firefox never expose that API and iOS
goes through the share sheet — and the Android APK, with its version, size and
checksum, behind a download the app hands to the system browser. The PWA
listener is armed from main.js because beforeinstallprompt fires once, during
startup, long before the panel is ever mounted.
App is now the first tab and every section is linkable: #settings/app,
#settings/mcp, and so on. Parsing moved into utils/hash.js, which the three
places in App.vue that route on the hash now share — before it, #settings/app
was read as the id of a session and opened an empty chat. Switching tabs
rewrites the address with replaceState, so Back leaves Settings instead of
walking back through the tabs, and a bare or unknown #settings normalises to
the first tab.
Checked in Chromium against both the dev server and the built bundle: each
hash opens its section, no /api/sessions/ request is ever made for one, a tab
click leaves history.length alone, and at 390 and 320px the strip keeps five
tabs with the active one scrolled into view and no sideways overflow.
Eugene Sukhodolskiy
committed
1 day ago
|

webclient: keep the artifacts tab icons on a phone
...
The artifacts panel has two squeeze ladders that did not know about each
other. At <=980px the mobile block hides the tab label, leaving icon +
count; a container query at <=360px separately hides the icon, because
the docked panel has no room for icon + label + count. The query
measures the panel, and on a phone the panel is the viewport — so on a
360px-wide screen (the most common Android width) both fired and every
tab became a bare counter: "Artifacts 0 Links 0 Files 0
Backgrounds 0". Between 361 and 980px the icons were fine, which is why
it only showed up on phones.
Wrap the icon-hiding step in @media (min-width: 981px), so it describes
the docked panel only, where the label is still attached and dropping
the icon still buys something. The panel is otherwise untouched.
Measured in Chromium against the built stylesheet and the component's
real scoped markup, old versus new side by side: at 320px and 360px the
icons go 0/4 -> 4/4 with no row overflow; 390, 430 and 900px are
unchanged; and every docked width tested (300, 340, 370, 450, 537px)
reports byte-identical values before and after. The pre-existing
overflow of the docked row between roughly 300 and 450px is untouched.
Eugene Sukhodolskiy
committed
1 day ago
|

webclient: animate <details> cards in every browser, not just Chromium
...
Open/close of the tool cards jumped in Firefox and Safari: the previous
attempt leaned on interpolate-size/::details-content, and interpolate-size
shipped in Chromium alone, so the @supports block degraded to a hard
attribute flip everywhere else.
Drive the element's own height from JS instead. Nothing there is
engine-specific: open animates to scrollHeight, close to the summary
height, overflow is clipped for the duration and the inline styles come
off on transitionend with a timer as a backstop. The element keeps its
native semantics — keyboard, screen readers, find-in-page — because the
open attribute is still what changes; the toggle event still fires, so
ContentCard's :open/@toggle pairing is untouched. prefers-reduced-motion
settles in a single frame.
A newly inserted card grows its grid row from 0fr to 1fr, which
interpolates everywhere and resolves to whatever the card measures, so
the card below slides instead of being displaced.
Measured in Chromium 153 and Firefox 155 against the real component
markup and the built bundle: 33 -> 586px over 18 distinct heights in
both, and the same for insertion, the nested Arguments block and the
plan card. The scroll behaviour of MessageList was left alone.
Eugene Sukhodolskiy
committed
1 day 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
1 day 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
2 days ago
|

profiles: restricted profiles for ordinary users, and a role gate that holds
...
`is_admin_only` was checked in one place out of nine and was not read from
config.json at all, so all seven profiles were reachable by every account with
role `user`. The flag now lives in config.json — the file is the baseline, a
`profile_overrides` row still wins on top of it — and one predicate,
`admin_only_blocked`, is the single place the rule is expressed. The nine
surfaces that list, switch to, spawn or resolve a profile all consult it:
`POST /sessions`, the WebSocket, switch_profile, list_profiles, the system
prompt's "Available profiles" block, spawn_agent and the Synapse reaction
runner. The prompt cache is now keyed by (profile, role), so a user's prompt
can never be served an admin's profile list.
The seven existing profiles (developer, discuss, dispatcher, modeler_3d,
navi_code, secretary, server_admin) are marked admin-only. Three new ones take
their place for ordinary users: assistant, designer_3d and coder. They share one
native tool set — ssh_exec, peer, reload_tools, create_mcp_server, test_mcp_tool,
image_view and gmail are withheld — and differ only in system prompt, model and
MCP groups. navi-web's raw `request` group, and the whole of gnexus-creds and
tgclient, are withheld too.
MCP per-user keys gain the missing half of the rule: a server that declares a
`user_key` slot is refused to anyone but an admin who has no personal key, and
is left out of their tool list entirely, instead of quietly falling back to the
owner's credential and appearing as a tool that cannot work. The refusal names
the server and points at Settings.
Also closes `GET /agents/prompts`, which served every profile's system prompt to
anyone, with no user dependency at all.
The accepted residual risk is written down in docs/profiles.md: the working
directory is a convention, not a sandbox.
Eugene Sukhodolskiy
committed
2 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
2 days ago
|
| 2026-10-07 |
webclient: hold the chat at the bottom when a stream ends
...
The list was pinned per streaming delta, but the last things to land arrive
after that final pin: the stats/rating footer, which renders only once
msg.done is set, and the copy buttons attached to code blocks after render.
Nothing re-clamped afterwards — the length watcher never fires (the message
stays in the array) and the landing loop only runs when a session opens — so
the view was left short of the bottom, looking like it had scrolled up.
Re-clamp through the same settle window the landing uses when streaming goes
true -> false, unless the user has scrolled up or a session is loading.
This addresses the late-layout half of the problem; the row is still remounted
when msg.id becomes h_<n>, which is the other source of a jump.
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: reload tools from a button in the MCP tab
...
Settings → MCP gains a Tools block for admins only: one button, then the
same report the tool prints — what loaded, how many are in the registry,
per-file errors, and names in enabled.json nothing answers to.
It sits inside the existing MCP tab rather than a new one: the reload
rewrites the toolset of the whole server, not just this user's MCP keys,
and it belongs next to the thing it affects. Non-admins never see it.
dist rebuilt together with the source, as the server serves the bundle.
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: keep MCP rows at content height on a phone
...
.mcp-keys-row stacks into a column below 768px, and .mcp-keys-info kept
its flex: 1 1 240px. That basis is a width in the desktop row and a
height once the row stacks, so every server reserved 240px and its text
sat at the top of the gap — 402px for a row whose content is 90px.
Back to content height on mobile only, and drop the kit's .form-group
bottom margin there: the row's own flex gap already separates the field
from the buttons.
Eugene Sukhodolskiy
committed
3 days ago
|

MCP settings tab: list every connected server, slot only where declared
...
The tab was empty on every install: it listed only servers whose config
declares a `user_key` slot, and no config declared one — which read as
"no MCP servers connected" even though five are wired to profiles.
- GET /mcp-keys now returns every server referenced by at least one
profile, keyed ones first, with `accepts_user_key`, the slot location
(null when there is none) and the profile ids that connect it. The
per-user key store is skipped entirely when nothing has a slot.
- gnexus-creds declares `user_key: {header: Authorization, prefix:
"Bearer "}` — it is the one server carrying a shared credential, so its
personal-key field is now real: users with a key run under their own,
users without one fall back to the shared default.
- The panel lists all servers (transport + profiles), dims the keyless
rows, and shows a key input only for slotted ones, spelling out the
shared-key fallback.
docs/api.md and docs/mcp.md updated; backend 1367 passed, webclient 148.
Eugene Sukhodolskiy
committed
3 days ago
|

Android app: native push notifications via JS bridge + background hold
...
WebView has no Push API, so the app shows notifications natively:
- NaviBridge JS interface (window.NaviAndroid): notify(), permission
request with a 'navi-perm-result' event callback, background-mode
start/stop and the Doze-exemption ask.
- BackgroundService: dataSync foreground service + partial wake lock —
holds the process (and the WebView WebSocket) open while the app is
minimized; quiet persistent notification with a "Отключить фон" action.
Notification channels: silent background presence, high-importance
messages.
- Manifest: POST_NOTIFICATIONS / FOREGROUND_SERVICE(_DATA_SYNC) /
WAKE_LOCK / REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + service entry;
notification tap resumes MainActivity (singleTask) into open_url.
- webclient: nativeBridge composable; usePush gets a bridge mode that
replaces web-push entirely (settings toggle keeps working); chat store
raises 'Navi ответила' through the bridge on stream end when the
window is hidden. Fixed a latent undefined-variable in web syncState
(notificationsSupported -> notifSupported()).
- New unit tests for the bridge surface; 147 frontend tests + APK build
pass.
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: settings tab polish, light chat-table styling, iOS launch screens
...
- Synapse tab icon was a no-op glyph: ph-diagram-project does not exist in
the bundled Phosphor regular set — the button rendered bare text. Switched
to ph-network, which ships.
- .settings-tabs capped at 1200px width on wide screens (was max-width:none).
- Chat markdown tables: the old .msg-assistant-content .table rules were
dead (markdown never adds a .table class) — retarget .table-wrap table and
restyle lightly: framed border + radius, muted header row, plain row
separators, no uppercase/no hover color jump.
- iOS home-screen: apple-mobile-web-app metas and a full apple-touch-startup-
image set (24 device classes, portrait+landscape) rendered from logo.svg
on the theme background.
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: fix mobile tab-block drift — column flex nowrap + stretch, kit table scrolls inside its wrapper
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: reveal the active settings tab when the mobile tab strip scrolls
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: settings header now literally the chat header; app-wide thin rounded scrollbars (override kit's thick square defaults)
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: exclusive radio groups in Synapse reactions (distinct names, kit options API); mobile sidebar row swaps close button after New Chat
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: settings page fits the app frame — flat chat-style header bar, single full-bleed scroll region, mobile adaptation (stacked MCP rows, viewport-safe modals)
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: MCP keys tab gets an empty-state when no server declares a user_key slot
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: settings page split into GnTabs sections (Account/Notifications/Synapse/MCP)
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: settings loaders switched to kit circle spinner (GnLoader circle)
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: MCP user keys panel (BYOK) + frontend tests
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: top margin on settings header
Eugene Sukhodolskiy
committed
3 days ago
|
| 2026-10-06 |
webclient: Synapse reactions settings + service sessions toggle
...
- sidebar: service-sessions toggle (special=true fetch replaces the
regular list, active search drops out), muted style + robot icon on
special items in the list
- settings: Synapse reactions panel — enable switch, completion push
preference (always / only failures / never), notification destination
(app / app+Synapse / Synapse, Synapse options disabled while no
source key), reaction instructions editor with save + edit history
(author and reason per version)
- stores: synapseReactions store; sessions store fetches respect
showSpecial; getSessions passes special param
- vitest for api/session store/panel; dist rebuilt
Eugene Sukhodolskiy
committed
4 days ago
|