mc35 downloadsFind notes that were most recently synced into your vault by Syncthing or other third-party tools, and open them in one tap. Sort by arrival, created or modified time, size, or pinyin initials.
Find the notes that most recently arrived in your vault, and open them in one tap.
If you sync your vault to your phone with Syncthing, Resilio or anything similar, this answers a simple question: which notes just arrived? Note that it is not the same as Obsidian's own recent-files list, which remembers what you opened — this remembers what arrived.
Built for synced vaults and tuned for Android. It exists because plugins that show "recently added files" rely
on Obsidian's create event, so they never notice files that appear while Obsidian is closed — which is exactly
when a sync tool does its work.
| Plugin ID | latest-arrivals |
| Sort keys | Arrival time · file name (pinyin initials) · created time · modified time · size |
| Interface language | English, 简体中文, 繁體中文 — follows Obsidian by default |
| Bundle | main.js ~81 KB (gzip ~21 KB), zero runtime dependencies |
| Platform | Android, iOS, and desktop (isDesktopOnly: false) |
| Minimum Obsidian | 1.8.7 |
| Network | None. No requests, no telemetry, no self-updating |
Plugins such as Recently Added Files listen only to
Obsidian's create event, and register that listener after the workspace is ready. Files written by an external
sync tool arrive while Obsidian is closed, so they are picked up by the startup scan and never fire an event.
They are silently missed.
This plugin does not depend on event timing: every refresh performs a full-vault set difference.
Relationship to Recently Added Files. This project is not a fork and contains no code from it. Its source was read only to diagnose the cause above, and credit is due for that starting point.
The plugin keeps a table of path → first-observed time. Where that table lives is the crux:
| Storage | Synced between devices? | |
|---|---|---|
data.json in the plugin folder |
Yes | ❌ devices overwrite each other |
App.saveLocalStorage() |
No | ✅ device-local, per vault |
Every refresh:
vault.getMarkdownFiles() plus in-memory TFile.stat. No I/O, milliseconds.adapter.list, to catch files Obsidian has not indexed yet.size and mtime is treated as a rename and inherits it; anything else is a new arrival.Arrival time is the default sort key because it is the only signal that does not depend on file attributes.
An external sync tool preserves mtime, so mtime really means "when you wrote the note elsewhere", and
ctime is unreliable on Android.
A newly discovered file therefore uses max(ctime, mtime) when that falls in the window since the last scan,
and is otherwise recorded as arriving now — so a freshly synced note always sorts to the top.
Obsidian's directory asks plugins to disclose what they touch. This plugin:
| Behaviour | Why |
|---|---|
Enumerates the vault (vault.getMarkdownFiles(), plus an optional recursive adapter.list) |
It has to see the full set of notes in order to compute "what is new". The deep scan exists to catch files Syncthing has written but Obsidian has not indexed yet, and can be turned off in settings. |
| Reads and writes vault files | Reads notes to open them; writes only the optional configuration export at the vault root. |
| Clipboard | None. The plugin neither reads nor writes the clipboard. |
Device-local key/value storage (App.saveLocalStorage) |
Stores the arrival ledger and a settings backup. Never synced. |
| Network | None. No network requests, no telemetry, and the plugin does not update itself or its dependencies. |
| Access outside the vault | None. No Node/Electron APIs, no adapter.getFullPath(). The deep scan starts at the vault root and only ever descends into directories it listed itself. |
File name (pinyin initials) · Arrival time · Created time · Modified time · Note size
Pinyin sorting is implemented with zero dependencies. The JavaScript runtime's ICU already ships Chinese collation, so the plugin binary-searches a small table of syllable-boundary characters to find a character's initial, then applies a sub-1 KB override table for polyphonic characters. Verified results:
知识管理→ZSGL 本地部署→BDBS 微信→WX 数学→SX 法律→FL 经济→JJ
物理→WL 化学→HX 中国→ZG 北京→BJ 上海→SH 长沙→CS 厦门→XM
At startup the plugin self-tests whether Intl.Collator('zh') really has pinyin data. If the device's WebView
lacks it, sorting degrades to code-point order and says so in the settings, rather than silently producing
the wrong order.
| Entry | Notes |
|---|---|
| Sidebar tab (recommended) | Mounted automatically; position (left / right / off) is configurable |
| Command palette | Latest Arrivals: Open latest arrivals |
| Quick Action (mobile) | Point it at Open latest arrivals and a pull-down from the top of the screen opens the list |
| Editor toolbar (mobile) | Settings → Interface → Toolbar → add the command |
| File context menu | Open "Latest arrivals" list · Ignore from latest arrivals |
The quick list shows 1–10 notes (configurable). Tap a row to open it; long-press for the context menu.
On mobile there is no ribbon to configure. Obsidian's mobile app has no ribbon settings page (searching settings for "Ribbon" finds nothing), which is why the sidebar tab and the Quick Action are the recommended entry points there.
Settings → Latest Arrivals → Scanning → Excluded folders → Choose folders…
A searchable checkbox list of every folder in the vault. Tick one and that folder and everything under it is left out of every list — no glob syntax required.
The matcher is anchored (^Folder/), so excluding Archive does not affect MyArchive/.
A free-text Additional exclusion rules box remains for wildcard cases such as Archive/20??/*.
Settings → Latest Arrivals → Configuration backup
| Button | Effect |
|---|---|
| Export | Writes the current settings to latest-arrivals-settings.json at the vault root |
| Import | Pick a JSON file from the vault and apply it (validated first; unknown keys are ignored) |
Writing the file into the vault is deliberate: it syncs to your other devices, so you can export on your computer and import on your phone, or vice versa. Import only overwrites keys that appear in the file.
Settings live in the plugin folder's data.json, which is Obsidian's convention — but that file sits inside
the plugin directory, so manually deleting and re-copying that folder takes your settings with it.
The plugin therefore also mirrors settings into device-local storage (the same place as the ledger, outside
the plugin folder). If data.json goes missing, the settings are restored from the backup on startup, with a
notice telling you so.
The plugin is English by default and follows your Obsidian interface language automatically. You can also pin it under Settings → Latest Arrivals → Interface language.
| Language | Status |
|---|---|
| English | Default, and the fallback for any missing key |
| 简体中文 | ✅ |
| 繁體中文 | ✅ |
Command names, the ribbon tooltip, and the view title are registered when the plugin loads, and Obsidian has no API to rename them. Changing the language therefore reloads the plugin; if that fails, you'll be asked to toggle it off and on manually.
Adding a language means one new file in src/i18n/ plus one line in LOCALES. npm test asserts that every
bundle has an identical key set and identical {placeholder} usage.
The
nameanddescriptioninmanifest.jsonare read directly by Obsidian and cannot vary by language, so the plugin list always shows the English name.
Settings → Community plugins → Browse, and search for Latest Arrivals.
| Result | What to do |
|---|---|
| You can find it | Install it from there. This is the simplest way, and Obsidian will keep it updated for you. |
| You cannot find it | It is still in review. Use one of the two methods below. |
The review only needs to pass once. After that the directory entry stays, and new releases reach users through Obsidian's own updater — no BRAT, no manual copying.
BRAT installs a plugin straight from a GitHub repository and updates it for you.
obsidian42-brat).iwannaknow2000/obsidian-latest-arrivalsTo update later, run BRAT: Check for updates to all beta plugins.
If BRAT says "This does not seem to be an obsidian plugin", the download was blocked — usually GitHub rate limiting. Turn off any VPN and try again, or use option B.
This needs no network access at all.
main.js, manifest.json, and styles.css from the
latest release.<your vault>/.obsidian/plugins/latest-arrivals/.Overwrite the three files; do not delete the folder —
data.jsonlives there. (And if you do delete it, the settings backup described above restores your configuration.)
| Installed via | How to update |
|---|---|
| Official directory | Settings → Community plugins → Check for updates — works once listed |
| BRAT | Settings → BRAT → Check for updates to all beta plugins |
| Manual copy | Re-copy the three files and restart Obsidian |
Until the directory entry is approved, the built-in "Check for updates" button cannot see this plugin — it only compares against the official directory. That is by design, not a fault.
npm install
npm run dev # watch build (writes main.js with an inline sourcemap)
npm test # 54 logic assertions + 29 runtime smoke assertions
npm run lint # official rule set, with type-checked rules
# (the directory's source-code review runs the same set)
npm run build # type-check + production build
npm run release:dry # show what a release would do, without changing anything
npm run release # bump version, commit, tag, push → CI builds and publishes
src/
├── main.ts Plugin entry: commands, ribbon icon, sidebar tab, settings, foreground rescan
├── ledger.ts Arrival ledger (device-local persistence, reconciliation, rename inheritance)
├── scanner.ts Fast path (vault index) + deep path (recursive adapter traversal)
├── service.ts Data core: scan → reconcile → sort → expose to the UI
├── sort.ts Five sort keys + exclusion matchers
├── settings-io.ts Configuration export and import
├── pinyin.ts Syllable-boundary binary search + polyphone table + capability self-test
├── format.ts Byte and time formatting
├── settings.ts Settings tab + diagnostics panel
├── types.ts Shared types and defaults
├── i18n/ en / zh-CN / zh-TW bundles
└── ui/ Modal, sidebar view, row renderer, folder picker
test/
├── run.ts Logic assertions (bundled by esbuild, run by Node)
├── smoke.cjs Runtime smoke test (stubbed Obsidian, loads the real build output)
└── stub/ Minimal Obsidian API stub
Logic (test/run.ts, 54 assertions): pinyin initials and polyphones; key-set and placeholder parity across
all three language bundles; ascending and descending order for all five sort keys; exclusion-folder anchoring,
escaping and false-positive checks; ledger baseline, idempotent rescans, frozen firstSeen, incremental
discovery, rename inheritance, delete-and-restore, arrival-window clamping, persistence round-trip.
Runtime smoke (test/smoke.cjs, 29 assertions): loads the actual build output with a stubbed Obsidian module
and runs onload() end to end — command, view and ribbon registration, automatic sidebar mounting (including
position and "does not steal focus"), sidebar relocation and deduplication, all five sort keys, the ledger
landing in device-local storage, incremental discovery, and ignoring.
adapter.list is one bridge call per directory. On a vault of a few thousand notes this
takes 1–2 seconds, so it is throttled to once every 180 s by default. It can be tuned or disabled.data.json is synced between devices (settings), while the arrival ledger is not (it lives in
device-local storage), so each device keeps its own arrival times.getSettingDefinitions(), the declarative settings API introduced in Obsidian 1.13.0. This plugin's
minAppVersion is 1.8.7, and using a 1.13-only API would lock out every user on an older build, so it
deliberately keeps display(). The trade-off is recorded in eslint.config.mjs.