From 9e3605e3464f1cecfc7ef577490b768748e68373 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Wed, 27 May 2026 22:21:10 -0700 Subject: [PATCH 01/64] test: encrypt unit tests with git-crypt --- .gitattributes | 1 + __tests__/ObservableMap.test.ts | Bin 13776 -> 13798 bytes __tests__/SharedFolder.test.ts | Bin 483 -> 505 bytes __tests__/TestMinimark.ts | Bin 3175 -> 3197 bytes __tests__/TestSettingsStorage.ts | Bin 16904 -> 16926 bytes __tests__/TestSyncStore.ts | Bin 25352 -> 25374 bytes __tests__/TestTimeProvider.ts | Bin 2848 -> 2870 bytes __tests__/TestTokenStore.ts | Bin 2944 -> 2966 bytes __tests__/mocks/MockTimeProvider.ts | Bin 3066 -> 3088 bytes 9 files changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..5573a5dd --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +__tests__/** filter=git-crypt diff=git-crypt diff --git a/__tests__/ObservableMap.test.ts b/__tests__/ObservableMap.test.ts index 8174debf381c4789b0ca982e5de046b828e2f8fe..38fc8feda66af3c7937bca1a82813288e905b9a5 100644 GIT binary patch literal 13798 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 literal 13776 zcmd5@NpIUo7Cy_rqDGgY(~i7k0t}qkgCyOJ&Y{scxG@LA2q>y#aZIr|EK;^&? zo0nqA#m=b~g4$N2n`Rg1V{{Dvnrp0Fg&vT2-LNtP~`e3r%)&(^dW zX5f{$mTVd4b)2zzR?7tznBeXHPLjnEn_x|!e=JgDe97~wMZoROs3_AFR%ZpzXISQw z-T0V&D9d>L{+6Y)W0u#;DKFnSx{g`2%T7+&N~AOP{gj=s^NV-QVmZI5KF{#<5IBr3 zxuPc8wd*kKJ1TiqmpS_xn@@6a9qnTNcZ`;Lw5Y4dqTuhg$G$G5T_{{J5xJ~DF7~Y! zshnske)g+#Hvx=8dR46cu326Vv38>fWdx(6_F>V!S(borJVo+}5K|m{(nuUj$qMy#XqSl`ge!@H8(@`c;&)3;d$8c^hbyVd9^iQ9OqNwzk`}N;8u*@;9Cp2wPAXrEX3yn-#2t zAvLX*Q0Xcaxt{Q|7C&Bs**2#WqrwoMF<}GoG?r>)i$t7t42qRwVe zr3$7xV{qXN?x*JANTWwftfg@c#P^Z|N_^yznbjUJbpvP#EZZKC*a3gSLSUSHX7o_+=rL~mW+KWG zG$0O)IyfMJuIZb9&dKvF}NfF4{}|?Pqh$hO&^UY zGGdH}5`12A#o)ZI_!>?=mGGt4X|-UB7%I6vU;(+7YY!9J##4)`tT|{5QBY$V&6vlT z)PuV0z?~wX{7^;LB;1dw#vU4`e3kbE*n^$jDHq6-AUOmFYaLOuUKb=4WKDA{qgkE>@7u zAKHATo33<*9+H!X2oO( z+A_WUPuTxZk4)ydpG#YYqGq7cJ!E&EZU4xY5^E%91v(L}s+YDinU%K;?DDsG#XoYM ze*zSIc7|F?w({>yU7|@17GnYMqc`!p8Ns)XVTPWeR%RqqvQVJ^(O}?gv8$96FjeR5SM* zvr5>v$LdFDoU1jS(@y) z(a#(wNPPA?VtfFepGB3N%A1(!IZ)$OiY6>vs_sq~!NjLYpTN33z+QdP7AZk44IUdlQlz|#$i}BxDh>7^%2UEq2kjt z1SAeqSR+f7u&8H_t0(t8&B{Z7696INhY$|QwFmOf*rVaGJ;)xKcC5uwqx)UU(o<%G z*DRgSd5JDS8?@#{!=LrqPtZMY=_=uIhq5{{2#=nzM>^%aAI_%p9u6J?njq{)T6=GF z^6x^sE@tF9JjJ|T@SGK~f<9Tq`Q?kANra4t3KrC^p0{Y+G{%SZn`M8<13;&n+5Ks; zI!dwL4C6Pa#m$D>`H}n7ibHuKk1u(N3MstYUh6vs^DOteNHn`sRc<~)uN=>w$4 z%G_M9xz@x1D?iJGI&?N3aLDta0f3|KI7zsal&X40X?wEIE2|sG{m^vxU2vy5?5O?l z8y>z5!mwp@NLsk7LW>ySKcMY{q-EwEk+MgK0HSVn=ZS^!+H~hTLfFt$*1Wr}vgX=v zD|Z%C=NUKdOsCUDvxrR8z;A9~bNJI)QXRvt-ADs_H%3z7l-Gr?AiTS43G3t;F#7cCI}e4uPM*;iu8xI3A*c`?wCNddvqm0U0jRRg$li`ZzB!T zmZYIH0b5{m(|mM;`~26Y(;-8_bgdjLPUWsSj2Qo`aPoxeh0~hl>q9Jfc+hq`>eUfm z9d%v-ZeFTsZ`8XkJP@9j*+$gIHzzCje}Wr8WhGV9Z%o_32>W2``tdfflde;89I2IG z9}ONJBHfy0obhE*A$;o`3^Z>K?mg2g>!Dk^;xlbT(jH-R3NIKA6-0(fqfhEy=2CTT zCADejuI2Xpy69!Q{V)Lc%$BQns)%=zZu6s7HxJ1*JbhCoUWL>D6>jH;& zy1k;VWt)^=w}9!ux^o}zQQ45zYfDr|tI64b5c?i{k<^?E^4!v9WpxYO`;G+=6TlZAA3L%n@; z>)yeNyZ)O_lseX>+wx>2xJ%s%y`FLD+SHCx_f|>ALSuXsbS<0ES66k2*1!9)$3$N> zr*R(o;)PAb=w-iaZ_4vtM{~(@#Mp#Bec6D7YZ0(S&}SbvIssN6%(UQ0>8pZyjO*-w z;-eUSL3@;=5qzw^x5+P6v>KsZW4qz%0bM7yRTP+k#?Y1O{;R*VaOdJL4#!JMpl_Mp zZ2Td^7d3dGc6xJQzQJhR5lx`9+D-T`nZGysPEc@s}eK%&JB?lEUl*ghxWBS7V1v)UBH${KwgoI0f>;SGGTqf7~6+W=ZJ7U1KY%KRL-R^0?#`P~e9xqTn3F4wb784Bn YpzgiB)jg(l0&elZuxS=|>P)}<1)-O4$p8QV diff --git a/__tests__/SharedFolder.test.ts b/__tests__/SharedFolder.test.ts index 6d71ba62adf8ad4012ff55b37eaa8fd126e50626..0735d759e7bf6a233cede6615529c7cc7bc5122a 100644 GIT binary patch literal 505 zcmV=yzIT9^9!5t@b1q|SS6#XT=rSS83>)2K27Kc}enit%JFbrG zKY%^9R_Emvd;0kI_JOpNi+!yhuErefiHFiv6+CQ3cPw9HT&`lSvo55gnGZ@{Tum@A zKCSokW3veo0!1z$0nA0s`bpGzfynJiS6pM!(TdUd(jJ%TRHa3q?R^i%yT-}ORiW`A zBU{kdl>@_dk36VO$c@!Qa)LP9aHhS}{Z8x?4SWm}MXvQ*>WGN>w0G}FO>h>RMrs7B zKUUKJ{Q#3mn<4oc7tIngzo|a%i8?OMk(^6LEcS$V@Xtvt&tj_;@zIKlySpaEg$e?W z@1A=pK65M3ry8Eleg{>siikW*nQIX7G#w~KX{#Ikkn*(=5I(^*GcwBUc5H$Ns#BJM zsAfOpY=Rb}543kCP{r(g|A!3{r!_FXa35R5Z0J9-E6OrM$(sZg5B1#B&_YPzHxm-b v6^P7MkWOdNnxGP2i|lH>2O)-O?NP8Cog=KOIU*#8Ft0bv9-#V(1~Gh!J`(nq literal 483 zcma)&L2JV>4293muMj>tP?AyDscbOl4r2^L$4;XM$5t1!#P%c?7E1s7l5}l%*jNuX zJoNNENv?~566^p!qxN=*>;;`LZ4t0T8qTsy4X6jQ1?JnKA6U}cJTZJh#>_{Qkf9q zj!5^#INl`3(yu$H*U>G6EUQ*~@BCXiznWjeGny^A)Wa%h+v!+zR?d#rCrsoFmnYL> z^}w*y+|)snJ$u*r)RPxHTvAOUFH*Sw*y{F>EX!}$fqs(zvd;q_J1<*coi8%6AHMN@ E0bt3K4FCWD diff --git a/__tests__/TestMinimark.ts b/__tests__/TestMinimark.ts index a09b48f5ec320ff5ed0a522946eafa99936e96b4..7520fd1c2afe3ac91e29253cf13f4baed6eef77d 100644 GIT binary patch literal 3197 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 diff --git a/__tests__/TestSettingsStorage.ts b/__tests__/TestSettingsStorage.ts index 0aa4765150ba2a535c726b9e0c6218b5f89af6b8..a239de04d805af6587d9db5f0d2ba4b438258e60 100644 GIT binary patch literal 16926 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 literal 16904 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 NT_vhy+dfQV>2e*nAaMWy literal 25352 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 diff --git a/__tests__/TestTimeProvider.ts b/__tests__/TestTimeProvider.ts index f506e280b0895d8fc5e1009af73c87f32e43cda4..f81f530bb74734f7ce4b8451575340289097348b 100644 GIT binary patch literal 2870 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 diff --git a/__tests__/TestTokenStore.ts b/__tests__/TestTokenStore.ts index d9dc455382a5f402838d1ad4e19e6199d62b6ab6..5e1139b6e967c83de8296092207e2952b8a3771f 100644 GIT binary patch literal 2966 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 Mhb8Za6Mrd^$?s6J!~g&Q literal 2944 zcmcImQE%He5Pp__#X;Bu-5GN1p?z?cV%<;-9a>;m{5A|JEzvd?ixfyYsS)_U@9s!h zvTJu;wgs>viO17-ci-JdT{qSRc!IgTSLQZYr(h3DXKTodV(eWx?pE^afo|B zWvO6}D+~yp23a7?Gw{J_v$`Ja2LtJMriA6zltEhqcjUIiFNU;hJce5pw#^_bt--Id zO+S(wFzOLb@r&Vg++jDW*w~dg-Pf)Mpai_PE4TqsEAQn>wIGLZ^BU;0t&@*vH-m4U zlHLB}v6tV}(kZ_tPlGJ0Y`LwK3Gb@b-w5B9aHZ$2dgVI<$KIP zb_%`lDo`=)HV8}-VkE1BXTeWxx<3oNSjFAA^IQ1t3SPh~x=gOFu7*Xhw_F}EB&^w< z;>-3UTBX}P{$d%pRzIuo%VT)^snKpnL8l7O+cF-oIjo>F&`9UiI}@TIehdkb+_rxC zT_koaEN6&ce1Gu!n$pWrVr6G-0U5E2C5SImJ$NBS-8KNGpV6J>wzWOPHH)A<~^G#KOx3vO}-H+mfUBcs9muryN zR(Ct!Kmng6`D&x`8iQ6rxX?PHlejs1sTrXZqg1Zees1$Wa)5{)Ej$54UGiWx|Fdn#pt)DQcG%$uc~FR$8R#|KF|2hr~@O@yjT diff --git a/__tests__/mocks/MockTimeProvider.ts b/__tests__/mocks/MockTimeProvider.ts index 2cd2d6f481dfe2df4eb100a7fe8e1c0fe483b793..18a8f47ecfedc56cdc4953f609d9e18d4b14bf46 100644 GIT binary patch literal 3088 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 literal 3066 zcmcguO>g5y3_aVwf*UMQ8L=hrVK0eov_N`rffkF_Jp}<)mMD7~Y2?m~k{Dk7_mZO- zdF(jt7X3IFuq5)$Bl$>wN1FS@apH%%Ma#YYlq3Q~*w91n3!`1C@h%eSH zD^soE$2xlq4M0@85-XHOpCTTHgPjsjsX@pZrO1HYZa_BG8rALbtD)CQ-@tcDrTZDV z4MZN84YIT>*J<{61JMZP3wWwUPVNJv#Abu)47FXr9PS?ie2FjiXtm7g)06@lWVO^r zHJPauVe*k?D|8(2B+EOZ9?SYU8WC7aUK_MH(a5v2o(fH)B%A{{AMJc3K8HjJg2S)? zmJH;gDCp408N%ch;s_G>Fr88W0~&8T#4m^u!#AkDKwSD+R+a3dZrqZ&>Uvnfs1I?$VU>a zTsXgD#k-97gm+JIAxbJ}6xm1HTJ~Q|pzr2$%9|&3fqB@BVd=*pw5LzU6=|vQv|l>8 zf1M1xW_l6W@&*lkeN9CQt?10Jt2q0;5=)l}{v-;Bj*)aTg7gK;0)+WSLn=YIb{NAm z(iE&P7c?=-J?LzUc~ep^ZV{L);0tDr5w#>_$5zN3U(O0rqTHA*wf+aD5YoH1<`1zK z4moyBaA3oJMB0A!o)@admGXK6isonRxxVJvGKrRcWQe+?a3{IY>AJ)T8&BvB!neFk zbf(14Ogf`P))U$orO1Stq;gLHuB~SDqcfcTTzY-%3MPf$8oAQG)&0+ z{-GcFi-?d4GQNS;nEvKirhDHRl#h?oDZM7G9RWV2Dy@(-u4k)74`ds&MB0O$ta^+u z&`?nd3XAs3k@lbA+4Qqk9F}dI9>vMV4PEZ+6&=YF%$eH;wTYl=pNc%FURY^PfG6a< z$6!-opSWgSUTMG8`*68gd>t3H`aaFJVb|N4bHa>9z{uhS9YQI#Yn9=X`Lp`MXb`&vU(AQu?u7&#!w6x{I)O<6trQ_T`P`54^Hg{_=qNl|9yVZ$F;> j9RA0Xv)>jr^Zop!{-mDpgZ{J^yS3d-ZhNn3hwbnWa7x@> From bd4c1173bfbb2adee66ecaa899627cb73175db0f Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 26 May 2026 16:56:14 -0700 Subject: [PATCH 02/64] build: ignore stdin for sync build metadata commands --- esbuild.config.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 5fa392cc..03b8f03f 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -14,9 +14,12 @@ if you want to view the source, please visit the github repository of this plugi */ `; -const gitTag = execSync("git describe --tags --always", { +const runBuildMetadataCommand = (command) => execSync(command, { encoding: "utf8", -}).trim(); + stdio: ["ignore", "pipe", "pipe"], +}); + +const gitTag = runBuildMetadataCommand("git describe --tags --always").trim(); const develop = process.argv[2] === "develop"; const staging = process.argv[2] === "staging"; From 211634c92f98e5a197ceb0fb45d6d013b0335893 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 26 May 2026 17:13:31 -0700 Subject: [PATCH 03/64] chore: prune stale y-indexeddb package lock entry --- package-lock.json | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index a694c217..97fa442c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,6 @@ "svelte-step-wizard": "^0.0.2", "tslib": "2.4.0", "uuid": "^9.0.1", - "y-indexeddb": "^9.0.9", "y-leveldb": "^0.1.2", "y-protocols": "^1.0.5", "y-websocket": "^1.5.3" @@ -6215,25 +6214,6 @@ "node": ">=0.4" } }, - "node_modules/y-indexeddb": { - "version": "9.0.12", - "resolved": "https://registry.npmjs.org/y-indexeddb/-/y-indexeddb-9.0.12.tgz", - "integrity": "sha512-9oCFRSPPzBK7/w5vOkJBaVCQZKHXB/v6SIT+WYhnJxlEC61juqG0hBrAf+y3gmSMLFLwICNH9nQ53uscuse6Hg==", - "dependencies": { - "lib0": "^0.2.74" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - }, - "peerDependencies": { - "yjs": "^13.0.0" - } - }, "node_modules/y-leveldb": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/y-leveldb/-/y-leveldb-0.1.2.tgz", @@ -10735,14 +10715,6 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, - "y-indexeddb": { - "version": "9.0.12", - "resolved": "https://registry.npmjs.org/y-indexeddb/-/y-indexeddb-9.0.12.tgz", - "integrity": "sha512-9oCFRSPPzBK7/w5vOkJBaVCQZKHXB/v6SIT+WYhnJxlEC61juqG0hBrAf+y3gmSMLFLwICNH9nQ53uscuse6Hg==", - "requires": { - "lib0": "^0.2.74" - } - }, "y-leveldb": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/y-leveldb/-/y-leveldb-0.1.2.tgz", From d9a38b96aaeaebf50c61d3e3c99808e9c3ae01b6 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 26 May 2026 17:16:52 -0700 Subject: [PATCH 04/64] feat: track IndexedDB persistence metrics --- src/debug.ts | 79 +++++++++++++++++ src/main.ts | 2 + src/storage/y-indexeddb.js | 20 ++++- src/types/obsidian-metrics.d.ts | 152 ++++++++++++++++++++++++++++++++ 4 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 src/types/obsidian-metrics.d.ts diff --git a/src/debug.ts b/src/debug.ts index 494f1aa2..b5ee2518 100644 --- a/src/debug.ts +++ b/src/debug.ts @@ -277,3 +277,82 @@ const debug = BUILD_TYPE === "debug"; export function createToast(notifier: INotifier) { return createToastFunction(notifier, debug); } + +// ============================================================================ +// Metrics Integration (for obsidian-metrics plugin) +// ============================================================================ + +import type { + IObsidianMetricsAPI, + MetricInstance, + ObsidianMetricsPlugin, +} from "./types/obsidian-metrics"; + +/** + * Metrics for Relay - uses obsidian-metrics plugin if available, no-ops otherwise. + * + * Uses event-based initialization to handle plugin load order. The obsidian-metrics + * plugin emits 'obsidian-metrics:ready' when loaded, and metric creation is idempotent. + */ +class RelayMetrics { + private dbSize: MetricInstance | null = null; + private compactions: MetricInstance | null = null; + private compactionDuration: MetricInstance | null = null; + + /** + * Initialize metrics from the API. Called when obsidian-metrics becomes available. + * Safe to call multiple times - metric creation is idempotent. + */ + initializeFromAPI(api: IObsidianMetricsAPI): void { + this.dbSize = api.createGauge({ + name: "relay_db_size", + help: "Number of updates stored in IndexedDB per document", + labelNames: ["document"], + }); + this.compactions = api.createCounter({ + name: "relay_compactions_total", + help: "Total compaction operations", + labelNames: ["document"], + }); + this.compactionDuration = api.createHistogram({ + name: "relay_compaction_duration_seconds", + help: "Compaction duration in seconds", + labelNames: ["document"], + buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10], + }); + } + + setDbSize(document: string, count: number): void { + this.dbSize?.labels({ document }).set(count); + } + + recordCompaction(document: string, durationSeconds: number): void { + this.compactions?.labels({ document }).inc(); + this.compactionDuration?.labels({ document }).observe(durationSeconds); + } +} + +/** + * Initialize metrics integration with Obsidian app. + * Sets up event listener for obsidian-metrics:ready and checks if already available. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function initializeMetrics(app: any, registerEvent: (eventRef: any) => void): void { + // Listen for metrics API becoming available (or re-initializing after reload) + registerEvent( + app.workspace.on("obsidian-metrics:ready", (api: IObsidianMetricsAPI) => { + metrics.initializeFromAPI(api); + }) + ); + + // Also try to get it immediately in case metrics plugin loaded first + const metricsPlugin = app.plugins?.plugins?.["obsidian-metrics"] as + | ObsidianMetricsPlugin + | undefined; + if (metricsPlugin?.api) { + metrics.initializeFromAPI(metricsPlugin.api); + } +} + +/** Singleton metrics instance */ +export const metrics = new RelayMetrics(); diff --git a/src/main.ts b/src/main.ts index ceba17bb..d0f6b559 100644 --- a/src/main.ts +++ b/src/main.ts @@ -30,6 +30,7 @@ import { RelayInstances, initializeLogger, flushLogs, + initializeMetrics, } from "./debug"; import { getPatcher, Patcher } from "./Patcher"; import { LiveTokenStore } from "./LiveTokenStore"; @@ -330,6 +331,7 @@ export default class Live extends Plugin { disableConsole: false, // Disable console logging }, ); + initializeMetrics(this.app, (ref) => this.registerEvent(ref)); this.notifier = new ObsidianNotifier(); this.debug = curryLog("[System 3][Relay]", "debug"); diff --git a/src/storage/y-indexeddb.js b/src/storage/y-indexeddb.js index 3ed601c1..5ca91c4a 100644 --- a/src/storage/y-indexeddb.js +++ b/src/storage/y-indexeddb.js @@ -2,6 +2,7 @@ import * as Y from 'yjs' import * as idb from 'lib0/indexeddb' import * as promise from 'lib0/promise' import { Observable } from 'lib0/observable' +import { metrics } from '../debug' const customStoreName = 'custom' const updatesStoreName = 'updates' @@ -27,7 +28,10 @@ export const fetchUpdates = (idbPersistence, beforeApplyUpdatesCallback = () => } }) .then(() => idb.getLastKey(updatesStore).then(lastKey => { idbPersistence._dbref = lastKey + 1 })) - .then(() => idb.count(updatesStore).then(cnt => { idbPersistence._dbsize = cnt })) + .then(() => idb.count(updatesStore).then(cnt => { + idbPersistence._dbsize = cnt + metrics.setDbSize(idbPersistence.name, cnt) + })) .then(() => { if (!idbPersistence._destroyed) { afterApplyUpdatesCallback(updatesStore) @@ -44,9 +48,17 @@ export const storeState = (idbPersistence, forceStore = true) => fetchUpdates(idbPersistence) .then(updatesStore => { if (forceStore || idbPersistence._dbsize >= RUNTIME_TRIM_SIZE) { + const startTime = performance.now() idb.addAutoKey(updatesStore, Y.encodeStateAsUpdate(idbPersistence.doc)) .then(() => idb.del(updatesStore, idb.createIDBKeyRangeUpperBound(idbPersistence._dbref, true))) - .then(() => idb.count(updatesStore).then(cnt => { idbPersistence._dbsize = cnt })) + .then(() => idb.count(updatesStore).then(cnt => { + idbPersistence._dbsize = cnt + metrics.setDbSize(idbPersistence.name, cnt) + })) + .then(() => { + const durationSeconds = (performance.now() - startTime) / 1000 + metrics.recordCompaction(idbPersistence.name, durationSeconds) + }) } }) @@ -117,8 +129,10 @@ export class IndexeddbPersistence extends Observable { if (this.db && origin !== this) { const [updatesStore] = idb.transact(/** @type {IDBDatabase} */ (this.db), [updatesStoreName]) idb.addAutoKey(updatesStore, update) + ++this._dbsize + metrics.setDbSize(this.name, this._dbsize) const trimSize = this.synced ? RUNTIME_TRIM_SIZE : STARTUP_TRIM_SIZE - if (++this._dbsize >= trimSize) { + if (this._dbsize >= trimSize) { // debounce store call if (this._storeTimeoutId !== null) { clearTimeout(this._storeTimeoutId) diff --git a/src/types/obsidian-metrics.d.ts b/src/types/obsidian-metrics.d.ts new file mode 100644 index 00000000..dfcfcfc9 --- /dev/null +++ b/src/types/obsidian-metrics.d.ts @@ -0,0 +1,152 @@ +/** + * Type declarations for the Obsidian Metrics API + * + * Copy this file into your plugin to get type-safe access to the metrics API. + * + * ## Accessing the API + * + * Access via the plugin instance: + * ```typescript + * const metricsPlugin = this.app.plugins.plugins['obsidian-metrics'] as ObsidianMetricsPlugin | undefined; + * const api = metricsPlugin?.api; + * ``` + * + * ## Handling Plugin Load Order + * + * The metrics plugin emits 'obsidian-metrics:ready' when loaded. Listen for this + * event to handle cases where your plugin loads before obsidian-metrics: + * + * ```typescript + * class MyPlugin extends Plugin { + * private metricsApi: IObsidianMetricsAPI | undefined; + * private documentGauge: MetricInstance | undefined; + * + * async onload() { + * // Listen for metrics API becoming available (or re-initializing after reload) + * this.registerEvent( + * this.app.workspace.on('obsidian-metrics:ready', (api: IObsidianMetricsAPI) => { + * this.initializeMetrics(api); + * }) + * ); + * + * // Also try to get it immediately in case metrics plugin loaded first + * const metricsPlugin = this.app.plugins.plugins['obsidian-metrics'] as ObsidianMetricsPlugin | undefined; + * if (metricsPlugin?.api) { + * this.initializeMetrics(metricsPlugin.api); + * } + * } + * + * private initializeMetrics(api: IObsidianMetricsAPI) { + * this.metricsApi = api; + * // Metric creation is idempotent - safe to call multiple times + * this.documentGauge = api.createGauge({ + * name: 'my_document_size_bytes', + * help: 'Size of documents in bytes', + * labelNames: ['document'] + * }); + * } + * + * updateDocumentSize(doc: string, bytes: number) { + * this.documentGauge?.labels({ document: doc }).set(bytes); + * } + * } + * ``` + * + * ## Key Points + * + * - **Do NOT cache the API or metrics long-term** - they become stale if obsidian-metrics reloads + * - Listen for 'obsidian-metrics:ready' and re-initialize your metrics each time it fires + * - Metric creation is idempotent: calling createGauge() with the same name returns the existing metric + * - It's safe to store metric references within an initialization cycle, but always re-create them + * when 'obsidian-metrics:ready' fires + */ + +export interface MetricLabels { + [key: string]: string; +} + +export interface CounterOptions { + name: string; + help: string; + labelNames?: string[]; +} + +export interface GaugeOptions { + name: string; + help: string; + labelNames?: string[]; +} + +export interface HistogramOptions { + name: string; + help: string; + labelNames?: string[]; + buckets?: number[]; +} + +export interface SummaryOptions { + name: string; + help: string; + labelNames?: string[]; + percentiles?: number[]; + maxAgeSeconds?: number; + ageBuckets?: number; +} + +export interface LabeledMetricInstance { + inc(value?: number): void; + dec(value?: number): void; + set(value: number): void; + observe(value: number): void; + startTimer(): () => void; +} + +export interface MetricInstance { + inc(value?: number, labels?: MetricLabels): void; + dec(value?: number, labels?: MetricLabels): void; + set(value: number, labels?: MetricLabels): void; + observe(value: number, labels?: MetricLabels): void; + startTimer(labels?: MetricLabels): () => void; + labels(labels: MetricLabels): LabeledMetricInstance; +} + +export interface IObsidianMetricsAPI { + // Metric retrieval + getMetric(name: string): MetricInstance | undefined; + getAllMetrics(): Promise; + clearMetric(name: string): boolean; + clearAllMetrics(): void; + + // Metric creation (idempotent - returns existing metric if name matches) + createCounter(options: CounterOptions): MetricInstance; + createGauge(options: GaugeOptions): MetricInstance; + createHistogram(options: HistogramOptions): MetricInstance; + createSummary(options: SummaryOptions): MetricInstance; + + // Convenience methods (create + optional initial value) + counter(name: string, help: string, value?: number): MetricInstance; + gauge(name: string, help: string, value?: number): MetricInstance; + histogram(name: string, help: string, buckets?: number[]): MetricInstance; + summary(name: string, help: string, percentiles?: number[]): MetricInstance; + + // Timing utilities + createTimer(metricName: string): () => number; + measureAsync(metricName: string, fn: () => Promise): Promise; + measureSync(metricName: string, fn: () => T): T; +} + +/** Type for the obsidian-metrics plugin instance */ +export interface ObsidianMetricsPlugin { + api: IObsidianMetricsAPI; +} + +/** Augment Obsidian's workspace events to include our custom event */ +declare module "obsidian" { + interface Workspace { + on( + name: "obsidian-metrics:ready", + callback: (api: IObsidianMetricsAPI) => void, + ): EventRef; + trigger(name: "obsidian-metrics:ready", api: IObsidianMetricsAPI): void; + } +} From 90d762fbd034c8638e903085c6884eef0d14f668 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 26 May 2026 17:50:15 -0700 Subject: [PATCH 05/64] fix: preserve pending IndexedDB state after loading from disk --- src/storage/y-indexeddb.js | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/storage/y-indexeddb.js b/src/storage/y-indexeddb.js index 5ca91c4a..d4be3c3c 100644 --- a/src/storage/y-indexeddb.js +++ b/src/storage/y-indexeddb.js @@ -7,6 +7,20 @@ import { metrics } from '../debug' const customStoreName = 'custom' const updatesStoreName = 'updates' +/** + * Compare two Uint8Arrays for equality + * @param {Uint8Array} a + * @param {Uint8Array} b + * @returns {boolean} + */ +const uint8ArrayEquals = (a, b) => { + if (a.length !== b.length) return false + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false + } + return true +} + // Use a higher threshold on startup to avoid slow initial compaction // After sync, use the lower threshold to keep the database lean export const STARTUP_TRIM_SIZE = 500 @@ -48,8 +62,9 @@ export const storeState = (idbPersistence, forceStore = true) => fetchUpdates(idbPersistence) .then(updatesStore => { if (forceStore || idbPersistence._dbsize >= RUNTIME_TRIM_SIZE) { + const compactedState = Y.encodeStateAsUpdate(idbPersistence.doc) const startTime = performance.now() - idb.addAutoKey(updatesStore, Y.encodeStateAsUpdate(idbPersistence.doc)) + idb.addAutoKey(updatesStore, compactedState) .then(() => idb.del(updatesStore, idb.createIDBKeyRangeUpperBound(idbPersistence._dbref, true))) .then(() => idb.count(updatesStore).then(cnt => { idbPersistence._dbsize = cnt @@ -102,12 +117,29 @@ export class IndexeddbPersistence extends Observable { this._db.then(db => { this.db = db + // Capture pending state before loading from IDB + /** @type {Uint8Array|null} */ + let pendingState = null /** * @param {IDBObjectStore} updatesStore */ - const beforeApplyUpdatesCallback = (updatesStore) => idb.addAutoKey(updatesStore, Y.encodeStateAsUpdate(doc)) - const afterApplyUpdatesCallback = () => { + const beforeApplyUpdatesCallback = (updatesStore) => { + // Capture any in-memory state before loading from IDB + pendingState = Y.encodeStateAsUpdate(doc) + } + const afterApplyUpdatesCallback = (updatesStore) => { if (this._destroyed) return this + // After loading from IDB, check if pending state had anything new + if (pendingState && pendingState.length > 2) { + const vectorBeforePending = Y.encodeStateVector(doc) + Y.applyUpdate(doc, pendingState, this) + const vectorAfterPending = Y.encodeStateVector(doc) + const changed = !uint8ArrayEquals(vectorBeforePending, vectorAfterPending) + // Only write if applying pending state actually changed something + if (changed) { + idb.addAutoKey(updatesStore, pendingState) + } + } this.synced = true this.emit('synced', [this]) } From cd1ae0ec041dd18a82450478808d93f02f2a52d1 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 26 May 2026 17:50:28 -0700 Subject: [PATCH 06/64] fix: skip storing empty IndexedDB updates --- src/storage/y-indexeddb.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/storage/y-indexeddb.js b/src/storage/y-indexeddb.js index d4be3c3c..cf724cac 100644 --- a/src/storage/y-indexeddb.js +++ b/src/storage/y-indexeddb.js @@ -159,6 +159,11 @@ export class IndexeddbPersistence extends Observable { */ this._storeUpdate = (update, origin) => { if (this.db && origin !== this) { + // Skip updates with empty state vectors (no actual content) + const stateVector = Y.encodeStateVectorFromUpdate(update) + if (stateVector.length === 0) { + return + } const [updatesStore] = idb.transact(/** @type {IDBDatabase} */ (this.db), [updatesStoreName]) idb.addAutoKey(updatesStore, update) ++this._dbsize From 5850d9831479be1514d1067a6cd754e72348d6af Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 20 Jan 2026 15:04:36 -0800 Subject: [PATCH 07/64] fix: don't try to reconnect to realtime continuously on auth failure --- src/LoginManager.ts | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/LoginManager.ts b/src/LoginManager.ts index 6b4e5837..68b35565 100644 --- a/src/LoginManager.ts +++ b/src/LoginManager.ts @@ -351,9 +351,16 @@ export class LoginManager extends Observable { const result = await this.endpointManager.validateAndSetEndpoints(timeoutMs); if (result.success && this.endpointManager.hasValidatedEndpoints()) { + // Clean up old PocketBase instance before creating new one + this.pb.cancelAllRequests(); + this.pb.realtime.unsubscribe(); + // Recreate PocketBase instance with new auth URL const pbLog = curryLog("[Pocketbase]", "debug"); - this.pb = new PocketBase(this.endpointManager.getAuthUrl(), this.authStore); + this.pb = new PocketBase( + this.endpointManager.getAuthUrl(), + this.authStore, + ); this.pb.beforeSend = (url, options) => { pbLog(url, options); if (!this.pb.authStore.isValid && this.user) { @@ -389,6 +396,7 @@ export class LoginManager extends Observable { logout() { this.pb.cancelAllRequests(); + this.pb.realtime.unsubscribe(); this.pb.authStore.clear(); this.user = undefined; this.notifyListeners(); @@ -549,10 +557,18 @@ export class LoginManager extends Observable { async login(provider: string): Promise { this.beforeLogin(); - const authData = await this.pb.collection("users").authWithOAuth2({ - provider: provider, - }); - return this.setup(authData, provider); + try { + const authData = await this.pb.collection("users").authWithOAuth2({ + provider: provider, + }); + return this.setup(authData, provider); + } catch (e) { + // Clean up realtime subscription to prevent reconnection loops + // authWithOAuth2 internally subscribes to @oauth2 via SSE, and if it fails, + // PocketBase's realtime client will keep trying to reconnect indefinitely + this.pb.realtime.unsubscribe(); + throw e; + } } async openLoginPage() { From bfa6e9ad2467d54ad7fcfd8e75af0e864af0c7f3 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Thu, 29 Jan 2026 00:47:41 +0000 Subject: [PATCH 08/64] fix: await pending IndexedDB writes before closing in y-indexeddb Track pending write operations and wait for them to complete in destroy() before closing the database. This prevents data loss when the persistence layer is torn down while writes are still in flight. Co-Authored-By: Claude Opus 4.5 --- src/storage/y-indexeddb.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/storage/y-indexeddb.js b/src/storage/y-indexeddb.js index cf724cac..d9060bd3 100644 --- a/src/storage/y-indexeddb.js +++ b/src/storage/y-indexeddb.js @@ -153,6 +153,11 @@ export class IndexeddbPersistence extends Observable { * @type {any} */ this._storeTimeoutId = null + /** + * Track pending write operations for proper teardown. + * @type {Set>} + */ + this._pendingWrites = new Set() /** * @param {Uint8Array} update * @param {any} origin @@ -165,7 +170,11 @@ export class IndexeddbPersistence extends Observable { return } const [updatesStore] = idb.transact(/** @type {IDBDatabase} */ (this.db), [updatesStoreName]) - idb.addAutoKey(updatesStore, update) + const writePromise = idb.addAutoKey(updatesStore, update) + this._pendingWrites.add(writePromise) + writePromise.finally(() => { + this._pendingWrites.delete(writePromise) + }) ++this._dbsize metrics.setDbSize(this.name, this._dbsize) const trimSize = this.synced ? RUNTIME_TRIM_SIZE : STARTUP_TRIM_SIZE @@ -200,16 +209,19 @@ export class IndexeddbPersistence extends Observable { return super.once(name, f) } - destroy () { + async destroy () { if (this._storeTimeoutId) { clearTimeout(this._storeTimeoutId) } this.doc.off('update', this._storeUpdate) this.doc.off('destroy', this.destroy) this._destroyed = true - return this._db.then(db => { - db.close() - }) + // Wait for all pending writes to complete before closing + if (this._pendingWrites.size > 0) { + await Promise.all(this._pendingWrites) + } + const db = await this._db + db.close() } /** From 3afe0a59fa7029ba4f4db98a63cf3b2621e2d0f4 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Wed, 4 Feb 2026 00:27:32 +0000 Subject: [PATCH 09/64] fix: correct hasUserData threshold from >3 to >0 The hasUserData() check determines whether IndexedDB contains stored Yjs updates (indicating the document has real content vs being empty). The previous threshold of >3 was based on outdated assumptions. Analysis of the current y-indexeddb implementation shows: - _dbsize only counts entries in the 'updates' store (not 'custom' metadata) - Empty docs don't write anything: pendingState.length must be >2 to write - The _storeUpdate handler skips updates with empty state vectors - Therefore: empty DB = 0 entries, any real content = 1+ entries The correct threshold is >0 to detect presence of stored updates. Co-Authored-By: Claude Opus 4.5 --- src/storage/y-indexeddb.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/storage/y-indexeddb.js b/src/storage/y-indexeddb.js index d9060bd3..24c2c49d 100644 --- a/src/storage/y-indexeddb.js +++ b/src/storage/y-indexeddb.js @@ -270,12 +270,12 @@ export class IndexeddbPersistence extends Observable { } /** - * Check if this database contains meaningful user data - * (more than just initial metadata) + * Check if this database contains meaningful user data. + * Returns true if there are any stored updates in IndexedDB. * @return {boolean} */ hasUserData () { - return this._dbsize > 3 + return this._dbsize > 0 } /** From f91d30301efa04d2992a0b167f581dd141bbaa28 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 26 May 2026 17:21:20 -0700 Subject: [PATCH 10/64] fix: cancel Observable deliveries without resurrecting PostOffice --- src/SettingsStorage.ts | 2 ++ src/observable/Observable.ts | 37 +++++++++++++++++++++++++++++------- src/observable/Postie.ts | 26 +++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/SettingsStorage.ts b/src/SettingsStorage.ts index 5d34051f..c19638af 100644 --- a/src/SettingsStorage.ts +++ b/src/SettingsStorage.ts @@ -113,6 +113,7 @@ */ import { Observable, type Unsubscriber } from "./observable/Observable"; +import { PostOffice } from "./observable/Postie"; export type PathSegment = string | number; export type Path = PathSegment[]; @@ -696,6 +697,7 @@ export class NamespacedSettings< run(this.get()); return () => { this._listeners.delete(run); + PostOffice.peekInstance()?.cancel(run); }; } diff --git a/src/observable/Observable.ts b/src/observable/Observable.ts index dbf3c1f6..a6baed86 100644 --- a/src/observable/Observable.ts +++ b/src/observable/Observable.ts @@ -44,11 +44,14 @@ export class Observable extends HasLogging implements IObservable { } notifyListeners(): void { + // Use peekInstance: late notifications from async work that finishes + // post-disable would otherwise auto-create a fresh PostOffice + // singleton (leaking the entire mail graph including recipient + // closures that capture SharedFolder/Document). + const postie = PostOffice.peekInstance(); + if (!postie) return; for (const recipient of this._listeners) { - PostOffice.getInstance().send( - this as unknown as T & IObservable, - recipient, - ); + postie.send(this as unknown as T & IObservable, recipient); } } @@ -61,7 +64,7 @@ export class Observable extends HasLogging implements IObservable { subscribe(run: Subscriber): Unsubscriber { this._listeners.add(run); - PostOffice.getInstance().send( + PostOffice.peekInstance()?.send( this as unknown as T & IObservable, run, true, @@ -72,23 +75,43 @@ export class Observable extends HasLogging implements IObservable { } off(listener: () => void): void { - this._listeners.delete(listener); + // May be called during plugin teardown after destroy() nulled + // _listeners — e.g. FolderNavigationDecorations holds off() closures + // from SharedFolder.fset.on() and fires them in its own destroy. + // If the SharedFolder was destroyed first, _listeners is null by + // the time the closure runs. Match unsubscribe()'s null guard. + this._listeners?.delete(listener); + // Use peekInstance: getInstance() throws after PostOffice.destroy() + // and auto-creates a fresh singleton if instance is null, which + // would leak the entire mail graph through unbounded mailbox + // accumulation when off() runs from an awaitOnReload promise. + PostOffice.peekInstance()?.cancel(listener); } unsubscribe(run: Subscriber): void { if (this._listeners) { this._listeners.delete(run); } + PostOffice.peekInstance()?.cancel(run); } destroy() { this.destroyed = true; + observables.delete(this); if (this.unsubscribes) { this.unsubscribes.forEach((unsub) => { unsub(); }); } - this._listeners?.clear(); + if (this._listeners) { + const postie = PostOffice.peekInstance(); + if (postie) { + for (const listener of this._listeners) { + postie.cancel(listener); + } + } + this._listeners.clear(); + } this._listeners = null as any; } } diff --git a/src/observable/Postie.ts b/src/observable/Postie.ts index eb5c0841..18312123 100644 --- a/src/observable/Postie.ts +++ b/src/observable/Postie.ts @@ -40,6 +40,19 @@ export class PostOffice { return PostOffice.instance; } + /** + * Returns the singleton if it exists, or null if PostOffice has been + * destroyed or was never created. Use this from cleanup/destroy paths + * that may run after `PostOffice.destroy()` (e.g. async work registered + * with `awaitOnReload`) — calling `getInstance()` post-destroy throws + * and creating a fresh singleton mid-teardown leaks the entire mail + * graph (each entry's `recipient` closure pins module-level classes). + */ + static peekInstance(): PostOffice | null { + if (this._destroyed) return null; + return PostOffice.instance ?? null; + } + beginTransaction(): void { this.isInTransaction = true; this.currentTransactionId++; @@ -52,6 +65,14 @@ export class PostOffice { } } + /** + * Cancel any pending deliveries for a recipient. + * Call this when unsubscribing to prevent stale notifications. + */ + cancel(recipient: (value: any) => void): void { + this.mailboxes.delete(recipient); + } + send( sender: T & IObservable, recipient: (value: T) => void, @@ -192,6 +213,11 @@ export class PostOffice { } static destroy(): void { + // Always mark destroyed even if no instance was lazily created this + // cycle — otherwise subscribers that fire from async work post-disable + // would call getInstance() and silently create a fresh singleton (no + // teardown wired up), accumulating mail that pins the entire module. + PostOffice._destroyed = true; if (PostOffice.instance) { // Clear all mailboxes PostOffice.instance.mailboxes = null as any; From 53f51d368949b9452f23ae3c8c4727aeb3c4e53f Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 28 Apr 2026 15:11:46 -0700 Subject: [PATCH 11/64] fix: avoid replaying immediate Postie deliveries --- __tests__/ObservableMap.test.ts | Bin 13798 -> 14383 bytes src/observable/Postie.ts | 1 + 2 files changed, 1 insertion(+) diff --git a/__tests__/ObservableMap.test.ts b/__tests__/ObservableMap.test.ts index 38fc8feda66af3c7937bca1a82813288e905b9a5..89e206cfa9c1b1e5216e1432811548e248e04f81 100644 GIT binary patch literal 14383 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 p9k7kNF&_jVX#e*MU$RV#}BUE;s( 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 diff --git a/src/observable/Postie.ts b/src/observable/Postie.ts index 18312123..b5701d06 100644 --- a/src/observable/Postie.ts +++ b/src/observable/Postie.ts @@ -94,6 +94,7 @@ export class PostOffice { if (immediate) { this.deliverImmediate(sender, recipient); + this.mailboxes.get(recipient)?.delete(sender); } else if (!this.isInTransaction && !this.isDelivering) { this.scheduleDelivery(); } From 5912b36a41079c0a605b3509847b8d03ade1120c Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Wed, 18 Mar 2026 13:43:42 -0700 Subject: [PATCH 12/64] fix: make staging build one-shot by default, opt into watch with --watch `node esbuild.config.mjs staging .` now builds and exits. `npm run staging` passes --watch to preserve existing watch behavior. Co-Authored-By: Claude Opus 4.6 --- esbuild.config.mjs | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 03b8f03f..512a9d93 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -23,7 +23,7 @@ const gitTag = runBuildMetadataCommand("git describe --tags --always").trim(); const develop = process.argv[2] === "develop"; const staging = process.argv[2] === "staging"; -const watch = process.argv[2] === "watch" || staging; +const watch = process.argv[2] === "watch" || process.argv.includes("--watch"); const debug = process.argv[2] === "debug" || watch || staging || develop; const out = process.argv[3] || "."; const tld = staging ? "dev" : "md"; diff --git a/package.json b/package.json index 57336274..1bc2f8a4 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs develop", "release": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", "beta": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs debug", - "staging": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs staging", + "staging": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs staging --watch", "version": "node version-bump.mjs && git add manifest.json versions.json", "test": "jest", "lint": "npx eslint . --ext .ts" From b2a04b59f8b39c49b27c8689481aa00d3bfccf52 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Thu, 19 Mar 2026 16:53:08 -0700 Subject: [PATCH 13/64] fix: skip flags when parsing output dir in esbuild config `staging --watch

` was setting output to `--watch` instead of the vault path because argv[3] grabbed the flag positionally. Co-Authored-By: Claude Opus 4.6 --- esbuild.config.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 512a9d93..9cc9d6a1 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -25,7 +25,8 @@ const develop = process.argv[2] === "develop"; const staging = process.argv[2] === "staging"; const watch = process.argv[2] === "watch" || process.argv.includes("--watch"); const debug = process.argv[2] === "debug" || watch || staging || develop; -const out = process.argv[3] || "."; +const positionalArgs = process.argv.slice(3).filter((a) => !a.startsWith("--")); +const out = positionalArgs[0] || "."; const tld = staging ? "dev" : "md"; const apiUrl = `https://api.system3.${tld}`; From bd68c8977902e8f1198c8417afc325b4fba8c01c Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Fri, 20 Mar 2026 00:38:42 -0700 Subject: [PATCH 14/64] fix: skip manifest copy in local debug builds Co-Authored-By: Claude Opus 4.6 --- esbuild.config.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 9cc9d6a1..895d7d11 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -127,7 +127,7 @@ const watchAndMove = (fnames, mapping) => { const mapping = debug ? { "manifest-beta.json": "manifest.json" } : {}; const manifest = debug ? "manifest-beta.json" : "manifest.json"; -const files = ["styles.css", manifest]; +const files = debug && out === "." ? ["styles.css"] : ["styles.css", manifest]; const updateManifest = (manifest) => { const manifestPath = path.join(out, path.basename("manifest.json")); @@ -159,7 +159,7 @@ if (watch) { } else { await context.rebuild(); move(files, mapping); - if (!develop) { + if (!develop && out !== ".") { updateManifest(); } process.exit(0); From 793e8c30f6030a454df7450bb18c9f5a452fb21c Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Wed, 8 Apr 2026 06:06:16 +0000 Subject: [PATCH 15/64] fix: stop LoginManager.openLoginPage from resolving false prematurely The listener ran resolve(false) unconditionally after the if-block, so any notification where loggedIn was still false settled the promise with false and left the listener attached to the Observable forever. Drop the unconditional resolve(false); the listener stays armed until loggedIn flips true and detaches itself in that branch. --- src/LoginManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LoginManager.ts b/src/LoginManager.ts index 68b35565..9fc3d733 100644 --- a/src/LoginManager.ts +++ b/src/LoginManager.ts @@ -579,7 +579,6 @@ export class LoginManager extends Observable { this.off(isLoggedIn); resolve(true); } - resolve(false); }; this.on(isLoggedIn); }); From ae71d5e20ea45b33158c71782c8ddd881e0fbeb2 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Thu, 21 May 2026 16:33:06 -0700 Subject: [PATCH 16/64] fix: keep yjs on one module graph --- esbuild.config.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 895d7d11..40285e31 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -47,6 +47,13 @@ const NotifyPlugin = { const YjsInternalsPlugin = { name: "yjs-internals", setup(build) { + // Keep public Yjs imports and internal helper imports on the same + // module graph so Item/GC instanceof checks agree in the bundle. + build.onResolve({ filter: /^yjs$/ }, args => { + return { + path: path.resolve("node_modules/yjs/src/index.js"), + }; + }); build.onResolve({ filter: /^yjs\/dist\/src\/internals$/ }, args => { return { path: path.resolve("node_modules/yjs/src/internals.js"), From 3a97a886debdaecd36ae1c38dbf1b79fd2d3d0e4 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 26 May 2026 17:22:09 -0700 Subject: [PATCH 17/64] fix: guard release checks after teardown --- src/UpdateManager.ts | 66 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/src/UpdateManager.ts b/src/UpdateManager.ts index 6effaa35..df27ea35 100644 --- a/src/UpdateManager.ts +++ b/src/UpdateManager.ts @@ -1,4 +1,4 @@ -import type { Plugin } from "obsidian"; +import { Plugin } from "obsidian"; import type { TimeProvider } from "./TimeProvider"; import { Observable } from "./observable/Observable"; import { customFetch } from "./customFetch"; @@ -72,6 +72,7 @@ export class UpdateManager extends Observable { private lastReleaseCheck: number = 0; private lastChannelCheck: number = 0; private readonly CHECK_INTERVAL = 1000 * 60 * 60 * 24; + private updateGeneration: number = 0; observableName = "UpdateManager"; constructor( @@ -85,6 +86,9 @@ export class UpdateManager extends Observable { } public get releases(): Release[] { + if (this.destroyed) { + return []; + } return [...this.githubReleases.values()].sort((a, b) => b.tag_name.localeCompare(a.tag_name, undefined, { numeric: true, @@ -94,13 +98,23 @@ export class UpdateManager extends Observable { } public get beta(): Release | undefined { + if (this.destroyed) { + return undefined; + } return this.releaseChannels.get("beta"); } public get stable(): Release | undefined { + if (this.destroyed) { + return undefined; + } return this.releaseChannels.get("stable"); } + private isActiveGeneration(generation: number): boolean { + return !this.destroyed && generation === this.updateGeneration; + } + private async fetchReleases(): Promise { const apiUrl = `https://api.github.com/repos/${REPOSITORY}/releases`; @@ -150,6 +164,9 @@ export class UpdateManager extends Observable { public async fetchLatestTagFromChannel( channel: "beta" | "stable", ): Promise { + if (this.destroyed) { + return null; + } try { const manifestPath = { beta: "manifest-beta.json", @@ -171,6 +188,10 @@ export class UpdateManager extends Observable { } public async getReleases(): Promise { + const generation = this.updateGeneration; + if (!this.isActiveGeneration(generation)) { + return []; + } try { // Only fetch if we haven't fetched in a while to avoid rate limiting const now = Date.now(); @@ -186,6 +207,9 @@ export class UpdateManager extends Observable { this.lastReleaseCheck = now; const releases = await this.fetchReleases(); + if (!this.isActiveGeneration(generation)) { + return []; + } const localReleases = new Set([...this.githubReleases.keys()]); const remoteReleases = new Set(); releases.forEach((release: Release) => { @@ -201,6 +225,9 @@ export class UpdateManager extends Observable { }); const latest = await this.fetchLatestRelease(); + if (!this.isActiveGeneration(generation)) { + return []; + } if (latest) { latest.latest = true; this.githubReleases.set(latest.tag_name, latest); @@ -209,12 +236,21 @@ export class UpdateManager extends Observable { // Notify subscribers that we have new releases data this.notifyListeners(); } catch (error) { - this.error("Failed to fetch GitHub releases:", error); + if (this.isActiveGeneration(generation)) { + this.error("Failed to fetch GitHub releases:", error); + } + } + if (!this.isActiveGeneration(generation)) { + return []; } return [...this.githubReleases.values()]; } private async getChannelRelease(): Promise { + const generation = this.updateGeneration; + if (!this.isActiveGeneration(generation)) { + return null; + } const now = Date.now(); const channelRelease = this.releaseChannels.get( this.releaseSettings.get().channel, @@ -225,6 +261,9 @@ export class UpdateManager extends Observable { this.lastReleaseCheck = now; const releases = await this.getReleases(); + if (!this.isActiveGeneration(generation)) { + return null; + } if (releases.length === 0) { this.debug("No releases found"); return null; @@ -234,6 +273,9 @@ export class UpdateManager extends Observable { if (!channel) return null; const version = await this.fetchLatestTagFromChannel(channel); + if (!this.isActiveGeneration(generation)) { + return null; + } if (!version) return null; const release = releases.find((release) => { @@ -248,14 +290,22 @@ export class UpdateManager extends Observable { } private async update() { + const generation = this.updateGeneration; await this.getReleases(); + if (!this.isActiveGeneration(generation)) { + return; + } await this.getChannelRelease(); } public start(): void { - this.update(); + if (this.destroyed) { + return; + } + this.updateGeneration += 1; + void this.update(); this.updateCheckInterval = this.timeProvider.setInterval( - () => this.update(), + () => void this.update(), this.CHECK_INTERVAL, ); } @@ -291,7 +341,9 @@ export class UpdateManager extends Observable { } } - public async fetchReleaseManifest(release: Release): Promise { + public async fetchReleaseManifest( + release: Release, + ): Promise { try { const manifestAsset = release.assets.find( (asset) => asset.name === "manifest.json", @@ -364,8 +416,12 @@ export class UpdateManager extends Observable { } override destroy(): void { + if (this.destroyed) { + return; + } // Stop periodic update checking this.stop(); + this.updateGeneration += 1; // Set state to null before destroying this.githubReleases = null as any; From 012a7f15e3e54f5a0f49b4d4f19597d96bb9d363 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 26 May 2026 17:22:09 -0700 Subject: [PATCH 18/64] fix: use release channel aliases in release changelog --- src/components/ReleaseManagerContent.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ReleaseManagerContent.svelte b/src/components/ReleaseManagerContent.svelte index 99071414..de4578ae 100644 --- a/src/components/ReleaseManagerContent.svelte +++ b/src/components/ReleaseManagerContent.svelte @@ -511,10 +511,10 @@
- {#if $manifestVersions[$selectedManifestTag] === "stable-alias" || $manifestVersions[$selectedManifestTag] === "beta-alias" || $activeChannel === "development"} - {#if $manifestVersions[$selectedManifestTag] === "stable-alias"} + {#if $releaseChannels[$selectedManifestTag] === "stable-alias" || $releaseChannels[$selectedManifestTag] === "beta-alias" || $activeChannel === "development"} + {#if $releaseChannels[$selectedManifestTag] === "stable-alias"}

Current stable release from main branch manifest.json

- {:else if $manifestVersions[$selectedManifestTag] === "beta-alias"} + {:else if $releaseChannels[$selectedManifestTag] === "beta-alias"}

Current beta release from main branch manifest-beta.json

{:else if $selectedManifestTag === plugin.version}

Current development version

From 80fd5a0774abf81517be4a4a95671b6a630b09fe Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 26 May 2026 17:53:01 -0700 Subject: [PATCH 19/64] chore: silence Svelte CSS deprecation warning --- esbuild.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 40285e31..4d9ac407 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -88,7 +88,7 @@ const context = await esbuild.context({ format: "cjs", plugins: [ esbuildSvelte({ - compilerOptions: { css: true }, + compilerOptions: { css: "injected" }, preprocess: sveltePreprocess(), }), YjsInternalsPlugin, From 13b021ecd6828b5c09f8c141d1f49b3bb09366f1 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 26 May 2026 17:53:15 -0700 Subject: [PATCH 20/64] build: refresh GIT_TAG during watch rebuilds --- esbuild.config.mjs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 4d9ac407..41ee5891 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -19,7 +19,14 @@ const runBuildMetadataCommand = (command) => execSync(command, { stdio: ["ignore", "pipe", "pipe"], }); -const gitTag = runBuildMetadataCommand("git describe --tags --always").trim(); +const getGitTag = () => { + try { + return runBuildMetadataCommand("git describe --tags --always").trim(); + } catch (e) { + return "dev"; + } +}; +const gitTag = getGitTag(); const develop = process.argv[2] === "develop"; const staging = process.argv[2] === "staging"; @@ -39,7 +46,15 @@ const NotifyPlugin = { name: "on-end", setup(build) { build.onEnd((result) => { - if (result.errors.length > 0) execSync(`notify-send "Build Failed"`); + if (result.errors.length > 0) { + execSync(`notify-send "Build Failed"`); + } else if (watch) { + const tag = getGitTag(); + const outfile = build.initialOptions.outfile; + const content = fs.readFileSync(outfile, "utf8"); + fs.writeFileSync(outfile, content.replace(/__GIT_TAG__/g, tag)); + console.log(`GIT_TAG: ${tag}`); + } }); }, }; @@ -100,7 +115,7 @@ const context = await esbuild.context({ sourcemap: debug ? "inline" : false, define: { BUILD_TYPE: debug ? '"debug"' : '"prod"', - GIT_TAG: `"${gitTag}"`, + GIT_TAG: watch ? '"__GIT_TAG__"' : `"${gitTag}"`, HEALTH_URL: `"${healthUrl}"`, API_URL: `"${apiUrl}"`, AUTH_URL: `"${authUrl}"`, From 479e97bd448df3583bf02a643ddebe1eabe78f6d Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Tue, 26 May 2026 17:54:01 -0700 Subject: [PATCH 21/64] fix: ignore dotfiles in esbuild file watcher --- esbuild.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 41ee5891..c9bc1673 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -136,7 +136,7 @@ const copyFile = (src, dest) => { const watchAndMove = (fnames, mapping) => { // only usable on top level directory const watcher = chokidar.watch(fnames, { - ignored: /(^|[\/\\])\../, // ignore dotfiles + ignored: /(^|[\/\\])\./, // ignore dotfiles persistent: true, }); From ca9e8da8b3f581cba85a47a8d15f24bfaa944008 Mon Sep 17 00:00:00 2001 From: Daniel Grossmann-Kavanagh Date: Thu, 22 Jan 2026 21:19:32 -0800 Subject: [PATCH 22/64] fix: share folder modal mobile layout - Use Obsidian's native modal title via setTitle() - Remove duplicate modal-content class nesting - Use SlimSettingItem for inline toggle and button layout - Add visible background to toggle track for dark backgrounds - Fix folder name overflow with ellipsis truncation - Prevent folder icons from shrinking (cherry picked from commit b512db14132dfc8ed5a69bb54d982eb189067a70) --- src/components/SelectedFolder.svelte | 12 +++++ src/components/ShareFolderModalContent.svelte | 47 ++++++++----------- src/ui/ShareFolderModal.ts | 2 + 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/components/SelectedFolder.svelte b/src/components/SelectedFolder.svelte index ac1e3e47..da886e75 100644 --- a/src/components/SelectedFolder.svelte +++ b/src/components/SelectedFolder.svelte @@ -105,12 +105,24 @@ display: flex; align-items: center; gap: 6px; + min-width: 0; + } + + .folder-state:last-of-type { + flex: 1; + min-width: 0; + } + + :global(.folder-icon) { + flex-shrink: 0; } .folder-name { font-weight: 500; color: var(--text-normal); white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .clear-button { diff --git a/src/components/ShareFolderModalContent.svelte b/src/components/ShareFolderModalContent.svelte index 7ea59726..d2c0bf58 100644 --- a/src/components/ShareFolderModalContent.svelte +++ b/src/components/ShareFolderModalContent.svelte @@ -3,9 +3,8 @@ import type { Relay, RelayUser, Role } from "../Relay"; import type { RelayManager } from "../RelayManager"; import type { SharedFolder, SharedFolders } from "../SharedFolder"; - import SettingItem from "./SettingItem.svelte"; import SettingItemHeading from "./SettingItemHeading.svelte"; - import SettingGroup from "./SettingGroup.svelte"; + import SlimSettingItem from "./SlimSettingItem.svelte"; import SelectedFolder from "./SelectedFolder.svelte"; import { onMount, onDestroy } from "svelte"; import { derived, writable } from "svelte/store"; @@ -22,6 +21,7 @@ isPrivate: boolean, userIds: string[], ) => Promise; + export let setTitle: (title: string) => void = () => {}; let currentStep: "main" | "users" = "main"; let isPrivate = false; @@ -109,6 +109,7 @@ if (isPrivate) { currentStep = "users"; + setTitle("Add Users to Folder"); } else { handleShare(); } @@ -154,6 +155,7 @@ function goBack() { currentStep = "main"; + setTitle("Share local folder"); } function getInitials(name: string): string { @@ -231,9 +233,7 @@ {#if currentStep === "main"} - - -