Run the instance in private mode when instance_url is not configured: the API rejects anonymous requests except the auth-bootstrap set (sign-in, token refresh, instance profile/settings, SSO providers, share-link access) plus first-run user creation, and the web UI redirects anonymous visitors to /auth instead of /explore. Setting instance_url keeps the current public behavior. Access tokens and personal access tokens are never gated.
Enforcement lives in a shared Authorizer used by both the Connect interceptor and the gRPC-gateway middleware; the file server applies the same rule to public-memo attachments and avatars. Also merges the duplicated Authenticate/AuthenticateToUser token dispatch behind resolveBearer, dedups the AuthContext unauthenticated state, extracts the redirect decision into a pure shouldGatePrivateInstance helper, and prints the access mode at startup.
Bold (and italic/code) now remove an empty just-inserted delimiter pair instead of nesting more asterisks, and strip real marks whichever way the selection was made; clicking Link inside a link unwraps it instead of inserting [](); the three list modes convert between each other (bullet on a task line no longer leaves '[ ] ...' behind), toggle off any ordered number, respect indentation, and apply across multi-line selections; list/heading edits keep the cursor in place (task insert on an empty line lands after the marker).
Structurally, highlight and toggle now share one line-mode detector so they cannot disagree; the heading regex is shared with headingDecorations, so indented headings render styled; one MARKS table drives all inline-mark behavior; run("link") is total (URL doubles as label on empty selection, now inserted inline rather than as its own block); the unused getSelectedText contract method and the dead markdown-list-detection.ts util are removed.
Known divergence, not addressed here: markdown-task-actions.ts TASK_LINE_REGEXP accepts ordered task items ('1. [ ]') which the editor's TASK_LINE does not; the viewer toggles such checkboxes but the editor toolbar won't detect them.
Rebuild the memo editor as a single CodeMirror 6 "decorated source"
editor. The document is the raw markdown, stored verbatim and styled in
place (markers stay visible), so the editor never serializes a tree back
to markdown — removing the round-trip fidelity bug class (inline images,
setext headings, ordered-list indentation, HTML entities) that the old
editor needed per-case patches for.
- MemoEditor/Editor: CodeMirror 6 + lang-markdown (GFM). Tokens, heading
lines, #tag/@mention, and the autocomplete popover are styled in plain
CSS (Editor/editor.css) with theme tokens, not a CSS-in-JS theme.
- Tab/Shift-Tab nest/outdent list items (marker-aware, ordered items
renumbered so nesting is CommonMark-valid); Escape blurs; #tag
autocomplete sourced from useTagCounts.
- Focus-mode toolbar reimplemented as markdown-text ops; active state read
from the Lezer tree via the backend-agnostic formatting/commands catalog.
- Remove the old serialize-back-to-markdown editor (its Editor dir,
PlainEditor, the editor-mode system) and its now-unused dependencies
(marked, textarea-caret, and the rich-text editor packages).
- Consolidate toolbar components under Toolbar/. Read-only MemoContent
rendering is unchanged.
Mirror the #tag mark so the TipTap editor styles @username while typing
and on load, matching the read-only view. A shared mention grammar
(utils/mention-grammar.ts) feeds both the editor tokenizer and the
read-only remark renderer so they can't drift.
Styling only -- no autocomplete dropdown (ListUsers is admin-only and
there is no user-search RPC). Bare emails still autolink to mailto: and
are never treated as mentions: the tokenizer's start() skips email-glued
@s so marked's GFM autolinker keeps seeing the whole address.
Memos layers #tag syntax on top of CommonMark, but there was no way to
write a literal "#NAS" — it always became a clickable tag (issue #6035).
Handle escapes lexically, the way TipTap/prosemirror-markdown do, with no
"escaped tag" node in the document model:
- Parse: marked's built-in backslash escape already turns "\#NAS" into
plain text; the read-only renderer (remark-tag) now honors the same
escape by lexing from the original source slice and falling back to the
prior value-based parse when it can't faithfully reconstruct the text.
- Serialize: a tag-aware Markdown extension (tagMarkdown.ts) backslash-
escapes a "#" that would re-parse into a tag, skipping links and code
where tags never form.
- The editor's #tag lexing moves to the canonical @tiptap/markdown
markdownTokenizer; tag-in-link skipping moves to a tree pass so the
editor and renderer agree.
The capped tag-run grammar is consolidated into utils/tag-grammar.ts
(TAG_RUN), shared by the tokenizer, the serialize-escape, and the
renderer so they can't drift. README documents why the markdown manager
is worked around in three places.
Behavior-preserving restructure of the MemoEditor subsystem for simplicity
and extensibility:
- Add a data-driven command catalog (editorCommands.ts) as the single source
for formatting verbs; the toolbar, active-state hook, and WYSIWYG handle all
derive from it, so adding a verb is a one-file change.
- Add a createSuggestionExtension factory; TagSuggestion consumes it, so new
`/` or `@` triggers reuse the shared popup in ~10 lines.
- Collapse FormattingController into a single EditorController with an optional
`formatting` capability; PlainEditor is an honest textarea fallback (no faked
formatting); replace stringly-typed isActive with typed getActiveFormats.
- Move audio-recorder state out of the reducer into useAudioRecorder, keeping a
single recorderBusy flag in the store.
- Convert the editor context to an external store (useSyncExternalStore) with
per-slice subscriptions, so typing no longer re-renders the toolbar, insert
menu, or metadata.
- Extract the shared #tag lexing grammar (tag-grammar.ts) used by both the
editor tokenizer and the remark renderer.
- Remove dead reducer actions/cases and fix a preview blob-URL leak on the
upload path.
Add tests for the command catalog, suggestion factory, and autosave.
- 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
Fixes issue where OAuth sign-in fails with 'Cannot read properties of
undefined (reading 'digest')' when accessing Memos over HTTP.
The crypto.subtle API is only available in secure contexts (HTTPS or
localhost), but PKCE (RFC 7636) is optional per OAuth 2.0 standards.
Changes:
- Make PKCE generation optional with graceful fallback
- Use PKCE when crypto.subtle available (HTTPS/localhost)
- Fall back to standard OAuth flow when unavailable (HTTP)
- Log warning to console when PKCE unavailable
- Only include code_challenge in auth URL when PKCE enabled
The backend already supports optional PKCE (empty codeVerifier), so no
backend changes needed. This fix aligns frontend behavior with backend.
Benefits:
- OAuth sign-in works on HTTP deployments (reverse proxy scenarios)
- Enhanced security (PKCE) still used when HTTPS available
- Backward compatible with OAuth providers that don't support PKCE
Fixes#5570
Root cause: enabled={isInitialized && !!user} prevented displaying cached
data when user auth state transitioned during token refresh.
Changes:
- Remove !!user check from Home page enabled condition
- Add clearAccessToken() in redirectOnAuthFailure for clean logout
Fixes#5565
- Remove menu item and dialog from MemoActionMenu
- Remove removeCompletedTasks() and hasCompletedTasks() utilities
- Remove translation keys from all 34 locale files
- Feature was not aligned with standard note-taking UX patterns
- Updated the isSuperUser function to only check for ADMIN role.
- Added SQL migration scripts for MySQL, PostgreSQL, and SQLite to change user roles from HOST to ADMIN.
- Created a new SQLite migration to alter the user table structure and ensure data integrity during the migration process.
- Deleted MemoContentContext and its associated types.
- Updated Tag and TaskListItem components to use MemoViewContext instead.
- Refactored MemoContent component to eliminate context provider and directly use derived values.
- Simplified MemoViewContext to only include essential data.
- Enhanced error handling in various components by introducing a centralized error handling utility.
- Improved type safety across components and hooks by refining TypeScript definitions.
- Updated remark plugins to enhance tag parsing and preserve node types.
Fixes#5319. Checkboxes inside code blocks were incorrectly counted when
toggling tasks, causing the wrong checkbox to be checked. Replaced regex-based
task detection with mdast AST parsing which properly ignores code block content.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract preference logic into dedicated hooks (useUserLocale, useUserTheme)
- Add applyLocaleEarly() for consistent early application
- Remove applyUserPreferences() from user store (now redundant)
- Simplify App.tsx by moving effects to custom hooks
- Make locale/theme handling consistent and reactive
- Clean up manual preference calls from sign-in flows
Fixes locale not overriding localStorage on user login.
Improves maintainability with better separation of concerns.
Add custom remark plugin to prevent setext headers (headers using === or --- underlines) from being recognized by the markdown parser. The plugin disables the setextUnderline construct at the micromark parser level.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove theme and locale from instance settings to eliminate duplication and
simplify the codebase. These are user-specific preferences and should only
exist in user settings, not instance-wide settings.
Backend changes:
- Remove theme from InstanceGeneralSetting proto
- Remove locale from InstanceCustomProfile proto
- Update instance service converters to remove theme/locale handling
- Simplify RSS feed to use static locale
Frontend changes:
- Remove theme/locale from instanceStore state
- Create unified initialization flow with clear fallback priority:
* Theme: user setting → localStorage → system preference
* Locale: user setting → browser language
- Add applyUserPreferences() to centralize theme/locale application
- Simplify App.tsx by removing redundant state synchronization
- Update all components to use new helper functions:
* getThemeWithFallback() for theme resolution
* getLocaleWithFallback() for locale resolution
- Remove theme/locale selectors from instance profile dialog
Theme utilities refactor:
- Organize code into clear sections with JSDoc comments
- Extract localStorage operations into getStoredTheme/setStoredTheme helpers
- Split DOM manipulation into focused functions
- Improve type safety with Theme and ResolvedTheme types
- Reduce code duplication and improve maintainability
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements critical OAuth 2.0 security improvements to protect against authorization code interception attacks and improve provider compatibility:
- Add PKCE (RFC 7636) support with SHA-256 code challenge/verifier
- Fix access token extraction to use standard field instead of Extra()
- Add OAuth error parameter handling (access_denied, invalid_scope, etc.)
- Maintain backward compatibility for non-PKCE flows
This brings the OAuth implementation up to modern security standards as recommended by Auth0, Okta, and the OAuth 2.0 Security Best Current Practice (RFC 8252).
Backend changes:
- Add code_verifier parameter to ExchangeToken with PKCE support
- Use token.AccessToken for better provider compatibility
- Update proto definition with optional code_verifier field
Frontend changes:
- Generate cryptographically secure PKCE parameters
- Include code_challenge in authorization requests
- Handle and display OAuth provider errors gracefully
- Pass code_verifier during token exchange
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>