Commit Graph
2229 Commits
Author SHA1 Message Date
tianma-if 2605774ecd docs(site): modernize three-pane and brand messaging across locales 2026-10-17 01:05:49 +08:00
tianma-if 8726df7542 feat(fonts): show loading status and retry failed font loads 2026-10-17 01:03:25 +08:00
tianma-if 9dc05c2480 feat(fonts): unify catalog for body and poster editors 2026-10-16 22:59:19 +08:00
tianma-if 12853c0624 fix(poster): move font selector to top of inspector 2026-10-16 22:49:42 +08:00
tianma-if a2a0f37377 Merge remote-tracking branch 'origin/main' 2026-10-16 22:41:16 +08:00
tianma-if 20a3851958 feat(share): add cached fonts and font picker previews 2026-10-16 22:40:12 +08:00
tianma-if 1d72367a9b fix(web): make save status tooltips keyboard accessible 2026-10-16 22:10:30 +08:00
Dariusz Koryto 50e4a1a7f2 fix(i18n): localize the suspicious overwrite save rejection (#593)
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".
2026-10-16 22:09:26 +08:00
Dariusz Korytoandtianma-if ee5e9621d5 fix(mobile): keep automatic sync alive when settling a pass fails (#597)
The automatic sync runner reset runningRef inside a finally block, after
awaiting refreshSyncQueueItems(). If reading the queue threw there,
runningRef stayed true and every later call returned early as if a pass
were still running, so automatic sync stopped until the app restarted.
A failing getMobileSyncRetryDelay() also surfaced as an unhandled
rejection from the void-ed run.

Move the settle step into settleMobileSyncPass(), where refreshing the
visible queue and reading the retry delay each handle their own errors
and the runner is always marked idle. Ordering is unchanged: refresh,
mark idle, run a pass requested meanwhile, otherwise schedule the retry.
This also removes the return inside finally reported by
no-unsafe-finally.

Co-authored-by: tianma-if <yingwaizhiying@gmail.com>
2026-10-16 22:07:23 +08:00
tianma-if 29ff4d14e0 Merge remote-tracking branch 'origin/main' 2026-10-16 21:58:11 +08:00
tianma-if 776fc3b566 feat(share): refine image themes and persist settings across clients 2026-10-16 21:57:49 +08:00
Dariusz Koryto bec688b91a ci: add oxlint correctness check and resolve its findings (#592)
* 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.
2026-10-16 21:57:36 +08:00
Dariusz Koryto c2ff5ca1b6 fix(diagram): keep the shape menu from blocking the editor (#595)
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.
2026-10-16 21:53:10 +08:00
Dariusz Koryto d96b8f6e69 feat(diagram): start new diagrams with labels in the interface language (#594)
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.
2026-10-16 21:53:03 +08:00
Thorsten Ruf 640f973997 fix(web): open internal note links in the target notebook (#581)
* fix(web): open linked notes in their target notebook

* fix(web): release pending note selection on manual scope changes
2026-10-16 21:52:57 +08:00
tianma-if 70d486e4d7 fix(desktop): exclude development files from production packages 2026-10-16 21:44:29 +08:00
tianma-if 2c1b713ffa docs: modernize product positioning and decouple evernote coupling across locales 2026-10-16 13:09:13 +08:00
yulei666andClaude Opus 5.5 e85916de78 fix(desktop): connect to WorkBuddy 5.8+ lite-wb CLI over stdio ACP (#586)
WorkBuddy 5.8+ ships only dist/codebuddy-lite-wb.mjs. Its loader picks that
bundle only with CODEBUDDY_FORCE_LITE_WB_BUNDLE=1, otherwise --acp requires the
removed dist/codebuddy and exits. A bare --acp in the lite-wb bundle also runs a
one-shot print session; stdio ACP is served only together with --serve.

When the lite-wb layout is detected, set the bundle variable and launch with
--acp --serve --acp-transport stdio. --serve also starts WorkBuddy's local
gateway, so pin it to loopback with --host 127.0.0.1 and SERVER__HOST, which
overrides an inherited SERVER__HOST; the port stays WorkBuddy's random default.
Older layouts keep the previous launch, and the login flow adds only --port
when the command already serves.

Fixes #584

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:00:35 +08:00
yulei666andClaude Opus 5.5 d26988e0ee fix(desktop): allow clearing local data from the scoped userData directory (#585)
The packaged app is named @edgeever/desktop, so Electron stores userData at
<appData>/@edgeever/desktop. The reset guard only accepted a direct child of
appData and therefore always failed with unsafe-data-directory. Also accept a
single @scope/name directory while still rejecting other nested paths.

Fixes #583

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 12:52:41 +08:00
tianma-if 0d1682e48d fix(web): show local agent connection failure reasons 2026-10-16 12:06:36 +08:00
Thorsten Ruf 367cb8368d feat(web): add a table context menu with copy and paste (#579)
* 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
2026-10-16 11:24:46 +08:00
tianma-if 1d6cc281e2 chore: release v1.120.0 [skip ci] 2026-10-16 01:51:16 +08:00
tianma-if 79ae74069b chore(demo): use default plain theme for flowchart and mind map seed notes 2026-10-16 00:27:47 +08:00
tianma-if 80038dd2d1 feat(editor): refine note image share themes with typography affinity and curated layout 2026-10-16 00:10:18 +08:00
tianma-if 3853b54c1b test(desktop): separate POSIX pipe inheritance from portable closure checks 2026-10-15 22:24:41 +08:00
tianma-if 3eaad71509 fix(desktop): await ACP process closure before workspace cleanup
Track close separately from exit, preserve probe outcomes on cleanup errors, and cover inherited pipes and Windows cwd locks. Add cross-platform CI with the official Codex ACP process chain smoke check.

Refs #523
2026-10-15 22:19:48 +08:00
Dariusz Koryto 3e54226110 chore(ts): enable noFallthroughCasesInSwitch and noImplicitOverride (#577)
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.
2026-10-15 21:54:59 +08:00
LuoQianhong 1bb9580007 feat(extension): expand vertical and article platform clipping (#578) 2026-10-15 21:39:41 +08:00
tianma-if 76d096ed83 docs(site): align site taglines with updated positioning across locales 2026-10-15 21:35:44 +08:00
Dariusz Koryto 72ab4816a1 feat(memos): duplicate a note with its files (#566)
* 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.
2026-10-15 16:54:04 +08:00
tianma-if 544a6e8e8c chore: release v1.119.0 [skip ci] 2026-10-15 12:46:48 +08:00
tianma-if 71e2395e28 fix(editor): search and replace in the active editor mode
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
2026-10-15 11:43:34 +08:00
tianma-if f4b36fc64f fix(desktop): repair ACP updates and add manual retry 2026-10-15 11:10:39 +08:00
Dariusz Koryto fc98dd6720 fix(api): accept file uploads on public table forms (#571)
* test(api): cover public table form access, uploads and submissions

Run the public form routes and the owner form settings routes on SQLite
with every migration and filesystem storage: unknown, closed and
deleted-note tokens, password unlock and its rate limit, the fields a
form publishes, stale pending upload cleanup, field validation, and
which uploads a submission may attach.

* fix(api): accept file uploads on public table forms

Public form routes run without a signed-in user, but storing an upload read
the workspace from the auth context, so every valid upload to
POST /api/public/forms/:token/resources failed with a TypeError (HTTP 500).
In the form page the visitor's row was then saved without its file.

- Resource creation accepts the note's workspace explicitly, and the form
  upload passes the form's workspace.
- A form accepts uploads only when it publishes an attachment field, and
  holds at most 50 uploads that were not submitted yet (stale ones are
  still retired after an hour). The limit is checked again after storing,
  so concurrent uploads cannot exceed it.
- A submission marks as used only the files its new row references; a file
  sent in another cell stays pending and is retired like other stale
  uploads instead of being kept forever.
2026-10-15 09:23:42 +08:00
Dariusz Koryto 4754eccb94 fix(api): validate a restore page before writing; test JSON backup restore end to end (#570)
* test(api): cover JSON backup restore end to end

Export a workspace through GET /api/v1/backups/json and restore it into
another installation through the restore routes, on SQLite with every
migration and filesystem storage, the way the ZIP backup does:

- notebooks, notes, revisions, tags, pinned state and files round-trip
  unchanged, and the restored notes are searchable and tagged;
- restoring the same backup twice changes nothing and keeps one object
  per file, and re-restoring a file replaces and removes its old object;
- restoring brings back notes and notebooks deleted after the backup and
  undoes later edits;
- damaged or minimal content falls back safely (empty document, Markdown
  derived from JSON, default title, normalized tags);
- ids used by another workspace, notebooks or notes pointing outside the
  workspace, revisions of another note, invalid file metadata, missing
  notes, empty files and another user's file id are rejected without
  writing;
- only signed-in users can restore.

Raises line coverage of backup-service.ts from 6% to 99% and of
backup-routes.ts from 46% to 94%.

* fix(api): validate a restore page before writing any note

restoreJsonMemos checked that each revision belongs to its note inside the
write loop, so a page whose second note carried another note's revision
returned 400 after the first note had already been restored. The check now
runs for the whole page before anything is written.
2026-10-15 09:23:18 +08:00
Dariusz Koryto 41bce7a126 test(api): cover note trash, user and session management, and object storage settings (#572)
* test(api): cover note trash, restore and permanent deletion

* test(api): cover user management and login session endpoints

Exercise the user and session routes against an in-memory SQLite database with every migration applied and sessions created through real password logins: owner-only access, member provisioning, username and password validation, password resets and account disabling revoking sessions, per-device session listing, renaming and revocation, and per-user storage usage.

* test(api): cover object storage settings and credential handling

Exercise the object storage routes and resolution helpers against a fully migrated in-memory SQLite database with a stubbed S3 endpoint: owner-only access, secret redaction and encryption at rest, input validation, switching providers, per-resource storage resolution, fallback-key credential migration, undecryptable credentials and grouped deletion.
2026-10-15 09:23:06 +08:00
Dariusz Koryto bbe80d18db fix(api): store LF line breaks for multipart image notes; make ACP host tests portable (#568)
* fix(api): store LF line breaks for multipart image notes

multipart/form-data encodes line breaks in text fields as CRLF. Browsers
always do so, and Bun 1.4 now does as well, so notes created through
POST /api/v1/memos/with-image (used by the Web Clipper) were stored with
CRLF line breaks, while every other note uses LF. The route now normalizes
the Markdown field to LF before creating the note.

This also fixes the existing route contract test under Bun 1.4, and a new
test sends a CRLF multipart body like a browser does.

* test(desktop): make ACP host tests independent of the machine and Bun version

- The ACP command allow-list test checked that a missing command is not
  installed, but on macOS the resolver also looks in /opt/homebrew/bin and
  /usr/local/bin, so it failed on any machine with that command installed.
  The test now only sees its temporary directory.
- The fake WorkBuddy agent started its account API without a port during
  probes and chats. Bun 1.3 then listened on a random port, while Bun 1.4,
  like Node, rejects port NaN, so six WorkBuddy session tests hung until
  their timeout. Like WorkBuddy itself, the fake agent now serves the
  account API only when started with a port.
2026-10-15 09:22:42 +08:00
Dariusz Koryto 9bb3218c03 feat(templates): show unedited default templates in the current UI language (#563)
* 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.
2026-10-15 09:10:38 +08:00
tianma-if 2977f07bdb fix(desktop): support bundled WorkBuddy credential codec exports 2026-10-15 01:20:12 +08:00
Dariusz Koryto b3f4dfda2b feat(workspace): localize default notebooks and welcome note at first sign-in (#562)
* 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
2026-10-14 20:45:42 +08:00
tianma-if 3c77c67155 fix(desktop): defer local AI probes until selector opens
Record adapter process lifecycle diagnostics to correlate transient Windows consoles.

Refs #561; Windows device verification remains pending.
2026-10-14 17:53:03 +08:00
tianma-if 2ba1e8e70e chore: release v1.118.0 [skip ci] 2026-10-14 11:03:01 +08:00
tianma-if 8127802f32 chore: release v1.117.0 [skip ci] 2026-10-14 10:56:01 +08:00
tianma-if 21d291dca5 test(desktop): avoid filesystem order assumptions in share recovery 2026-10-14 10:54:27 +08:00
tianma-if c6a5eeef5d chore: release v1.116.0 [skip ci] 2026-10-14 10:51:28 +08:00
tianma-if 4c592d68a1 fix(ai): clarify current note context for local agents 2026-10-14 10:48:42 +08:00
tianma-if c78f17a800 fix(desktop): restore WorkBuddy sign-in after connection failure 2026-10-14 10:40:53 +08:00
tianma-if d18c63618f fix(desktop): persist failed share imports across restarts 2026-10-14 10:33:10 +08:00
tianma-if 82fa24c3a0 fix(sharing): align memo sharing actions and allow revocation retries 2026-10-14 10:27:01 +08:00
tianma-if 043752546f fix(web): correct sharing dialog initial focus and close button rings 2026-10-14 10:12:25 +08:00