The API rejects a save that changes the title while most of the note
content disappears with an English message, and clients showed it
verbatim in every locale.
- shared: add editor.saveState.errorReason.suspiciousOverwrite in all
four catalogs and getMemoSaveErrorReasonKey(), matching the error code
or the server message; the API reuses the exported code and message.
- desktop sync details: translate the item by lastErrorCode.
- Android: map the stored message to its zh-CN source text so the
localized components translate it.
- web editor: show the reason in the save status tooltip and label
instead of the bare "Could not save".
* refactor: resolve oxlint correctness findings without behavior changes
- Snapshot iterations that mutate their collection with Array.from()
instead of a spread (live childNodes, self-removing disposers, a Set
extended inside its own loop).
- Drop empty spread fallbacks, redundant regex escapes, Boolean() casts
inside negations, a rethrow-only try/catch and statement ternaries.
- Use startsWith/endsWith for anchored literal checks in the extension.
- List U+FE0F explicitly in the mobile attachment emoji class; the
matched set is unchanged.
- Fix the WeChat copy fixture, where \oint in a template literal lost its
backslash and the block formula never exercised \oint.
- Document the intentional control-character regexes (filename
sanitizing and input validation), the throwing provider stream test,
Astro's generated type reference and the mobile auto-sync finally
block with narrow inline exceptions.
* ci: run oxlint correctness rules on pull requests
Pin oxlint 1.88.0 and enable only its correctness category across apps/
and packages/, excluding build output, vendored code and the iOS app.
Findings are errors and the script passes --deny-warnings, so CI fails
on any finding. no-unused-vars stays off until unused code is removed
in a dedicated pass.
The shape-library trigger opened its menu on every pointerenter. When the
editor replaced the note list under a pointer that did not move, or when
the pointer only passed over the toolbar, the menu opened by itself and
stayed open. For mind maps and flowcharts that menu was modal, so the
back-to-list button could not be pressed until the menu was closed. On
touch screens pointerenter fires right before the pointerdown that
toggles the menu, so a tap could open it and close it again.
- Open the menu on hover only for mouse pointers.
- Make the mind-map and flowchart insert menu non-modal, as the
architecture library already is.
New mind maps, flowcharts and architecture diagrams always started with
Chinese labels, whatever language the interface used.
- createDefaultDiagramDocument takes optional starter labels. Without
them it returns the existing Chinese starter unchanged, so the other
callers (tests, desktop sidecar checks) keep their output.
- The web client passes translated labels from new diagram.seed keys in
en-US, zh-CN, ja and pl.
- fitDefaultDiagramLabels sizes the nodes and lays the starter out again
only when a translated label needs more room than the default label it
replaces, so the Chinese starter keeps its tuned geometry. The helper is
loaded with the lazily loaded diagram editor, which keeps dagre out of
the workspace chunk; if that import fails the note is still created
with the starter geometry.
* feat: add table context menu with copy and paste
* fix(web): preserve native input and test table context menu regressions
* fix: harden table context actions and desktop clipboard
Turn on both compiler checks in the root TypeScript configuration, which
apps/web, apps/api, apps/extension, packages/shared and packages/plugin-api
extend, and in the separate Expo configuration of apps/mobile.
No switch statement falls through today. The 19 members that override a
base class member (React error boundaries, the local database recovery
error and the vendored poster badge) now declare `override`; this only
changes type checking and is erased from the emitted JavaScript.
* feat(memos): duplicate a note with its files
Add "Duplicate note" to the note list's context menu and the text editor's
"More note actions" menu. The copy keeps the note's content and tags, is
titled "<title> (copy)" in the UI language, and opens in the editor.
POST /api/v1/memos/:id/duplicate (write:memos) copies every image and
attachment of the note to a new resource with its own stored object and
rewrites the content to reference the copies, so editing or deleting either
note never affects the other. Shares, revisions, the pinned state, public
form links and unsubmitted form uploads are not copied. A note with more
than 20 files or more than 64 MiB of files is refused, and files written for
a copy that fails are removed.
The Web and desktop clients synchronize the note's queued changes before
duplicating and refuse while any remain, including unresolved conflicts;
the editor item stays disabled until the current edit is saved.
* fix(memos): keep committed duplicate files and check the whole desktop outbox
- Copied objects are removed only when the write batch fails. A failure
after the commit, such as a transient error while reading the new memo,
no longer deletes files that committed resource rows reference. When the
memo row is missing after the commit (its notebook was removed meanwhile),
the rows are removed before their objects.
- The sidecar lists at most 200 outbox items, so a conflict of the note
further down the queue was invisible and the stale server version could be
copied. The desktop client now compares the listed items with the
sidecar's status counts and refuses to duplicate unless the whole
outstanding queue was listed, nothing is being synchronized, and no item
belongs to the note.
- Regression tests cover a read failure after the commit and a target
conflict at position 201 behind 200 other conflicts.
Search raw Markdown in source mode with literal character matching, batched undoable replacement, and matching highlights. Preserve query whitespace and keep replacement focus stable. Route source editor search shortcuts through the workspace controller.
Validation: 141 editor tests and 2 Chrome end-to-end cases passed; web and mobile typechecks and the web build passed.
Refs #574
* feat(templates): show unedited default templates in the current UI language
Default templates are stored in the language of the workspace's first
sign-in, so switching the UI language left them in that language. While a
default template still equals one of its seeded translations, the web
template center and the Android template picker now show it in the current
UI language, and using it creates the note in that language. Templates the
user created or edited are shown and used exactly as stored.
POST /api/v1/templates/:id/use accepts an optional `locale`; without it the
stored template is used as before, so MCP, agents and older clients are
unaffected.
* fix(templates): edit the stored template and stop localizing retagged defaults
- The template center now keeps each card's stored template next to its
display translation. Editing starts from the stored fields, so saving a
name-only change in another UI language no longer writes the translated
title, description and Markdown.
- A default template whose tags differ from its seeded tags is treated as
edited and is no longer localized.
- Add regression tests for the name-only edit and changed tags.
* feat(workspace): localize default notebooks and welcome note at first sign-in
A new installation always created its five default notebooks, welcome
note and note templates in Chinese, whatever the interface language.
Members added by the owner got notebooks and templates in the owner's
language instead of their own.
The seed data is now localized once, at the first sign-in of the
workspace's user, using the UI locale the client sends with the login
request (Web and Android send it; other clients fall back to
Accept-Language, and unsupported languages to English).
Migration 0070 marks the default workspace as waiting for this only on
an installation nobody has used yet: no users, no notes beyond the
welcome note, no edits to it, no extra notebooks, revisions or
resources. Existing installations keep their data unchanged. Member
workspaces created by the owner are marked when created.
The sign-in localizes notebook names, templates, the welcome note and
its search document in one transaction that also clears the mark, and
replaces only values that still equal a seeded translation. A failed
attempt leaves the mark for the next sign-in to retry, and a second
sign-in changes nothing. The demo clears the mark so its bilingual seed
stays as it is.
Refs #483
* fix(workspace): guard seed inserts and title-only welcome edits during localization
- Default template and AI prompt inserts in the first-sign-in batch now
require the pending marker, so a delayed concurrent sign-in cannot
recreate seeds removed after the faster sign-in finished.
- The welcome note is treated as untouched only while it keeps the seeded
title, the system actor, the seed content hash, and revision 0, so a
title-only edit is no longer overwritten.
- Add regression tests for both cases.
Refs #483
Add notebook-level share management, directory navigation, and live subtree checks for notes and attachments. Include migration and access-boundary regression tests.
Refs #551
Preserve HTTP status and fixed failure categories for direct and proxied agent requests, display diagnostics beside failed turns, and prevent AI SDK error logging from exposing request data.
Refs #555
Add owner-only attachment summaries and a compact member management table. Keep personal attachment statistics unchanged and exclude Trash from usage totals.
Refs #553
Record sanitized request URLs, redirects, timing, HTTP errors and proxy resolution from the actual updater session. Include the trace in copyable update diagnostics. Refs #513