Replace the standalone SSE dot above UserMenu with a small badge
overlaid on the bottom-right of the user avatar. Only visible when
status is connecting (yellow) or disconnected (red) — invisible in the
normal connected state, removing constant visual noise.
Add a comment preview section below memo cards in list view, displaying
up to 3 comment snippets with a "View all" link. Removes the old comment
count icon from the memo header in favor of this richer inline display.
Comment preview is hidden in memo detail view.
- Reduce themes from 6 to 4: system, light, dark, paper
- Remove midnight (too similar to dark) and whitewall (too similar to light)
- Drop all unused tokens: chart-*, sidebar-primary, sidebar-border,
sidebar-ring, shadow-2xs, tracking-normal, spacing
- Remove redundant @theme inline blocks from dynamic theme files
(Tailwind only processes them at compile time, not at runtime)
- Move shared tokens (fonts, radius) to default.css only
- Polish dark mode color palette
- Consistent cool-slate hue (h265) across all surfaces
- Proper surface layering: sidebar(0.07) → bg(0.09) → card(0.13) →
popover(0.17) → secondary(0.19) → muted(0.21) → accent(0.22)
- Foreground softened to 0.82 (from 0.9) — comfortable off-white
- Accent more chromatic than muted (0.012 vs 0.008) — hover feels interactive
- Popover elevated above card (0.17 vs 0.13) — floating elements visible
- Destructive more vivid (0.62/c0.20) — clearly red on dark surfaces
- Input border (0.25) more distinct than layout border (0.21)
- Sidebar foreground (0.68) dimmer than content foreground (0.82)
- Fix inline code background invisible in dark mode (#5674)
- Muted was same lightness as card — increased to 0.21 for clear contrast
- Replace PAT-only auth with optional auth supporting both PAT and JWT
via auth.Authenticator.Authenticate(); unauthenticated requests see
only public memos, matching REST API visibility semantics
- Inline auth middleware into mcp.go following fileserver pattern;
remove auth_middleware.go
- Introduce memoJSON response type that correctly serialises store.Memo
(including Payload.Tags and Payload.Property) without proto marshalling
- Add tools: list_memo_comments, create_memo_comment, list_tags
- Extend list_memos with state (NORMAL/ARCHIVED), order_by_pinned, and
page parameters
- Extend update_memo with pinned and state parameters
- Extract #tags from content on create/update via regex to pre-populate
Payload.Tags without requiring a full markdown service rebuild
- Add MCP Resources: memo://memos/{uid} template returns memo as
Markdown with YAML frontmatter, allowing clients to read memos by URI
- Add MCP Prompts: capture (save a thought) and review (search + summarise)
Add migration scripts for existing instances without a storage setting
to explicitly preserve DATABASE as their storage type, ensuring backward
compatibility. Change the default for new installations to LOCAL to
improve out-of-the-box performance.
Align header padding, text color, background tokens, and item row styles
across CodeBlock, AttachmentList, RelationList, and LocationDisplay so all
metadata panels follow the same visual pattern.
- Add PR stale/close handling alongside issues
- Reduce close grace period from 7 to 3 days
- Add inline comments explaining the two-phase stale behavior
- Rename workflow and job names for clarity
When multiple tabs are open and a token expires, each tab independently
attempts a refresh. With server-side token rotation this causes all but
the first tab to fail, logging the user out.
Add a BroadcastChannel (memos_token_sync) so that when any tab
successfully refreshes, it broadcasts the new token to all other tabs.
Receiving tabs adopt the token in-memory immediately, skipping their own
refresh request and avoiding conflicts with token rotation.
Falls back gracefully when BroadcastChannel is unavailable (e.g. some
privacy modes).
Add ApplyToContext and AuthenticateToUser helpers to the auth package,
then remove the duplicated auth code spread across the MCP middleware,
file server, Connect interceptor, and gRPC-Gateway middleware.
- auth.ApplyToContext: single place to set claims/user into context after Authenticate()
- auth.AuthenticateToUser: resolves any credential (bearer token or refresh cookie) to a *store.User
- MCP middleware: replaced manual PAT DB lookup + expiry check with Authenticator.AuthenticateByPAT
- File server: replaced authenticateByBearerToken/authenticateByRefreshToken with AuthenticateToUser
- Connect interceptor + Gateway middleware: replaced duplicated context-setting block with ApplyToContext
- MCPService now accepts secret to construct its own Authenticator
Embeds a Model Context Protocol (MCP) server into the Memos HTTP
process, exposing memo operations as MCP tools at POST/GET /mcp using
Streamable HTTP transport.
Authentication is PAT-only — requests without a valid personal access
token receive HTTP 401. Six tools are exposed: list_memos, get_memo,
create_memo, update_memo, delete_memo, and search_memos, all scoped to the authenticated user.
Two bugs caused users to be redirected to /auth too frequently:
1. Race condition in Promise.all([initInstance(), initAuth()]):
initInstance() makes a gRPC request whose auth interceptor calls
getAccessToken() synchronously. When the access token was expired,
getAccessToken() eagerly deleted it from localStorage as a "cleanup"
side-effect. By the time initAuth() ran and checked hasStoredToken(),
localStorage was already empty, so it skipped the getCurrentUser()
call and the token refresh cycle entirely — logging the user out even
when the refresh-token cookie was still valid. Fix: remove the
localStorage deletion from getAccessToken(); clearAccessToken()
(called on confirmed auth failure and logout) handles proper cleanup.
2. React Query retry: 1 caused a second refresh+redirect attempt after
auth failures. The auth interceptor already handles token refresh and
request retry internally. If it still throws Unauthenticated, the
redirect is already in flight — a React Query retry only fires another
failed refresh and a redundant redirectOnAuthFailure() call. Fix: use
a shouldRetry function that skips retries for Unauthenticated errors
while keeping the existing once-retry behaviour for other errors.
The explore page sidebar was showing tags from the current user's private
memos because the default ListMemos query applies a server-side OR filter
(creator_id == X || visibility in [...]), mixing private content in.
Fix by using a visibility-scoped ListMemos request in the explore context
so private memos are always excluded via the AND'd server auth filter.
Also consolidate two always-firing useMemos calls into one context-aware
query, unify activity stats computation with countBy across all branches,
and extract a toDateString helper to remove duplicated formatting logic.
Fixes two bugs reported in #5603:
1. store/attachment.go: ignore os.ErrNotExist when removing a local
attachment file so that a missing file on disk (broken state from
failed uploads) no longer blocks deletion of the DB record, allowing
memos referencing corrupt attachments to be deleted normally.
2. memo_attachment_service.go: add nil guard on GetAttachment result
before dereferencing it in SetMemoAttachments, preventing a nil
pointer panic when an attachment UID no longer exists in the DB.
- Add plugin/webhook/validate.go as single source of truth for SSRF
protection: reserved CIDR list parsed once at init(), isReservedIP(),
and exported ValidateURL() used at registration/update time
- Replace unguarded http.Client in webhook.go with safeClient whose
Transport uses a custom DialContext that re-resolves hostnames at
dial time, defeating DNS rebinding attacks
- Call webhook.ValidateURL() in CreateUserWebhook and both
UpdateUserWebhook paths to reject non-http/https schemes and
reserved/private IP targets before persisting
- Strip internal service response body from non-2xx error log messages
to prevent data leakage via application logs
Switch from sessionStorage to localStorage so the auth token survives
across tabs and browser restarts, matching standard platform behavior.
Also guard the signup redirect in App.tsx behind profileLoaded to avoid
a false redirect when the instance profile fetch fails.
When no token exists in sessionStorage, AuthContext.initialize() was
still calling GetCurrentUser, triggering the auth interceptor to attempt
RefreshToken and retry — producing a burst of 5+ auth API calls in under
a second that reverse proxies with rate limiting (e.g. CrowdSec) flag as
brute force.
Add hasStoredToken() to auth-state and bail out of initialize() early
when there is definitively no session to restore. The refresh flow for
expired tokens is preserved since hasStoredToken() checks for presence
regardless of expiry.
Fixes#5647
The closest() selector was targeting a CSS class that never existed on
the container, causing fallback to document.body and collecting task
items across all visible memos. This caused index collisions when
multiple memos with todo lists were on the page.
Adds data-memo-content attribute to the container and updates the
selector accordingly.
Fixes#5635
Wrapping the returned function in useCallback prevents a new reference
on every render, which was causing an infinite startViewTransition loop
on the initial signup page (fresh install with no admin).
Fixes#5626