Skip to content

Commit 8d4f7f7

Browse files
dwertheimerclaude
andcommitted
Fix getUsersNoteFileExtension() returning a function in React windows
In an HTML/React window DataStore is an async bridge proxy: every property access returns a function, so the truthiness check in getUsersNoteFileExtension() returned that function instead of a string. makeCalendarFilename() then stringified it, producing filenames like "20260829.function(...args) { return sendBridgeCall(...) }". Visible in the Dashboard's Add-to-any-note dialog: picking a date in the NoteChooser calendar picker filled the Note field (and the heading-chooser's sourceNote) with that function source. - NPFileExtensions: only trust an actual non-empty string; fall back to DEFAULT_NOTE_FILE_EXTENSION for anything else. Trim and strip a leading dot. - NoteChooser: match the picked date against loaded notes on the filename without its extension, since a React window can't know the real extension. - Tests for the bridge-function case, non-string values, and trimming. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 9b42b28 commit 8d4f7f7

3 files changed

Lines changed: 36 additions & 4 deletions

File tree

‎helpers/NPFileExtensions.js‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ export const DEFAULT_NOTE_FILE_EXTENSION: string = SUPPORTED_NOTE_FILE_EXTENSION
1919

2020
/**
2121
* User's note file extension without leading dot (DataStore preference, or DEFAULT_NOTE_FILE_EXTENSION).
22+
* Note: in a React/HTML window `DataStore` is an async bridge proxy, so every property access returns a
23+
* function rather than the underlying value. Only an actual non-empty string is trusted here; anything
24+
* else (function, Promise, empty string, missing DataStore) falls back to DEFAULT_NOTE_FILE_EXTENSION.
2225
* @returns {string}
2326
*/
2427
export function getUsersNoteFileExtension(): string {
2528
try {
26-
if (DataStore.defaultFileExtension) {
27-
return DataStore.defaultFileExtension
29+
const ext = DataStore.defaultFileExtension
30+
if (typeof ext === 'string' && ext.trim() !== '') {
31+
return ext.trim().replace(/^\./, '')
2832
}
2933
} catch {
3034
/* use fallback */

‎helpers/__tests__/NPFileExtensions.test.js‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,28 @@ describe(`${PLUGIN_NAME}`, () => {
9696
expect(getUsersNoteFileExtension()).toBe(DEFAULT_NOTE_FILE_EXTENSION)
9797
})
9898

99+
test('returns default when defaultFileExtension is a function (React/HTML window bridge proxy)', () => {
100+
// In a React/HTML window every DataStore property access returns an async bridge function,
101+
// which is truthy - returning it built filenames like "20260829.function(...args) {...}".
102+
// $FlowFixMe[incompatible-type] - deliberately wrong type, to mimic the WebView bridge
103+
DataStore.defaultFileExtension = function (...args) {
104+
return Promise.resolve(args)
105+
}
106+
expect(getUsersNoteFileExtension()).toBe(DEFAULT_NOTE_FILE_EXTENSION)
107+
expect(makeCalendarFilename('20260829')).toBe(`20260829.${DEFAULT_NOTE_FILE_EXTENSION}`)
108+
})
109+
110+
test('returns default when defaultFileExtension is not a string', () => {
111+
// $FlowFixMe[incompatible-type] - deliberately wrong type
112+
DataStore.defaultFileExtension = { then: () => {} }
113+
expect(getUsersNoteFileExtension()).toBe(DEFAULT_NOTE_FILE_EXTENSION)
114+
})
115+
116+
test('trims whitespace and any leading dot', () => {
117+
DataStore.defaultFileExtension = ' .txt '
118+
expect(getUsersNoteFileExtension()).toBe('txt')
119+
})
120+
99121
test('returns default when DataStore access throws', () => {
100122
const savedDataStore = global.DataStore
101123
global.DataStore = {

‎helpers/react/DynamicDialog/NoteChooser.jsx‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,14 @@ export function NoteChooser({
399399

400400
logDebug('NoteChooser', `handleCalendarDateSelect: calendarFilename="${calendarFilename}", dateISO="${dateISO}"`)
401401

402-
// Find the note in the notes array or create a new option
403-
const existingNote = notes.find((note) => note.filename === calendarFilename || note.filename.endsWith(`/${calendarFilename}`))
402+
// Find the note in the notes array or create a new option.
403+
// Match on the date part only: in a React window the user's real file extension isn't knowable,
404+
// so calendarFilename falls back to the default one and wouldn't match a .txt vault's note.
405+
const calendarFilenameBase = calendarFilename.replace(RE_NOTE_FILE_EXTENSION, '')
406+
const existingNote = notes.find((note) => {
407+
const noteFilenameBase = note.filename.replace(RE_NOTE_FILE_EXTENSION, '')
408+
return noteFilenameBase === calendarFilenameBase || noteFilenameBase.endsWith(`/${calendarFilenameBase}`)
409+
})
404410

405411
logDebug('NoteChooser', `handleCalendarDateSelect: existingNote=${existingNote ? `found: ${existingNote.title}` : 'not found'}`)
406412

0 commit comments

Comments
 (0)