Commit Graph

497 Commits

Author SHA1 Message Date
nntrivi2001 decf176b4b chore: v2.8.78 — workspace and session sharing 2026-11-06 13:05:10 +07:00
nntrivi2001 18e574a765 feat(server-mode): pane presence and typing audit (sharing phase 4)
A pane's tab shows the other people who have it open (pane-presence.js,
in memory, fed by the /ws-pty attach; GET /api/lanes/:id/presence and the
pane_presence broadcast to the owner and full collaborators) and says
"Billed to <name>" for a pane a collaborator opened (startedBy on the run).
The first real keystroke of each attach into a shared workspace's pane by
anyone but its owner is audited as `type into <run id>` (mouse, focus and
terminal replies to tmux do not count). The pty socket now pings, so a
vanished browser is ended at the next re-check and leaves the presence list.
2026-11-06 11:32:25 +07:00
nntrivi2001 879a6bb02d feat(server-mode): share one session; scope collaborators' pane tokens (sharing phase 3)
A workspace's owner (or an admin) can share ONE session from its right-click
menu (GET/PUT /api/lanes/:id/sessions/:sid/shares, table session_shares,
audited). The user sees the workspace (access: "session") with only that
session, joins the pane holding it, resumes it on their own account in a
free slot, and uses the workspace's git and Files reads and git writes;
everything else of the workspace answers 403 ESESSIONSHARE (scope.js
sessionShareAllows in scopeGuard). Hook/runtime broadcasts stay with full
collaborators.

A collaborator's pane token (readable by the owner's Linux account) now acts
as them inside that workspace only, and never starts or bills a session
(auth.js paneLaneAllows, 403 EPANESCOPE); a Terminal tab the owner or an
admin opens acts as the owner again. Docs say plainly that under S3 a
session share hands over the owner's account inside the terminal.
2026-11-06 11:25:40 +07:00
nntrivi2001 1a365a45bd feat(server-mode): collaborators run sessions on their own account (sharing phase 2)
A user a workspace is shared with may now start and resume AI sessions in
it. The run uses THEIR OWN account — a Claude pool setup-token, a Codex/Cursor
pool API key, or an AI Proxy of theirs — never the owner's login or a pool
login (409 ENOCOLLABACCOUNT otherwise). Their quota decides (EQUOTA, their own
flag file, the sweep goes by payer).

Money is per payer by UTC hour (session_payers, server/lib/session-payers.js):
a session nobody else ran is billed as before; one a collaborator started is
theirs; a resume changes hands from the next hour. Only Claude books by the
hour, so a cross-payer resume of any other tool and a collaborator's
resumeLast are refused (409 ECOLLABRESUME); a Terminal tab stays the owner's.
Users page, details, quotas, Today (mine=1) and developer budgets count by
payer. Docs: SERVER-MODE.md, API.md, CLAUDE.md, plan status.
2026-11-06 11:10:05 +07:00
nntrivi2001 54001585e2 fix(server-mode): close the gaps the review of workspace sharing found
- A Terminal tab a collaborator opens records them (dashboard_runs.started_by):
  its pane token acts as them — and only while still shared with them —
  never as the workspace owner (userFromPaneToken).
- POST /api/run always starts the lane's AI tool: a collaborator gets
  ECOLLABSTART whatever the body says (shell: true no longer slips past).
- A collaborator disabled since no longer locks the share list: kept on it
  (shown disabled) or taken off; only a new addition must be enabled.
- The workspace home tells a collaborator to open an existing session and
  keeps Start disabled instead of failing on every submit.
2026-11-06 10:48:29 +07:00
nntrivi2001 87ba935c6e feat(server-mode): share a workspace with other users (sharing phase 1)
A workspace's owner (or an admin) shares it from its ⋯ menu → Share…
(lane_shares, GET/PUT /api/lanes/:id/shares, audited). The users it is shared
with see it, its sessions, Changes, history and live updates, join and type
into its open terminals, commit and push — every check through scope.js
(canSeeLane, sessionWhere, laneWhere, broadcastAudience viewers). Rename,
reset, remove, delete and sharing stay the owner's or an admin's
(403 ENOTOWNER), and a collaborator starts no AI session there yet
(403 ECOLLABSTART; a plain terminal tab is fine). Budgets and Today stay on
owned rows only (ownedOnlySessionWhere). Taking a share back sends lane_access
to those users and their terminals close at the next attach re-check.

