Skip to content
18 changes: 13 additions & 5 deletions packages/desktop/src/main/filesystem/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,19 @@ class Watcher {

depth: type === 'file' ? (isOsx ? 1 : 0) : undefined,

// Please see GH#1043
awaitWriteFinish: {
stabilityThreshold: WATCHER_STABILITY_THRESHOLD,
pollInterval: WATCHER_STABILITY_POLL_INTERVAL
},
// Defer events until writes settle only for the file watcher, which
// reloads file CONTENT on change and would otherwise read a partial file
// (GH#1043). The directory watcher just lists nodes and re-sorts by mtime,
// so deferring its `add` events only made new files appear in the sidebar
// ~1s late (GH#3955).
...(type === 'file'
? {
awaitWriteFinish: {
stabilityThreshold: WATCHER_STABILITY_THRESHOLD,
pollInterval: WATCHER_STABILITY_POLL_INTERVAL
}
}
: {}),

usePolling
// chokidar's `ignored` callback signature varies between versions; this options
Expand Down
2 changes: 0 additions & 2 deletions packages/desktop/src/renderer/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@ const isCodeMirrorRaceCondition = (error: Error | null | undefined): boolean =>
const handleRendererError = (event: ErrorEvent | PromiseRejectionEvent | Event): void => {
const errorEvent = event as ErrorEvent
if (errorEvent.error) {
// Suppress known non-fatal CodeMirror race conditions
// These occur during rapid clicking/editing and don't affect functionality
if (isCodeMirrorRaceCondition(errorEvent.error)) {
console.warn('Suppressed non-fatal CodeMirror race condition:', errorEvent.error.message)
return
Expand Down
2 changes: 0 additions & 2 deletions packages/desktop/src/renderer/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import bus from '../bus'
// Directly import translation files
import enTranslations from '../../../../static/locales/en.json'

// Create the Vue i18n instance.
// vue-i18n's options type intersection between Composition + Legacy modes is
// notoriously difficult to satisfy with mixed shapes; we cast the options once
// at the call site rather than spreading `any` further.
Expand Down Expand Up @@ -61,7 +60,6 @@ export const t = (key: string, ...args: unknown[]): string => {
// don't fire duplicate IPCs for the same locale.
const inflightLoads = new Map<string, Promise<Record<string, unknown> | undefined>>()

// Export language setter function
export const setLanguage = async(locale: string): Promise<void> => {
if (!locale) return
const globalI18n = i18n.global
Expand Down
1 change: 0 additions & 1 deletion packages/desktop/src/renderer/src/pages/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ const notificationStore = useNotificationStore()

const timer = ref<ReturnType<typeof setTimeout> | null>(null)

// States from Pinia
const { windowActive, platform, init } = storeToRefs(mainStore)
const { showTabBar } = storeToRefs(layoutStore)
const { sourceCode, theme, customCss, textDirection, zoom } = storeToRefs(preferencesStore)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
<div class="detection-header">
<h6>{{ t('preferences.image.uploader.picgoDetection') }}</h6>
<div class="detection-controls">
<!-- Standalone refresh button -->
<button
v-if="showStandaloneRefreshButton"
class="standalone-refresh-button"
Expand Down Expand Up @@ -84,7 +83,6 @@
>
<!-- All SVG icons removed -->
</button>
<!-- Refresh button -->
<button
v-if="showRefreshButton"
class="refresh-button"
Expand Down Expand Up @@ -167,7 +165,6 @@
</div>
</div>

<!-- PicGo basic usage guide -->
<div class="usage-guide">
<div class="usage-title">
{{ t('preferences.image.uploader.usageGuide.title') }}
Expand Down Expand Up @@ -290,7 +287,7 @@ const picgoDetectionFailed = ref<boolean>(false) // Whether detection failed
const picgoDetectionStatus = ref<string>('') // Detection status text
const picgoDebugInfo = ref<string>('') // Debug information
const isDetecting = ref<boolean>(false) // Whether detection is in progress
const lastDetectionTime = ref<string | null>(null) // Last detection time
const lastDetectionTime = ref<string | null>(null)
const lastSuccessTime = ref<string | null>(null) // Last successful detection time
const detectionTimer = ref<ReturnType<typeof setTimeout> | null>(null) // Detection interval constant moved into scheduleNextDetection function
const consecutiveFailures = ref<number>(0) // Number of consecutive failures
Expand Down Expand Up @@ -695,7 +692,6 @@ const stopAnimationAndButton = () => {
}

const testPicgo = async (): Promise<void> => {
// Record detection start time
lastDetectionTime.value = new Date().toISOString()

const debugMessages: string[] = []
Expand Down
1 change: 0 additions & 1 deletion packages/desktop/src/renderer/src/store/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1942,7 +1942,6 @@ const createApplicationMenuState = ({
}
}

// Clean up
if (Object.getOwnPropertyNames(state.affiliation).length >= 2 && state.affiliation.p) {
delete state.affiliation.p
}
Expand Down
1 change: 0 additions & 1 deletion packages/desktop/src/renderer/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const pinia = createPinia()
export const useMainStore = defineStore('main', () => {
// Platform of system: 'darwin' | 'win32' | 'linux'
const platform = ref<NodeJS.Platform>(window.electron.process.platform)
// MarkText version string
const appVersion = ref<string>(window.electron.process.env.MARKTEXT_VERSION_STRING ?? '')
// Whether current window is active or focused
const windowActive = ref(true)
Expand Down
2 changes: 1 addition & 1 deletion packages/desktop/src/renderer/src/store/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export const usePreferencesStore = defineStore('preferences', {

// Edit modes of the current window (not part of persistent settings)
typewriter: false, // typewriter mode
focus: false, // focus mode
focus: false,
sourceCode: false, // source code mode

// user configration
Expand Down
1 change: 0 additions & 1 deletion packages/desktop/src/renderer/src/util/exportHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ export const exportStyledHTML = async(
dir
})

// Pull out the rendered <article class="markdown-body">…</article> body.
const articleMatch = /<article class="markdown-body">([\s\S]*)<\/article>/.exec(fullDoc)
let article = articleMatch ? articleMatch[1] : fullDoc

Expand Down
2 changes: 0 additions & 2 deletions packages/desktop/src/shared/types/bufferedState.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// Buffered editor state (persisted across window close/reopen).
//
// Refined when Commit 7 rewrites the editor + bufferedState stores.

import type { IFileState } from './files'

Expand Down
1 change: 0 additions & 1 deletion packages/desktop/src/types/muya-core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ declare module '@muyajs/core' {

export function renderToStaticHTML(...args: any[]): any

// Utils.
export function escapeHTML(str: string): string
export function unescapeHTML(str: string): string
export function sanitize(html: string, config?: any, isInline?: boolean): string
Expand Down
3 changes: 0 additions & 3 deletions packages/desktop/src/types/muya.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
* Delete this file the day upstream lands; see packages/website/content/docs/dev/TYPESCRIPT.md.
*/

// Ambient bridge to the JavaScript muya/ tree. Delete this file the day
// upstream TS muya (https://github.com/marktext/muya) lands.
//
// We declare every muya path that's imported from outside src/muya/. This
// cuts the dependency graph at the boundary: TypeScript no longer follows
// imports into the legacy JS source, so e.g. inferred types from dompurify
Expand Down
2 changes: 0 additions & 2 deletions packages/desktop/test/e2e/crash-range-offset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ test.describe('Crash: setStart Range offset', () => {
if (app) await app.close()
})

// Recipe from #2526.
test('Issue #2526: typing escaped <pre>...</pre> then re-selecting does not crash', async() => {
// Type literal `\<pre\>some text\</pre\>` as the user described.
await typeIntoEditor(page, '\\<pre\\>some text\\</pre\\>')
Expand All @@ -64,7 +63,6 @@ test.describe('Crash: setStart Range offset', () => {
await expectNoRendererErrors(app)
})

// Recipe from #3737 — backspace at the start of a code block.
test('Issue #3737: backspace at start of a code block does not crash', async() => {
// Build a code block via the markdown source-mode round-trip.
// (Code blocks are rendered as CodeMirror instances inside Muya; the
Expand Down
3 changes: 1 addition & 2 deletions packages/desktop/test/e2e/parity-source-undo-saved.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import {
} from './helpers'

// PARITY SCOREBOARD — desktop gaps PG2 (file PG02), PG14 (file PG15),
// PG15 (file PG16). Each RUNS headless but currently fails, so each is marked
// `test.fail()`. When the corresponding fix lands, remove the `test.fail()`.
// PG15 (file PG16).

// Trigger an editor undo through the same IPC channel the Edit › Undo menu item
// uses (`mt::editor-edit-action` → bus `undo` → editor.undo()). More reliable
Expand Down
1 change: 0 additions & 1 deletion packages/desktop/test/e2e/view-modes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ const waitForChecked = async(
return last ?? { checked: false, enabled: false }
}

// Poll the menu item until its `enabled` flag matches `want`.
const waitForEnabled = async(
app: ElectronApplication,
id: string,
Expand Down
4 changes: 0 additions & 4 deletions packages/desktop/test/unit/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ export const ListsTemplate = () => {
return loadMarkdownContent('common/Lists.md')
}

// --------------------------------------------------------
// GFM templates
//

export const GfmBasicTextFormattingTemplate = () => {
return loadMarkdownContent('gfm/BasicTextFormatting.md')
}
Expand Down
2 changes: 0 additions & 2 deletions packages/desktop/test/unit/specs/format-menu-state.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ vi.mock('main_renderer/i18n', () => ({ t: (key: string) => key }))

import { createSelectionFormatState } from '@/store/editor'
import { updateFormatMenu } from 'main_renderer/menu/actions/format'
// The toolbar config is a deep subpath of @muyajs/core that vite resolves at
// runtime but whose types are not exposed via the package `exports` map.
// @ts-expect-error deep @muyajs/core subpath resolves at runtime (vite) but exposes no types
import inlineFormatIcons from '@muyajs/core/ui/inlineFormatToolbar/config'
import keybindingsWindows from 'main_renderer/keyboard/keybindingsWindows'
Expand Down
2 changes: 1 addition & 1 deletion packages/desktop/test/unit/specs/pdf.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ vi.hoisted(() => {
// characterize the branch *dispatch* (academic/liber take the inline path and
// never touch `window.fileUtils`/`window.marktext`, unlike a disk theme name)
// rather than asserting a theme-specific selector token, which is unavailable
// here. See suspectedBugs / notes.
// here.

const loadPdf = async() => {
return import('@/util/pdf')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'

// #3955: newly created files appeared in the sidebar ~1s late because the
// directory watcher inherited chokidar's `awaitWriteFinish` (stabilityThreshold
// 1000ms), which defers `add`/`change` events until the file size settles. That
// protection (GH#1043) only matters for the file watcher, which reloads file
// CONTENT on change; the directory watcher just lists nodes and re-sorts by
// mtime. These tests pin that only the file watcher defers events.

const watchMock = vi.fn()

function fakeWatcher(): Record<string, ReturnType<typeof vi.fn>> {
const w: Record<string, ReturnType<typeof vi.fn>> = {}
w.on = vi.fn(() => w)
w.close = vi.fn()
w.add = vi.fn()
w.unwatch = vi.fn()
return w
}

vi.mock('chokidar', () => ({
default: {
watch: (...args: unknown[]) => {
watchMock(...args)
return fakeWatcher()
}
}
}))

// Importing the watcher pulls in the markdown loader, whose encoding detection
// uses the native `ced` addon. Its bindings are built for Electron's ABI, not
// the plain-Node test runner, so stub it to keep this spec import-only.
vi.mock('ced', () => ({ default: () => 'UTF-8' }))

import Watcher, {
WATCHER_STABILITY_THRESHOLD,
WATCHER_STABILITY_POLL_INTERVAL
} from 'main_renderer/filesystem/watcher'

function optionsForLastWatch(): Record<string, unknown> {
const calls = watchMock.mock.calls
return calls[calls.length - 1][1] as Record<string, unknown>
}

describe('watcher awaitWriteFinish (#3955)', () => {
let watcher: Watcher
const win = { id: 1, webContents: { send: vi.fn() } }

beforeEach(() => {
watchMock.mockClear()
const preferences = { getItem: vi.fn(() => false) }
watcher = new Watcher(preferences as never)
})

it('does not defer directory-tree events with awaitWriteFinish', () => {
watcher.watch(win as never, '/project', 'dir')
expect(optionsForLastWatch().awaitWriteFinish).toBeFalsy()
})

it('keeps awaitWriteFinish for the file watcher (GH#1043)', () => {
watcher.watch(win as never, '/project/note.md', 'file')
expect(optionsForLastWatch().awaitWriteFinish).toEqual({
stabilityThreshold: WATCHER_STABILITY_THRESHOLD,
pollInterval: WATCHER_STABILITY_POLL_INTERVAL
})
})
})
2 changes: 0 additions & 2 deletions packages/muya/e2e/tests/blocks/reference-link-image.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,7 @@ test.describe('reference link', () => {
window.muya!.setContent('[a][r]\n');
});

// No reference-link element of any kind survives an unresolved label.
await expect(page.locator('.mu-reference-link')).toHaveCount(0);
// The raw text round-trips through getMarkdown unchanged.
const md = await page.evaluate(() => window.muya!.getMarkdown());
expect(md).toContain('[a][r]');
});
Expand Down
1 change: 0 additions & 1 deletion packages/muya/e2e/tests/diagrams/mermaid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ test.describe('mermaid diagram', () => {
await expect(error).toBeVisible({ timeout: 15_000 });
await expect(error).toContainText('Invalid Diagram Code');

// No SVG should have mounted for a failed parse.
await expect(page.locator(`${editor.diagramPreview} svg`)).toHaveCount(0);

// The editor stays alive (no thrown crash): the source still round-trips.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ test('a wide sequence diagram scales to fit its block, not clipped (#3560)', asy
return { wideEnough: intrinsic > blockWidth, fits: rendered <= blockWidth + 1 };
}, editor.diagramPreview);

expect(fits.wideEnough).toBe(true); // the diagram really is wider than the block
expect(fits.fits).toBe(true); // ...but is scaled down to fit it
expect(fits.wideEnough).toBe(true);
expect(fits.fits).toBe(true);
});
1 change: 0 additions & 1 deletion packages/muya/e2e/tests/diagrams/sequence.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ test.describe('sequence diagram', () => {
const svg = page.locator(`${editor.diagramPreview} svg`).first();
await expect(svg).toBeVisible({ timeout: 15_000 });

// The vendored renderer always tags the svg with the `sequence` class.
await expect(svg).toHaveClass(/sequence/);
});

Expand Down
Loading
Loading