Phase 5: voice consistency pass
Tier-1 voice-consistency pass: whole-document LLM review surfacing passages that read tonally out of place (formal/over-polished/paraphrased-too-closely), as honey-decorated `voice` flags with no correction (awareness-only). - internal/llm/voice.go: RunVoice sends the whole document (no TruncateDoc), MaxTokens 2048, 20s per-doc floor (VoiceInterval). Standalone voice prompt in prompts.go (not bundled with the grammar checkpoint, per spec). - internal/suggestions: POST /api/docs/:id/voice. replacePending is now family-scoped (pendingScope) so grammar and voice never clobber each other's pending flags; both passes return the unified pending set. check/voice share one runPass helper. TestVoicePassCoexists covers both directions. - Frontend: api.voiceDoc, useCheckpoint voicing/runVoice, honey "Check my voice" toolbar pill, breathing honey dot in StatusBar. Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
This commit is contained in:
@@ -3,6 +3,9 @@ import { useEditorState } from '@tiptap/react'
|
||||
|
||||
interface Props {
|
||||
editor: Editor | null
|
||||
// Runs the whole-document voice-consistency pass; `voicing` shows its progress.
|
||||
onVoiceCheck: () => void
|
||||
voicing: boolean
|
||||
}
|
||||
|
||||
// A formatting button. `active` gets the rose pill treatment so the writer can
|
||||
@@ -47,7 +50,7 @@ const Divider = () => (
|
||||
// Toolbar renders inline formatting controls bound to the live Tiptap editor.
|
||||
// useEditorState subscribes to just the flags it reads, so the buttons reflect
|
||||
// the current selection without re-rendering the whole tree on every keystroke.
|
||||
export function Toolbar({ editor }: Props) {
|
||||
export function Toolbar({ editor, onVoiceCheck, voicing }: Props) {
|
||||
const state = useEditorState({
|
||||
editor,
|
||||
selector: ({ editor }) =>
|
||||
@@ -134,6 +137,28 @@ export function Toolbar({ editor }: Props) {
|
||||
>
|
||||
⇥
|
||||
</TBtn>
|
||||
<Divider />
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Check my voice"
|
||||
disabled={voicing}
|
||||
onMouseDown={(e) => e.preventDefault()} // keep editor selection
|
||||
onClick={onVoiceCheck}
|
||||
className="ml-0.5 inline-flex h-8 items-center gap-1.5 whitespace-nowrap px-3 text-xs font-bold transition-colors disabled:opacity-70"
|
||||
style={{
|
||||
borderRadius: 'var(--radius-pill)',
|
||||
color: 'var(--color-plum)',
|
||||
background: 'var(--color-honey)',
|
||||
}}
|
||||
title="Read the whole document for passages that don't sound like you"
|
||||
>
|
||||
<span
|
||||
className={voicing ? 'petal-checkpoint-dot inline-block h-2 w-2 rounded-full' : 'hidden'}
|
||||
style={{ background: 'var(--color-plum)' }}
|
||||
aria-hidden
|
||||
/>
|
||||
{voicing ? 'Reading…' : 'Check my voice 🍯'}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user