Under S3 a collaborator works as the owner's Linux account; the Share dialog
says so plainly (docs/plans/workspace-sharing/, R1 option a).
2026-11-06 10:43:16 +07:00
nntrivi2001 e60ebe468b chore: v2.8.77 — pool accounts in Profiles, Changes panel on the workspace home 2026-11-06 10:07:52 +07:00
nntrivi2001 26977780cc feat(workspace): the home's context card follows the scroll; no scrollbar
On the workspace home the Instructions / Memory / Context / Folder card is
sticky inside the home's scroller (scrolling itself, barless, when taller
than the window), and the home column hides its scrollbar — wheel and touch
still scroll it.
2026-11-06 10:05:32 +07:00
nntrivi2001 e06472ecd9 fix(workspace): the home no longer scrolls the page into empty space
Each Recents row carries an absolutely placed sr-only label. Placed against
the row outside the home's own scroll column, those labels escaped its
clipping and stretched the page by the hidden part of the list — an empty
page-length scroll under the home and the Changes panel. The column is now
relative, so its scroller contains them.
2026-11-06 09:50:27 +07:00
nntrivi2001 ab27629139 feat(workspace): the Changes panel shows on the workspace home too
The home used to hide the whole row the Changes panel lives in, so a
workspace's changes showed only once a session tab was open. The home now
shares that row: the panel (or its rail) sits beside it at the same width,
the page is locked to the viewport and the home scrolls in its own column.
The panel's Workspace context fold is left out there, since the home shows
that column itself. The console's section stays mounted and hidden (CSS).
2026-11-06 09:42:08 +07:00
nntrivi2001 3b1fdfac51 feat(workspace): the Changes panel's repo sections are an accordion
With several repos stacked in the Changes panel, only one section is open at
a time: the first starts open, opening another folds the rest so it gets the
whole height, and clicking the open one folds it. A chat's open-file request
opens the section holding the file. Folded sections stay mounted (CSS), so an
unsaved edit survives.
2026-11-06 09:25:46 +07:00
nntrivi2001 087c1ddb5d feat(settings): pool accounts are rows of Profiles; the separate Pool account section is gone
Settings → AI tools → Profiles now lists the account-pool accounts the user
may use (GET /api/me/pool-accounts) as rows of each tool's tab, with quota,
an 'in use' badge and Switch to (PUT /api/me/pool-accounts), plus an
Automatic row; an admin's pin locks them. A saved profile with the same
email as a pool account is shown once, as the pool row. Developers now see
Profiles too, with only their pool rows; Cursor gets a tab when the pool
holds a Cursor account for the user.
2026-11-06 09:21:46 +07:00
nntrivi2001 890cd08f57 chore: v2.8.76 — four new colour themes: Sunset Cloud, Terminal, Night Sea and Sakura 2026-11-05 13:54:01 +07:00
nntrivi2001 aabc95768c feat(appearance): Sunset Cloud, Terminal, Night Sea and Sakura colour themes
Four named themes from ai-chat-themes (MIT; colours only — no wallpapers,
fonts or custom CSS): three dark ones beside Darcula / Apple Dark / Orca
Dark and Sakura beside the light ones. Each theme's background, text and
accent colour become the full token ramp, held to the existing contrast
bar (text and fg-muted >= 4.5:1 on every surface, accent on its /15 chip,
white on accent-solid, status labels on their /20 chip). Old Paper was
left out.

The terminal took its ANSI blue from --accent, which would print blue as
green (Terminal) or coral next to red (Sunset Cloud); a theme can now set
--ansi-blue, which lib/terminalTheme.ts prefers.
2026-11-05 12:15:21 +07:00
nntrivi2001 b34b201bd1 chore: v2.8.75 — a page published from Claude's scratchpad keeps a viewable copy
- An artifact page published from the session's own scratchpad (where
  Claude Code's Artifact tool writes pages by default) now keeps a copy
  that viewers can preview here. Before, Preview said the file was gone.
2026-11-05 12:00:49 +07:00
nntrivi2001 c356cf96a1 fix(artifacts): a page published from its session's scratchpad keeps a viewable copy
The Artifact tool writes a page into the session's own Claude Code
scratchpad by default (<tmp>/claude-<uid>/<project>/<session>/scratchpad),
but pageCopy took the file only from the workspace folder, so such a page
got no copy and Preview said "the file ... is gone" while it was still there.

