| 2026-10-09 |

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
3 hours ago
|
| 2026-10-07 |
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
1 day 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
1 day 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
1 day 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
1 day 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
1 day ago
|
webclient: fix mobile tab-block drift — column flex nowrap + stretch, kit table scrolls inside its wrapper
Eugene Sukhodolskiy
committed
1 day ago
|
webclient: reveal the active settings tab when the mobile tab strip scrolls
Eugene Sukhodolskiy
committed
1 day ago
|
webclient: settings header now literally the chat header; app-wide thin rounded scrollbars (override kit's thick square defaults)
Eugene Sukhodolskiy
committed
1 day 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
1 day 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
1 day ago
|
webclient: MCP keys tab gets an empty-state when no server declares a user_key slot
Eugene Sukhodolskiy
committed
1 day ago
|
webclient: settings page split into GnTabs sections (Account/Notifications/Synapse/MCP)
Eugene Sukhodolskiy
committed
1 day ago
|
webclient: settings loaders switched to kit circle spinner (GnLoader circle)
Eugene Sukhodolskiy
committed
1 day ago
|
webclient: MCP user keys panel (BYOK) + frontend tests
Eugene Sukhodolskiy
committed
1 day ago
|
webclient: top margin on settings header
Eugene Sukhodolskiy
committed
1 day 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
2 days ago
|

handbook alignment: profile.updated mirror, health contract, Synapse gateway
...
Handbook gaps closed (10-platform auth/health/notifications):
- webhooks: handle user.profile_updated — mirror the gnexus-auth profile
into navi_users (name, contact fields, avatar_url — new column, boot
migration); role/permissions keep their dedicated events
- auth: avatar_url now flows through the login upsert and the API-token
resolution path
- /health: status is now the aggregate of the sub-checks (degraded embed
or hive no longer reports plain ok); embed probe cached for 10 s; body
grew the machine-readable checks{} map, legacy embed/hive payloads kept
- Synapse: s2s delivery gateway — POST /webhooks/synapse (both spellings),
per-user delivery secrets (synapse_targets, Fernet-encrypted, shown
once), verification via gnexus-synapse v0.1.2 client lib, idempotent
event ids; deliveries are verified and logged for now — navi generates
no outbound events by decision; web-push stays as the local browser
channel
- webclient settings: Synapse deliveries panel (add/revoke targets)
Eugene Sukhodolskiy
committed
2 days ago
|
webclient: mobile input bar flush; settings panels full container width
...
- .input-bar on mobile: no top/bottom padding — once attachments appear,
the file strip provides the gaps itself
- ApiKeys / Notifications panels: drop the 800px cap, fill the centered
1200px settings column
Eugene Sukhodolskiy
committed
2 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
2 days ago
|
| 2026-10-05 |
webclient: W5 — icon/variant audit + design-system docs in CLAUDE.md
...
- ApiKeysPanel: dropped invalid variant="ghost" on GnIconButton (the
component has no variant prop — it leaked into the DOM as an attribute)
- Icon audit: all icon props/classes carry the required ph base class
and ph- prefix (kit v1.0 dev-warning otherwise); variant whitelist
(primary/secondary/warning/danger/info) confirmed against the kit
- webclient/CLAUDE.md: new 'Design system (gnexus-ui-kit v1.0)' section
— points to vendor/gnexus-ui-kit/CLAUDE.md, documents prebuilt-css
consumption, dark kit.css theme, --gn-* + kit-deps tokens, icon rule,
test-locked app classes, and the documented non-kit surfaces
Eugene Sukhodolskiy
committed
3 days ago
|
webclient: W3 — migrate settings/sidebar/chip surfaces onto kit components
...
- AppSidebar: hand-styled <select> -> GnSelect (options computed from the
profiles store); the ~60-line bespoke select styling in app.scss is now
three compact overrides collapsing GnSelect's form chrome for the slim
sidebar row
- ApiKeysPanel: bespoke grid-table -> GnTable with cell slots (monospace
key prefix, formatted dates, revoke GnIconButton in actions); loading
state -> GnLoader; no-auth callout -> GnAlert(variant=warning)
- NotificationSettingsPanel: unsupported-browser callout -> GnAlert(info)
- SettingsView: header -> GnPageHeader (compact) with sidebar toggle in
#actions
- ShowTokenModal: token field -> GnInputGroup + GnCopyButton (drops the
bespoke copy/useCopy wiring)
- QueuedMessagesChip: .task-chip div -> GnChip (icon prop)
Verified: vitest 111 passed, build OK, screenshots of / and #settings
Eugene Sukhodolskiy
committed
3 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
3 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
3 days ago
|
| 2026-09-26 |

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
12 days ago
|
| 2026-06-22 |
Add NAVI_AUTH_ENABLED switch for optional auth
...
- Add navi_auth_enabled setting (default true) to navi/config.py and .env.example
- When disabled, treat every request as anonymous admin user (id='anonymous')
- Create/update fixed anonymous navi_users row on startup
- Bypass OAuth/cookie/API-token resolution in navi/auth/deps.py
- Update /auth/status to return {enabled, configured}
- Log security warning on startup when auth is disabled
- Update webclient: skip fetchMe/login screen, show Local mode footer,
expose /admin link, warn in API keys panel
- Rebuild webclient production bundle
- Add unit and integration tests for no-auth mode
- Update docs: auth.md, config.md, api.md, api_tokens.md, sessions.md,
websocket.md, mechanics.md, index.md
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 22 Jun
|
| 2026-05-24 |
Remove redundant success toast on token creation — modal is enough
...
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Fix ApiKeysPanel by removing GnTable wrapper — required columns/rows props were missing
...
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Show copied checkmark on token copy button for 1.5s
...
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Apply review fixes to API token auth system
...
Backend:
- navi/auth/deps.py: replace 3 DB round-trips with single JOIN query for
token resolution; update last_used_at still separate (best-effort)
- navi/api/routes/api_tokens.py: replace asyncpg-specific "UPDATE 1"
string check with RETURNING id fetchrow; increase token_prefix from
8 to 12 chars for better visual identification; add security notes
- tests/unit/auth/test_api_tokens.py: update tests for JOIN query and
RETURNING-based revoke
Frontend:
- webclient/src/components/settings/ShowTokenModal.vue: new modal that
shows the plain token in a readonly field with copy button and
explicit warning — replaces the transient toast notification
- webclient/src/components/settings/ApiKeysPanel.vue: use ShowTokenModal
- webclient/src/composables/useWebSocket.js: add security comment about
localStorage XSS risk and query param log exposure
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|