Note: All AI contributions will be carefully reviewed by the project maintainers before being merged.
- This file documents discoverable project behavior for coding agents.
- AI-instruction scan performed with glob
**/{.github/copilot-instructions.md,AGENT.md,AGENTS.md,CLAUDE.md,.cursorrules,.windsurfrules,.clinerules,.cursor/rules/**,.windsurf/rules/**,.clinerules/**,README.md}. - Result: only
README.mdmatched (no existing agent-specific rules files were found).
- floccus is a cross-platform bookmarks sync engine with two runtimes: browser extension and Capacitor mobile app.
- Entrypoints are minimal:
src/entries/background-script.js(browser controller),src/entries/options.js(web UI),src/entries/native.js(native UI),src/entries/test.js(in-extension tests). - Runtime abstraction is via
src/lib/Controller.ts: browser UI talks to service worker/runtime messages; native uses direct controller implementation. - Sync orchestration is centered in
src/lib/Account.ts:- creates adapter + local tree + storage
- runs strategy (
default/merge/unidirectional) - persists cache, mappings, and continuation state
- applies failsafes and error normalization
- Core sync algorithm lives in
src/lib/strategies/Default.ts(multi-stage diff/reconcile/execute pipeline with resumable continuation JSON).
- Flow: UI action/event ->
BrowserController/NativeController->Account.sync()-> strategy -> local tree + server adapter. - Storage is per-account and platform-specific:
- browser:
src/lib/browser/BrowserAccountStorage.js(browser.storage.local) - native:
src/lib/native/NativeAccountStorage.js(@capacitor/preferences)
- browser:
- Critical persisted keys per account:
bookmarks[<id>].cache,bookmarks[<id>].mappings,bookmarks[<id>].continuation. - Adapter implementations are server boundary points under
src/lib/adapters/(Nextcloud, WebDAV, Git, Dropbox, Google Drive, Linkwarden, Karakeep, Fake).
- Install/build:
npm install,npm run build. - Dev watch loop:
npm run watch(also syncs Capacitor assets; seegulpfile.js). - Release artifacts:
npm run build-release-> zip/xpi/crx inbuilds/. - Static checks:
npm run lint,npm run typecheck. - Selenium integration tests:
npm test(expects Selenium server + env vars; runner intest/selenium-runner.js). - Node.js test harness:
npm run build:test-nodebundlessrc/entries/test-node.jstodist/node-tests/fake-tests.jsviawebpack.node-tests.js. - Node.js test execution:
npm run test:node:fakeruns the bundled Mocha suite without a browser/WebDriver. Defaults areFLOCCUS_TEST_ACCOUNTS=fake,fake-noCache,FLOCCUS_TEST_BROWSER=node, andCI=true; useful knobs includeFLOCCUS_TEST(grep),FLOCCUS_TEST_INVERT=true,FLOCCUS_TEST_ACCOUNTS=...,FLOCCUS_TEST_SEED=..., andFLOCCUS_NODE_INCLUDE_BENCHMARK=true(npm run test:node:fake:benchmark). - Appium/native Android harness:
npm run test:appiumrunstest/appium-runner.js, which waits for an Appium server, creates an AndroidUiAutomator2session, switches into the app'sWEBVIEW, opens the native#/testroute, and streams Mocha logs until aFINISHEDmarker is emitted. - Appium prerequisites: the Android app/APK must already be built and installed, and an Appium server with the
uiautomator2driver must be running. Common env vars areAPPIUM_SERVER,APPIUM_DEVICE_NAME, eitherAPPIUM_APPor (APPIUM_APP_PACKAGE+APPIUM_APP_ACTIVITY), plus the same test-selection env used by the browser harness (FLOCCUS_TEST,FLOCCUS_TEST_SEED,APP_VERSION,TEST_HOST, adapter-specific credentials/tokens such as Google/Dropbox/Linkwarden/Karakeep). - Browser-local test mode is destructive to bookmarks unless using a dedicated profile (see
README.mdtest section).
- Mixed JS/TS/Vue2 codebase (
allowJs: trueintsconfig.json); keep edits consistent with surrounding file language. - Lint style is strict and legacy-standard-like: single quotes, no semicolons, 2-space indent (
.eslintrc.json). - Adapters are registered centrally in
src/lib/Account.tsviaAdapterFactory.register(...)(dynamic imports). - Sync reliability relies on continuation persistence and mapping GC; avoid "simplifying" this flow without preserving resume semantics.
IS_BROWSERcompile-time flag (webpack define) is the platform switch; do not branch on ad-hoc runtime checks when an existingIS_BROWSERpath exists.
- Browser manifests differ (
manifest.firefox.jsonis MV2 background page;manifest.json/manifest.chrome.jsonare MV3 service worker). gulpfile.jscontains a guard to preventbrowser-apileakage into native chunk (webpackCheck).- Nextcloud adapter (
src/lib/adapters/NextcloudBookmarks.ts) is the most feature-rich reference for locking, sparse tree loading, ordering, and request handling. - If adding/changing adapters, implement
interfaces/Resource.tscapabilities (getCapabilities,isAtomic, optionalorderFolder/bulkImportFolder/loadFolderChildren) and verify strategy interactions. - i18n strings live in
_locales/en/messages.json; UI text should use i18n helpers rather than hardcoded strings.
Scanner(src/lib/Scanner.ts) diffscacheTreeRoot(always local-located) against a live tree; itsmergeablecallback returns true if items areMappings.mappable(known identity) ORcanMergeWith(weak: bookmarks by URL, folders by title). Themappablecheck is already tried first per pair.canMergeWithmatches are self-healing for mappings: every match path callsScanner.addMapping, which evicts the stale entry and re-points it at the matched item. So a wrong/weak pairing can't strand a mapping on a deleted id — don't assume a stale mapping originates here.reconcileDiffsinDefault.tsbuilds the per-target plan; it must never plan anUPDATE/MOVEagainst an item that's absent from the freshly-fetched target tree (executes as E002UnknownBookmarkUpdateError/ E004UnknownMoveTargetError). Concurrent-removal detection viaREMOVEactions +Diff.findChainis best-effort; a target-tree existence check (targetTree.findItem(type, mapId(...))) is the robust guard.
- The
fake-noCachebenchmark interrupt test simulates nextcloud-bookmarks: both accounts share one serverbookmarksCacheandisAtomic() === false;setInterrupt()aborts syncs mid-flight (recoverable errors are E026/E027 only — seesyncAccountWithInterruptsinsrc/test/utils.js). - Logs are noisy and misleading: the fuzzers (
randomTreeManipulationWithDeletion) wrap their ownNativeTreemutations in try/catch andconsole.logthe errors, so mostE001/E002/E004lines (stack viaNativeTree.updateBookmark) are expected noise. The real failure is the lineSyncing failed with ...(stack throughFakeAdapter+SyncProcess). - CI job logs interleave real-time stdout with a buffered
Loggerdump at the end, andutil.inspecttruncates trees/actions ([Bookmark],[Array]) — scan-result/plan contents are not fully recoverable from logs; trace by item id and theMapping <server|local> planmarkers instead.