pageCopy now also accepts that ONE scratchpad, named from the publishing
session's id, its cwd and the workspace account's uid; before reading,
each of its four folders must be a real directory of that account, and each
file is realpath-confined, opened O_NOFOLLOW and must be a regular file of
that uid. Supporting files come only from the page's own root. The older-
page backfill (POST /api/artifacts/:id/copy) uses the session's cwd too.
The noCopy message now says the file may also lie outside both.
2026-11-05 11:20:34 +07:00
nntrivi2001 2a31537b80 chore: v2.8.74 — a member's chat no longer lags a turn behind the terminal
- Under server mode with per-user accounts, a member's Claude session chat
  showed the transcript as of the last import snapshot: transcript lookups
  used access(2), which cannot see past the member's 0700 projects folder.
  They now use stat, so the chat reads the live transcript again.
2026-11-05 09:39:30 +07:00
nntrivi2001 59533b997b chore: v2.8.73 — Artifacts folder bar restyled; hidden live profile gets a stand-in
- Artifacts: folders are rounded chips with a count badge; rename/delete are
  icons beside the picked folder (double-click renames); New folder is a
  dashed chip; the empty-folder hint mentions drag and drop.
- GET /api/pricing/quota: every Claude row carries its accountUuid.
- Settings → Profiles and the status bar's Usage panel: a profile hidden in
  this browser is left out of the Usage panel too; when the live login is
  hidden, the first visible profile is shown in its place (Logged in now,
  "in use", the % used chip with that profile's own usage). Display only.
2026-11-04 16:14:27 +07:00
nntrivi2001 e21120d51d chore: v2.8.72 — update survives npm-rewritten lockfiles; drag artifacts into folders
- update: before the --ff-only merge, put back package-lock.json /
  client/package-lock.json when they are the ONLY tracked changes (an
  earlier update's npm install rewrote them; v2.8.71 changed
  client/package-lock.json and the merge refused on such installs). Any
  other change still makes the merge refuse.
- Artifacts: drag a row or card onto a folder chip to put it there, onto
  "All folders" to take it out.
2026-11-04 15:27:48 +07:00
nntrivi2001 c946b73d11 chore: v2.8.71 — remote clients (company hub), artifacts shared with chosen people, folders and viewable copies, fixes
Remote clients (docs/plans/remote-clients/README.md, B1–B3 for the Claude pool):
- server/hub/ holds the hub-only code (devices, remote sessions, upload ingest,
  LAN gateway), required only in server mode; hub-boundary.test.js keeps local
  code from importing it.
- A local install enrolls with `agenthub remote login` or Settings → AI tools →
  Company hub; the "Company account" AI Proxy (reserved name agenthub.company)
  sends a session through the hub's gateway, which adds a pool account's token —
  no credential reaches the machine. Only those sessions are uploaded, hourly
  and at their end; secret-shaped strings are redacted on the client.
- Gateway refuses: unknown device (401), a message without a session id, an
  unregistered session under AGENTHUB_REMOTE_GATE=block, a user over quota
  (EQUOTA), a purged session (410). Device tokens 90 days, conversations 30.
- New alert rule type remote_unmonitored: a company-account session its device
  never registered; a late registration clears the flag instead.
- The hub imports a session once per upload round (chunks carry `more`),
  not once per chunk.

Artifacts:
- Share an artifact with chosen people (artifact_shares, PATCH sharedWith,
  GET /api/artifacts/people) besides everyone; a "Shared with you" tab.
  Only the owner or an admin sees or edits who it is shared with; audited.
- Folders: each account's own arrangement (agenthub.artifacts.folders pref).
- A claude.ai page keeps a copy viewable without the publishing account: its
  HTML file (and supporting files) snapshotted at publish, or the copy Claude
  saves for a Doc / Slides / Design in artifacts-export/ (a synchronous
  PostToolUse hint hook, scripts/artifact-export-hint.js, admin switch),
  attached to the page at Stop. Viewed in HtmlFrame's sandbox, Markdown, or
  pdf.js (pdfjs-dist 6.4.299, legacy build, loaded only for a PDF).

Fixes:
- tmux: a cached /proc/<pid>/exe of an exited server falls back to PATH's
  tmux (stop the last pane, start again within 10 s failed with ENOENT).
- shelves: a shelf restores beside changes in other files.
- chat: a finished reply ends the turn even with no hook at all.
- Settings → AI tools: profile list display tweaks.
2026-11-04 15:16:49 +07:00
nntrivi2001 3daf7ed4cb chore: v2.8.70 — select combobox, chat turn end without Stop, no Stop flicker 2026-11-03 14:09:39 +07:00
nntrivi2001 f9869ada6d fix(chat): a finished turn ends without a Stop hook; Stop no longer flickers
Claude Code skips the Stop hook in roughly one turn in fifteen (only the
prompt-suggestion helper's SubagentStop arrives), so the main agent stayed
"working" until the idle Notification a minute later and the chat kept
showing Stop on a finished answer. The transcript's turn_duration entry is
now an end of turn too: one newer than the session's last prompt, read
while main still works with no tool open, lands it in Waiting like Stop.

The chat's Stop button now drops to Send only after every busy signal has
been quiet for 2.5 s (Stop pressed still drops at once), so a spinner line
that comes and goes while the CLI redraws no longer flips it back and forth,
and only the newest "working" read is applied.
2026-11-03 14:02:33 +07:00
nntrivi2001 0fc436dc0c chore(select): no search box for the hour pickers and the webhook type
24 fixed hours (reports, backups) are found by type-ahead and the webhook
providers are a short fixed list; a search box there is only noise.
Branches, models, AI proxies and webhook field options keep theirs.
2026-11-03 13:33:12 +07:00
nntrivi2001 cd25791160 feat(select): long lists become a combobox; the branch switcher uses it (#88)
Select opens with a focused search box when it has more than 8 options
(or searchable): typing narrows by label/value, ↑/↓ move, Enter picks,
Esc closes; role=combobox drives the listbox via aria-activedescendant,
and options may carry a group heading. The git bar's native branch
select (and the separate search box added for #88) is replaced by it,
remote branches under their own heading.
2026-11-03 13:25:16 +07:00
nntrivi2001 2a83551eee chore: v2.8.69 — pet window width 2026-11-03 13:05:49 +07:00
nntrivi2001 d0889d5250 fix(pet): the pet window no longer scrolls sideways
The status chips' row needs ~340 px; the window was 320 and its body's
overflow-y:auto made x scrollable too, so a horizontal scrollbar showed
and Clear alerts was cut. 22rem wide (capped to the viewport), x hidden.
2026-11-03 13:05:49 +07:00
nntrivi2001 6958a7b4f6 chore: v2.8.68 — server for coding only (OOM kills the command, no tests from Claude, 4 AI sessions), pet without chat, branch search 2026-11-03 12:08:26 +07:00
nntrivi2001 aa7f1b1c60 feat(pet): remove the pet's chat
Each question started a full AI session (a heavy process on a shared
server, and one of a member's 4). A click on the pet now opens its window
(PetPanel): its name (rename), a pat, and the status strip. PetChat, its
off-screen terminal, its AI hand-off and its strings are gone; the window
is where the pet's own care comes next.
2026-11-03 12:08:14 +07:00
nntrivi2001 70d7cf30c2 feat(git): search box for the branch switcher (#88)
A Search branches… box beside Switch branch narrows the local and remote
lists to names containing the text (case-insensitive). One match is
picked at once, Enter picks the first, Esc clears. The picked branch
stays listed, so the select never loses its value.

Closes #88
2026-11-03 12:00:10 +07:00
nntrivi2001 497faae3bf feat(settings): admin switch to allow or block members' test runs
Settings → AI tools → Tests on this server (admin, S3 only). Stored in
/opt/agenthub/app/member-policy.json, which member-guard.js reads as the
member at each Bash call — no restart; no file = blocked. GET/PUT
/api/ai-tools/member-policy, audited, 409 ENOS3 outside S3.
2026-11-03 11:37:13 +07:00
nntrivi2001 0792c88612 chore: v2.8.67 — remove user chat and drop its data
The chat_* tables are dropped at start, and <data dir>/chat/ (its uploads)
is deleted.
2026-11-03 10:08:57 +07:00
nntrivi2001 a3d1dce2ca revert: remove user chat (direct + groups) from the pet window
Reverts 21a042d, 0d3e92c, 0016b6a, c797e98, 03ebea1. Keeps the pet's
own-sessions scope, status bar mine=1, scroll speed and the health hover card.
The chat_* tables and <data dir>/chat/ files are left in place (unused).
2026-11-03 10:06:12 +07:00
nntrivi2001 db2e61a74b chore: v2.8.66 — chat: notices, right-click, delete chat, Seen, chat button 2026-11-03 10:00:08 +07:00
nntrivi2001 03ebea1d92 fix(chat): the review's gaps — owner hand-over, live group lines, names, scroll, one tab's sound, uploads on disk, a chat button without the pet
- A group whose creator left or was deleted passes to its longest-standing
  member (ensureOwner), so it can still be managed.
- Group changes (added / removed / renamed / left) are sent live as
  chat_message and never count as unread, here or on the server.
- A disabled account is still named in old chats (people carries
  `disabled`) and never offered for a new one; a deleted one reads
  'Deleted user' instead of '?'.
- Load older keeps the message being read in place.
- With several tabs open only the first to see a message plays its sound /
  system notification (agenthub.chat.notified).
- The people list is read again on New chat and on every room change.
- Uploads go to <data dir>/chat/.incoming/ (multer disk storage) and are
  renamed into place — never held in memory; a refused one is removed.
- With the pet hidden, a chat button in the bottom-right corner (ChatDock,
  server mode) opens the same window, unread count and new-message card
  included.
2026-11-03 09:59:13 +07:00
nntrivi2001 c797e9868e feat(chat): delete a chat, disband a group, Seen — and purge what everyone deleted
Delete chat is the caller's alone: the history so far vanishes for them
(chat_members.cleared_id) and a direct chat leaves their list (hidden) until a
new message arrives; the others keep theirs. What EVERY member has deleted is
deleted for real (purge): messages up to the lowest cleared_id and their files
on disk, and a direct chat both people deleted, whole. Purge also runs when a
member leaves or is removed. A group's creator disbands it for everyone
(DELETE /api/chat/rooms/:id).

Seen: rooms carry every member's read mark (reads); a mark that moves is told
to the members (chat_read, exactAudience) and never passes the newest
message. Under your newest message: Seen / Seen by … / Seen by everyone.
A room open on a tab you come back to is marked read then.
2026-11-03 09:54:55 +07:00
nntrivi2001 0016b6a427 feat(chat): right-click menus everywhere in the chat, and deleting your own message
Rail: a room — open, mark as read, mute / unmute notifications (per browser,
the unread count still shows), copy name, group settings, leave; the pet and
+ — chat with the pet, new chat, mark all chats as read. A message — copy
text, reply (quotes it into the box), message the sender privately (group),
delete your own (asks first). A file — open in a new tab, download, copy file
name. A group member — message privately, copy name, remove (the creator).

DELETE /api/chat/rooms/:id/messages/:messageId deletes the caller's OWN text
message and its files (403 for anyone else, an admin included) and tells the
members with a chat_deleted frame (exactAudience).
2026-11-03 09:47:32 +07:00
nntrivi2001 0d3e92cfda feat(chat): a new message shows on the pet; chat panels scroll one way only
A message from someone else in a room not on screen raises a card on the
pet (sender · group, the text or its files; click opens the room, gone after
10 s), plays the notification sound when notifications are on, and while
the tab is hidden shows a local system notification — never through the
server's push relay, which would reach other users' devices.

Group settings scrolled both ways (the name field and Rename overflowed, the
add-people list scrolled inside the panel): one vertical scroll now, the
field shrinks, the list grows in place. Every chat list hides horizontal
overflow and a long word or link wraps.
2026-11-03 09:43:09 +07:00
nntrivi2001 1dd2521183 chore: v2.8.65 — chat between users, own-only counts, slower scroll 2026-11-03 09:36:54 +07:00
nntrivi2001 21a042dc2b feat(chat): direct chats and groups between users in the pet's window
Server mode: a rail beside the pet's chat lists the pet, then the user's
direct chats (one per pair) and groups with unread counts, and + for a new
chat. A room shows its messages (group senders named), loads older pages,
and sends text, pasted / dropped / attached files (<= 10 x 25 MB; images
inline). A group's creator renames it and adds or removes people; anyone may
leave. A blue dot on the pet counts unread messages.

Only a room's members read or write it, an admin included (roomFor → 404).
Files are stored under server-made names in <data dir>/chat/<room>/ and
served to members only under a sandbox CSP + nosniff. chat_message /
chat_room frames reach exactly the ids in `to` (scope.exactAudience).
New tables chat_rooms / chat_members / chat_messages / chat_files (additive).
2026-11-03 09:33:33 +07:00
nntrivi2001 85edc88a6d fix(status-bar): running / need-you count only the viewer's own sessions
An admin's status bar and the Dashboard's Needs-you strip counted every
user's waiting sessions; both now ask GET /api/sessions with mine=1, like
Today's spend already did.
2026-11-03 09:07:06 +07:00
nntrivi2001 c229b4b79b fix(pet): each user's pet tells only of their own sessions
An admin/analyst's pet counted every user's sessions (live, waiting, errors)
because their WebSocket carries everyone's. Server mode now marks their
session/agent/event frames `mine: true|false` (scope.broadcastAudience owners,
worked out once per broadcast), and GET /api/sessions?mine=1 lists only the
caller's own sessions (ownSessionWhere). The pet seeds from mine=1 and skips
frames marked mine: false. Developers and local mode are unchanged.
2026-11-03 09:01:57 +07:00
nntrivi2001 9721941962 feat(terminal): scroll 30% slower
lib/wheelReports.ts SCROLL_SPEED = 0.7: a touchpad's travel is scaled, and a
wheel notch's reports are carried as a fraction (rounded, so the first notch
still moves; reset when the direction turns). xterm's own scrollback gets the
same scrollSensitivity.
2026-11-03 08:52:43 +07:00
nntrivi2001 b777e08371 fix(health): the user hover card leaves out the login name
It now opens only once the users list is read (admin/analyst): role, today's
spend, workspaces, sessions and last sign-in. A developer gets no card.
2026-11-03 08:50:26 +07:00
nntrivi2001 d0b4f8aaf6 fix(health): the user hover card sits on the name row only
Over the whole tile it opened together with the CPU bar's own help tip and
the two overlapped.
2026-11-03 08:47:13 +07:00
nntrivi2001 916505fbac chore: v2.8.64 — user hover card on System Health 2026-11-03 08:38:51 +07:00
nntrivi2001 75a4693f27 feat(health): hovering a user tile shows their login, role and today's spend
System Health's Users · CPU / RAM card: each tile's hover card names the login
name; for an admin or analyst it adds role, $ spent today, workspaces,
sessions and last sign-in from GET /api/users (staff-only, re-read every 60 s).
A developer's card shows the login name alone and never calls /api/users.
2026-11-03 08:38:48 +07:00
nntrivi2001 cd6a1d36ff chore: v2.8.63 — commit signing per workspace 2026-11-02 16:50:15 +07:00
nntrivi2001 2342f54179 feat(git-identity): commit signing per workspace, and Delete in the signing card
Signing was account-wide (gpg.format/user.signingkey/commit.gpgsign in
~/.gitconfig), so every repository was signed. Now Settings → Git →
Commit signing lists the user's workspaces, each with its own key picker
("Don't sign" by default). PUT /api/me/git/signing/lane {laneId, key}
writes a small file of ours (lane-<id>.gitconfig beside the account's
keys; a member's through write-as) and includes it from ~/.gitconfig
with includeIf "gitdir:<the workspace's git dir>" and
"gitdir:<common dir>/worktrees/ah-<id>-*" (its sessions' worktrees) —
only that workspace signs, from the terminal, the Changes panel or an
agent, and nothing is written into the repository. The git dirs come
from git run in the workspace's folder as its account; a workspace that
runs as another account is EFOREIGN, a folder that is not a repo
ENOREPO. Stopping needs no workspace, so one removed from the dashboard
while it signed is still listed and can be turned off.

The card now lists the keys that can sign, each with Delete (confirm;
deleting a key stops every workspace signing with it), a way out of the
old account-wide setting, and Remove for a signing setup the user made
themselves (PUT /signing {clearForeign: true}: the three settings only,
never their key file). Importing a key no longer turns on signing.

Verified with real signatures: the picked workspace's commits are G,
another repo's and an unrelated worktree's N, a session worktree's G.
2026-11-02 16:49:27 +07:00
nntrivi2001 246b554dab chore: v2.8.62 — every account sees other users' avatars 2026-11-02 16:40:37 +07:00
nntrivi2001 600e8eec6a fix(avatars): every signed-in account sees other users' pictures
System Health shows every user's name and avatarAt to everyone, but the
picture route GET /api/users/:id/avatar sat under the staff-only /users
prefix, so a developer got 403 EADMIN and saw only initials. authGuard now
lets any signed-in account make that one read (EVERYONE_READS); every
other /users path stays staff-only, and the test checks both.
2026-11-02 16:40:29 +07:00