From 672fa4514175dd610ddefc04cc62dcea085252e2 Mon Sep 17 00:00:00 2001 From: Jack Terwilliger Date: Mon, 13 Jul 2026 16:48:57 -0700 Subject: [PATCH 1/4] desktop needed to read templates --- packages/desktop/package.json | 1 + packages/mumo/src/App.svelte | 4 +- .../tests/template-apply.test.ts | 56 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 packages/serialization/tests/template-apply.test.ts diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 5b13c69..5ab182e 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -15,6 +15,7 @@ "desktopName": "mumo.desktop", "main": "out/main/index.js", "scripts": { + "predev": "node -e \"require('electron')\"", "dev": "electron-vite dev", "build": "electron-vite build", "pack": "electron-vite build && pnpm run rebuild-native && electron-builder --config electron-builder.config.cjs --dir", diff --git a/packages/mumo/src/App.svelte b/packages/mumo/src/App.svelte index 1db13ce..547bffd 100644 --- a/packages/mumo/src/App.svelte +++ b/packages/mumo/src/App.svelte @@ -4322,7 +4322,9 @@ let patternSchemaDlgOpen = $state(false) async function applyTemplate() { const file = await platform.openBinaryFile(['mmetf', 'etf'], 'Template files') if (!file) return - const text = await file.file.text() + const text = file.path && isDesktop(platform) + ? new TextDecoder().decode(await platform.readFileAsBytes(file.path)) + : await file.file.text() const ext = (file.path ?? file.file.name).split('.').pop()?.toLowerCase() let tmpl: Parameters[0] try { diff --git a/packages/serialization/tests/template-apply.test.ts b/packages/serialization/tests/template-apply.test.ts new file mode 100644 index 0000000..c3a041b --- /dev/null +++ b/packages/serialization/tests/template-apply.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest' +import { parseMMETF, parseMMEAF } from '../src/index.js' +import { AnnotationStore } from '@mumo/core' +import { readFileSync } from 'fs' +import { fileURLToPath } from 'url' +import { join, dirname } from 'path' +import { buildTemplateMerge } from '../../mumo/src/template-merge.js' + +const root = join(dirname(fileURLToPath(import.meta.url)), '../../../') + +function makeStore() { return new AnnotationStore() } + +describe('template apply end-to-end', () => { + it('applies vocab, LT, and pattern schema from template.mmetf onto a transcript-loaded store', () => { + const store = makeStore() + + // Simulate loading transcript.mmeaf (goes through mmeaf importer → loadJSON with full storeData) + const transcriptXml = readFileSync(join(root, 'tmp/transcript.mmeaf'), 'utf8') + const transcriptResult = parseMMEAF(transcriptXml) + store.loadJSON({ + annotations: transcriptResult.annotations, + tiers: transcriptResult.tiers, + vocabularies: transcriptResult.vocabularies, + linguisticTypes: transcriptResult.linguisticTypes, + patternSchemas: transcriptResult.patternSchemas, + patterns: transcriptResult.patterns, + participants: transcriptResult.participants, + }) + + console.log('store after EAF load:') + console.log(' vocabs:', store.allVocabularies().map(v => v.name)) + console.log(' LTs:', store.allLinguisticTypes().map(lt => lt.name)) + console.log(' patterns:', store.allPatternSchemas().map(s => s.name)) + + // Parse template and build merge + const templateXml = readFileSync(join(root, 'tmp/template.mmetf'), 'utf8') + const tmpl = parseMMETF(templateXml) + const merge = buildTemplateMerge(tmpl, store) + + console.log('conflicts:', merge.conflicts) + console.log('preview:', JSON.stringify(merge.preview, null, 2)) + console.log('hasChanges:', merge.preview.some(s => s.items.some(i => i.action !== 'skip'))) + + // Apply + merge.applyFn(store) + + console.log('store after apply:') + console.log(' vocabs:', store.allVocabularies().map(v => v.name)) + console.log(' LTs:', store.allLinguisticTypes().map(lt => lt.name)) + console.log(' patterns:', store.allPatternSchemas().map(s => s.name)) + + expect(store.allVocabularies().map(v => v.name)).toContain('test') + expect(store.allLinguisticTypes().map(lt => lt.name)).toContain('blah') + expect(store.allPatternSchemas().map(s => s.name)).toContain('howdy') + }) +}) From 5b98c672d265ebf5c9eb43dd0d68cc9e0965fb37 Mon Sep 17 00:00:00 2001 From: Jack Terwilliger Date: Mon, 13 Jul 2026 17:14:20 -0700 Subject: [PATCH 2/4] desktop needed to read templates --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c37e060..f21a66e 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.1", "packageManager": "pnpm@10.33.0", "engines": { - "node": ">=22.13.0" + "node": ">=22.12.0" }, "scripts": { "clean": "rm -rf packages/*/dist packages/*/dist-lib packages/*/out packages/audio-analysis-wasm/pkg", From c26f2c10020533411de7b20ec4ac19d501580b07 Mon Sep 17 00:00:00 2001 From: Jack Terwilliger Date: Mon, 13 Jul 2026 18:40:38 -0700 Subject: [PATCH 3/4] github test --- package.json | 2 +- .../tests/template-apply.test.ts | 59 ++++++++++--------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index f21a66e..c37e060 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.1", "packageManager": "pnpm@10.33.0", "engines": { - "node": ">=22.12.0" + "node": ">=22.13.0" }, "scripts": { "clean": "rm -rf packages/*/dist packages/*/dist-lib packages/*/out packages/audio-analysis-wasm/pkg", diff --git a/packages/serialization/tests/template-apply.test.ts b/packages/serialization/tests/template-apply.test.ts index c3a041b..ecdd074 100644 --- a/packages/serialization/tests/template-apply.test.ts +++ b/packages/serialization/tests/template-apply.test.ts @@ -1,22 +1,23 @@ import { describe, it, expect } from 'vitest' -import { parseMMETF, parseMMEAF } from '../src/index.js' +import { parseMMETF, parseMMEAF, emitMMEAF, emitMMETF } from '../src/index.js' import { AnnotationStore } from '@mumo/core' -import { readFileSync } from 'fs' -import { fileURLToPath } from 'url' -import { join, dirname } from 'path' import { buildTemplateMerge } from '../../mumo/src/template-merge.js' -const root = join(dirname(fileURLToPath(import.meta.url)), '../../../') - function makeStore() { return new AnnotationStore() } describe('template apply end-to-end', () => { - it('applies vocab, LT, and pattern schema from template.mmetf onto a transcript-loaded store', () => { - const store = makeStore() - - // Simulate loading transcript.mmeaf (goes through mmeaf importer → loadJSON with full storeData) - const transcriptXml = readFileSync(join(root, 'tmp/transcript.mmeaf'), 'utf8') + it('applies vocab, LT, and pattern schema from template onto a transcript-loaded store', () => { + // Build a transcript store with some LTs and a pattern schema (simulates transcript.mmeaf) + const transcriptStore = makeStore() + transcriptStore.addLinguisticType('default-lt') + transcriptStore.addLinguisticType('symbolic_association', { constraint: 'symbolic_association' }) + const repairSchema = transcriptStore.addPatternSchema({ name: 'repair', slots: [] }) + + // Round-trip through MMEAF serialization (same path as the app's MMEAF importer) + const emptyDoc = { type: 'doc', content: [] } as never + const transcriptXml = emitMMEAF(emptyDoc, transcriptStore) const transcriptResult = parseMMEAF(transcriptXml) + const store = makeStore() store.loadJSON({ annotations: transcriptResult.annotations, tiers: transcriptResult.tiers, @@ -27,30 +28,34 @@ describe('template apply end-to-end', () => { participants: transcriptResult.participants, }) - console.log('store after EAF load:') - console.log(' vocabs:', store.allVocabularies().map(v => v.name)) - console.log(' LTs:', store.allLinguisticTypes().map(lt => lt.name)) - console.log(' patterns:', store.allPatternSchemas().map(s => s.name)) + expect(store.allPatternSchemas().map(s => s.name)).toContain('repair') + + // Build a template store with vocab "test", LT "blah" (referencing vocab), pattern schema "howdy" + const templateStore = makeStore() + const vocab = templateStore.addVocabulary('test', [ + { id: 'e1', value: 'A', description: 'B' }, + { id: 'e2', value: 'C', description: 'D' }, + ]) + templateStore.addLinguisticType('blah', { constraint: 'symbolic_association', vocabularyId: vocab.id }) + templateStore.addPatternSchema({ + name: 'howdy', + slots: [{ id: 's1', name: 'a', anchorKind: 'any', required: true, metrics: [] }], + }) - // Parse template and build merge - const templateXml = readFileSync(join(root, 'tmp/template.mmetf'), 'utf8') + // Round-trip through MMETF serialization (same path as Save Template → Apply Template) + const templateXml = emitMMETF(templateStore) const tmpl = parseMMETF(templateXml) - const merge = buildTemplateMerge(tmpl, store) - - console.log('conflicts:', merge.conflicts) - console.log('preview:', JSON.stringify(merge.preview, null, 2)) - console.log('hasChanges:', merge.preview.some(s => s.items.some(i => i.action !== 'skip'))) // Apply - merge.applyFn(store) + const merge = buildTemplateMerge(tmpl, store) + expect(merge.conflicts).toHaveLength(0) + expect(merge.preview.some(s => s.items.some(i => i.action !== 'skip'))).toBe(true) - console.log('store after apply:') - console.log(' vocabs:', store.allVocabularies().map(v => v.name)) - console.log(' LTs:', store.allLinguisticTypes().map(lt => lt.name)) - console.log(' patterns:', store.allPatternSchemas().map(s => s.name)) + merge.applyFn(store) expect(store.allVocabularies().map(v => v.name)).toContain('test') expect(store.allLinguisticTypes().map(lt => lt.name)).toContain('blah') expect(store.allPatternSchemas().map(s => s.name)).toContain('howdy') + expect(store.allPatternSchemas().map(s => s.name)).toContain('repair') }) }) From 8c77c253d5b170044b9b42156a53e8f8f32e10bf Mon Sep 17 00:00:00 2001 From: Jack Terwilliger Date: Mon, 13 Jul 2026 18:58:47 -0700 Subject: [PATCH 4/4] simplify --- packages/desktop/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 5ab182e..5b13c69 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -15,7 +15,6 @@ "desktopName": "mumo.desktop", "main": "out/main/index.js", "scripts": { - "predev": "node -e \"require('electron')\"", "dev": "electron-vite dev", "build": "electron-vite build", "pack": "electron-vite build && pnpm run rebuild-native && electron-builder --config electron-builder.config.cjs --dir",