Conversation
… insertion, UX improvements - Rich LLM system prompt with few-shot examples for #tags, key:: value, **bold**/*italic*, and - TODO action item detection - Long notes (>=20 words, configurable) create a dedicated transcript page Voice Note YYYY-MM-DD HH:mm:ss with source:: backlink; short notes inline - Voice notes insert into the currently-open page, falling back to journal - Removed 10-word minimum word-count gate - Android SpeechRecognizer silence timeout: 3s->6s complete, 1.5s->3s partial, +2s minimum so users can pause to think - includeRawTranscript toggle controls #+BEGIN_QUOTE on transcript page - transcriptPageWordThreshold setting configurable from settings UI Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JVM Load Benchmark (Desktop)Synthetic in-memory benchmark measuring load performance for the desktop (JVM) app.
Flamegraphs (this PR)**Allocation** — object allocation pressure (JDBC/SQLite churn)Alloc flamegraph not available CPU — method-level hotspots by on-CPU time CPU flamegraph not available Top SQL queries by total time (this PR)| table:operation | calls | p50 | p99 | max | total | |-----------------|-------|-----|-----|-----|-------| | `pages:select` | 2 | 1ms | 1ms | 1ms | 1ms |Top allocation hotspots (this PR)`65.6%` byte[]_[k] `6.2%` java.lang.String_[k] `2.6%` jdk.internal.org.objectweb.asm.SymbolTable$Entry_[k] `2.6%` java.lang.Object[]_[k] `2.1%` int[]_[k]Top CPU hotspots (this PR)`99.5%` /usr/lib/x86_64-linux-gnu/libc.so.6 `0%` java/lang/invoke/AbstractValidatingLambdaMetafactory._[0] `0%` java/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater.get_[1] `0%` dev/stapler/stelekit/db/DatabaseWriteActor$1.invokeSuspend_[0] `0%` /tmp/sqlite-3.51.3.0-5ac9c2f9-f0c9-4a67-a3b4-487f183c4e85-libsqlitejdbc.so |
…silence until explicit stop Replace the single-shot listen loop with an accumulating restart loop. When the recognizer stops due to silence (ERROR_NO_MATCH / ERROR_SPEECH_TIMEOUT) it restarts automatically and appends the new text to the accumulated transcript. The coroutine only resolves when stopListening() is called explicitly — setting stopRequested=true causes the next onResults/onError to return the full text rather than restarting. If stopListening() is called between cycles (activeRecognizer==null) the next startCycle() sees stopRequested=true and resolves immediately. Also add project_plans/voice/ spec artifacts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds richer voice-note formatting and improves how/where voice transcripts are inserted in the graph, including optional transcript pages for longer notes and better capture UX.
Changes:
- Expands the default LLM system prompt to encourage richer Logseq-friendly formatting (tags, properties, emphasis, TODO/DONE).
- Adds transcript-page creation for longer notes, with optional raw-transcript quoting and a configurable word threshold.
- Routes voice-note insertion to the currently open page (fallback to today’s journal) and tweaks Android SpeechRecognizer silence timing.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/voice/VoiceSettings.kt | Persists new voice settings for raw transcript inclusion + transcript-page threshold. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/voice/VoicePipelineFactory.kt | Threads new settings into VoicePipelineConfig. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/voice/VoicePipelineConfig.kt | Updates prompt and adds config fields for transcript-page behavior. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/voice/VoiceCaptureViewModel.kt | Implements current-page insertion and transcript-page creation + block formatting helpers. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/components/settings/VoiceCaptureSettings.kt | Adds UI controls for raw transcript toggle + word-threshold input. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/ui/App.kt | Wires current open page UUID into the voice capture VM. |
| kmp/src/commonMain/kotlin/dev/stapler/stelekit/repository/JournalService.kt | Adds appendToPage, transcript page creation, and UUID→page name lookup helpers. |
| kmp/src/businessTest/kotlin/dev/stapler/stelekit/voice/VoiceSettingsTest.kt | Adds tests for the new includeRawTranscript setting. |
| kmp/src/businessTest/kotlin/dev/stapler/stelekit/voice/VoiceNoteBlockFormatTest.kt | Updates tests for new inline/transcript-page block formats and raw-transcript toggling. |
| kmp/src/businessTest/kotlin/dev/stapler/stelekit/voice/VoiceCaptureViewModelTest.kt | Updates routing tests and removes the old minimum-word gate expectations. |
| kmp/src/androidMain/kotlin/dev/stapler/stelekit/voice/AndroidSpeechRecognizerProvider.kt | Adjusts silence/timeout extras to allow longer pauses. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| val wordCount = formattedText.split(Regex("\\s+")).count { it.isNotBlank() } | ||
| val useTranscriptPage = wordCount >= pipeline.transcriptPageWordThreshold | ||
|
|
| @@ -136,27 +131,93 @@ class VoiceCaptureViewModel( | |||
| } | |||
| platformSettings.getString(KEY_TRANSCRIPT_PAGE_WORD_THRESHOLD, "20").toIntOrNull() ?: 20 | ||
|
|
||
| fun setTranscriptPageWordThreshold(threshold: Int) = | ||
| platformSettings.putString(KEY_TRANSCRIPT_PAGE_WORD_THRESHOLD, threshold.toString()) | ||
|
|
||
| companion object { |
| OutlinedTextField( | ||
| value = transcriptPageWordThreshold, | ||
| onValueChange = { transcriptPageWordThreshold = it; saved = false }, | ||
| label = { Text("Create transcript page after N words") }, | ||
| singleLine = true, | ||
| keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), | ||
| modifier = Modifier.fillMaxWidth().padding(top = 8.dp), | ||
| ) |
| suspend fun appendToPage(pageUuid: String, content: String) { | ||
| val page = pageRepository.getPageByUuid(pageUuid).first().getOrNull() | ||
| if (page == null) { | ||
| appendToToday(content) | ||
| return | ||
| } | ||
| val blocks = blockRepository.getBlocksForPage(page.uuid).first().getOrNull() ?: emptyList() | ||
| val nextPosition = (blocks.maxOfOrNull { it.position } ?: -1) + 1 | ||
| val newBlock = Block( | ||
| uuid = UuidGenerator.generateV7(), | ||
| pageUuid = page.uuid, | ||
| content = content, | ||
| position = nextPosition, | ||
| createdAt = Clock.System.now(), | ||
| updatedAt = Clock.System.now(), | ||
| ) | ||
| if (writeActor != null) { | ||
| writeActor.saveBlock(newBlock) | ||
| } else { | ||
| blockRepository.saveBlock(newBlock) | ||
| } |
|
|
||
| // --- includeRawTranscript --- | ||
|
|
||
| @Test | ||
| fun `getIncludeRawTranscript_should_return_true_by_default`() { | ||
| val settings = VoiceSettings(MapSettings()) | ||
| assertTrue(settings.getIncludeRawTranscript(), "Default should be true") | ||
| } | ||
|
|
||
| @Test | ||
| fun `setIncludeRawTranscript_should_persist_value_across_get_calls`() { | ||
| val settings = VoiceSettings(MapSettings()) | ||
| settings.setIncludeRawTranscript(false) | ||
| assertFalse(settings.getIncludeRawTranscript(), "Expected persisted false value") | ||
| settings.setIncludeRawTranscript(true) | ||
| assertTrue(settings.getIncludeRawTranscript(), "Expected persisted true value after re-setting to true") | ||
| } |
Android Load BenchmarkInstrumented benchmark on an API 30 x86_64 emulator — 500-page synthetic graph. Comparing Graph Load
Interactive Write Latency (during Phase 3)
SAF I/O Overhead (ContentProvider vs direct File read)Measures Binder IPC cost added by ContentResolver per readFile() call.
|
…cle() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use rawTranscript (not formattedText) for transcript page word-count threshold decision — LLM expansion/compression no longer skews routing - Replace println with Logger for LLM failure warning - Clamp transcriptPageWordThreshold to >= 1 in getter, setter, and UI save - Extract appendBlockToPage() private helper shared by appendToToday/appendToPage - Add VoiceSettingsTest coverage for threshold default, persistence, and clamping Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
#tags,key:: valueproperties,**bold**/*italic*emphasis, and- TODOaction items from voice transcriptsVoice Note YYYY-MM-DD HH:mm:sspage withsource::backlink, formatted content, and optional raw transcript; short notes stay inlineWhat changes
New block format (inline, in journal or current page)
New transcript page (
Voice Note 2026-05-02 14:35:22)Short notes (< threshold) stay fully inline — no separate page:
Test plan
includeRawTranscripttoggle tests on transcript page contentVoice Note ...page with wikilink in journal#+BEGIN_QUOTEFuture work
project_plans/voice/requirements.md)🤖 Generated with Claude Code