inkwell.wiki
The Tool

Most tools give you a score.
This one tells you where you were lied to.

They polished your sentences. Nobody mentioned they said nothing.

The Pipeline — Voice Protected, Structure Flagged
Director Architect (flags structure) Research Wordsmith Sentinel Designer Advocate Final Director (voice check)
The Architect flags structure. It does not reorder your chapters. Final Director checks voice, not chapter order.
Pricing
⭐ Founding 25
Free year
then $9.99/mo locked forever
First 25 only · 40% off standard
Spark
$9.99/mo
10 runs · single writer
Draft
$19.99/mo
30 runs · single writer
Forge
$39.99/mo
Unlimited · priority
Flesch-Kincaid 98. Zero people felt anything. Good score though.
⭐ Claim Founding 25 — 1 year free at launch →
✦ Inkwell GRAND EDITORIAL SUITE
0
words
0 min
read time
0
sentences
0
avg words/sent
grade level
Your Draft
Voice
Seed — topic, opening image, question, anything
Target Word Count
Prior Instructions (optional)
Editor

Finished Piece

Heads up — Inkwell is not a CPA, tax preparer, attorney, financial advisor, or licensed insurance agent. Numbers and recommendations on this site are estimates from data you logged. Prices, tip percentages, mileage deductions, ink valuations, gas prices, venue locations, and earnings are user-submitted or AI-generated and may be inaccurate. Verify with a qualified professional before relying. No CPA-client, attorney-client, or advisory relationship is created by use of this site. By using Inkwell you agree to the Terms. © 2026 G10be — Amarillo, Texas.
'; window.stop(); } })(); // ─── SUPABASE / BETA ─────────────────────── const SUPA_URL = 'https://zmrouoqututfndplboyc.supabase.co'; const SUPA_ANON = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inptcm91b3F1dHV0Zm5kcGxib3ljIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzM0NDgyMDgsImV4cCI6MjA4OTAyNDIwOH0.LlFEregfgfs8wMuogLNCO9811wOF2XssZ0p-lyPds4E'; const WORD_LIMIT = 15000; let userEmail = ''; let wordsUsed = 0; function supaHeaders() { return { 'apikey': SUPA_ANON, 'Authorization': `Bearer ${SUPA_ANON}`, 'Content-Type': 'application/json' }; } async function submitEmail() { const raw = document.getElementById('emailInput').value.trim().toLowerCase(); if (!raw || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(raw)) { showGateErr('Please enter a valid email.'); return; } const btn = document.getElementById('gateBtn'); btn.disabled = true; btn.textContent = 'One moment...'; showGateErr(''); // Save email best-effort — don't block access on failure fetch(`${SUPA_URL}/functions/v1/email-save`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: raw, source: 'thetool' }) }).catch(() => {}); // Notify Lyle fetch('/notify-signup', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Thetool-Key': 'itk-gw-4f8a9c2e7b3d' }, body: JSON.stringify({ email: raw, source: 'thetool' }) }).catch(() => {}); userEmail = raw; wordsUsed = 0; document.getElementById('gate').classList.add('hidden'); document.getElementById('app').classList.add('show'); setTimeout(() => document.getElementById('draftText').focus(), 100); } function showGateErr(msg) { document.getElementById('gateErr').textContent = msg; } async function recordWords(count) { if (!userEmail || count <= 0) return; wordsUsed += count; } function checkWordBudget(incomingWords) { const remaining = WORD_LIMIT - wordsUsed; if (incomingWords > remaining) { const pct = Math.round((wordsUsed / WORD_LIMIT) * 100); toast(`Beta limit: ${wordsUsed.toLocaleString()} / ${WORD_LIMIT.toLocaleString()} words used (${pct}%). Visit inkwell.wiki/join for full access.`); return false; } return true; } // Focus email input on load setTimeout(() => { const el = document.getElementById('emailInput'); if (el) el.focus(); }, 200); // ─── MODE ────────────────────────────────── let currentMode = 'desk'; function setMode(m) { currentMode = m; document.getElementById('modeDesk').classList.toggle('active', m === 'desk'); document.getElementById('modeForge').classList.toggle('active', m === 'forge'); document.getElementById('draftPanel').classList.toggle('hidden', m === 'forge'); document.getElementById('seedMode').style.display = m === 'forge' ? 'block' : 'none'; } // ─── VOICE (FORGE) ───────────────────────── let forgeVoice = 'hst'; document.querySelectorAll('[data-v]').forEach(b => { b.addEventListener('click', () => { document.querySelectorAll('[data-v]').forEach(x => x.classList.remove('active')); b.classList.add('active'); forgeVoice = b.dataset.v; document.getElementById('customVoiceWrap').classList.toggle('hidden', forgeVoice !== 'custom'); }); }); // ─── PRIOR INSTRUCTIONS ──────────────────── function togglePrior() { const m = document.getElementById('priorModal'); m.style.display = m.style.display === 'flex' ? 'none' : 'flex'; } // ─── STATS ───────────────────────────────── let statsTimer = null; function onDraftInput() { clearTimeout(statsTimer); statsTimer = setTimeout(updateStats, 400); } function updateStats(text) { text = text || document.getElementById('draftText').value; const words = text.trim() ? text.trim().split(/\s+/).length : 0; const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 3).length; const readMin = Math.max(1, Math.round(words / 238)); const avg = sentences ? Math.round(words / sentences) : 0; // Flesch–Kincaid grade estimate (simplified) const syllables = text.split(/\s+/).reduce((n, w) => n + countSyllables(w), 0); const grade = sentences && words ? Math.max(1, Math.round(0.39 * (words / Math.max(1,sentences)) + 11.8 * (syllables / Math.max(1,words)) - 15.59)) : 0; flash('statWords', words.toLocaleString()); flash('statRead', readMin + ' min'); flash('statSent', sentences.toLocaleString()); flash('statAvg', avg); flash('statGrade', grade ? 'Grade ' + grade : '—'); } function flash(id, val) { const el = document.getElementById(id); const v = String(val); if (el.textContent !== v) { el.textContent = v; el.classList.add('changed'); setTimeout(() => el.classList.remove('changed'), 600); } } function countSyllables(word) { word = word.toLowerCase().replace(/[^a-z]/g, ''); if (!word) return 0; let count = word.match(/[aeiou]/g)?.length || 0; if (word.endsWith('e') && count > 1) count--; return Math.max(1, count); } function clearDraft() { document.getElementById('draftText').value = ''; updateStats(''); } // ─── PIPELINE DEFINITIONS ────────────────── const PERSONAS = [ { id:'director_brief', icon:'◈', name:'The Director', role:'Grand Literary Vision', color:'var(--director)' }, { id:'architect', icon:'⬡', name:'The Architect', role:'Master Structural Design', color:'var(--architect)' }, { id:'researcher', icon:'⬡', name:'The Researcher',role:'Epic Content & Truth Weaver', color:'var(--research)' }, { id:'advocate', icon:'⬡', name:'The Advocate', role:'Reader Soul & Stakes Guardian', color:'var(--advocate)' }, { id:'wordsmith', icon:'⬡', name:'The Wordsmith', role:'Voice Elevation & Line Majesty', color:'var(--wordsmith)' }, { id:'sentinel', icon:'⬡', name:'The Sentinel', role:'Precision Guardian & Polish', color:'var(--sentinel)' }, { id:'designer', icon:'⬡', name:'The Designer', role:'Grand Format & Presentation', color:'var(--designer)' }, { id:'director_final', icon:'◈', name:'The Director', role:'Final Grand Integrity & Elevation', color:'var(--director)' }, ]; const SYSTEMS = { director_brief: `You are The Director — the senior literary editor who reads a draft first, before any other editor touches it. Your job: identify the grand potential in this writing and craft a MASTER STYLE BRIEF that elevates it toward timeless, publishable literary art. Read cold. Identify the voice, tradition, signature moves, intentional rule-breaks. Envision the grand version: emotionally resonant, structurally masterful, voice unforgettable. Be surgical yet visionary. Be specific. OUTPUT FORMAT: ## STYLE IDENTIFICATION [One paragraph naming the tradition and architecture precisely. Name its grand potential.] ## SIGNATURE MOVES (PROTECT THESE) [Numbered. Quote each example. Explain the craft choice and how it can be made more magnificent.] ## INTENTIONAL RULE-BREAKS (PROTECT THESE) [Numbered. Name the broken convention and why it serves the piece — amplify for grandeur.] ## WATCH LIST [Things that look intentional but may be accidental. Flag for author confirmation. Note grand opportunities.] ## STYLE BRIEF FOR DOWNSTREAM EDITORS [Direct address to the editorial team. What to protect fiercely. What to push for epic impact. What the author is reaching for — and how to help them arrive grandly.] ## INITIAL READ [One honest, ambitious paragraph: what's alive and can be made legendary, what's dead and must be cut for the grand vision.] ## PREPARED DRAFT [Return the draft exactly as received. No edits.]`, architect: `You are The Architect — grand structural designer. You build a masterful, epic framework before the others polish it into a work of lasting power. You have the Style Brief. Work with the style, not against it. Envision and deliver a grand architecture: sweeping through-lines, unforgettable openings/closings, perfect pacing that builds to emotional and intellectual catharsis. CRITICAL — NEVER REORDER: Do not move chapters. Do not shuffle sections. Do not rewrite the draft into Introduction/Buildup/Climax/Conclusion order. Nonlinear, gonzo, in-medias-res, and numbered-chapter sequences stay exactly as the writer left them. If structure is wrong, FLAG it. Do not fix it in the draft. Chapter order as written is law. Evaluate: through-line, opening, ending, section order, pacing, tonal consistency, overall grandeur. Does the structure serve the voice and elevate it to timeless status? ## STRUCTURAL FLAGS [Numbered. Cite the exact passage. Flag any out-of-order sections or flow breaks. Mark [STYLE CONFLICT] for anything that conflicts with the Style Brief. Be ruthless on ordering. Call out missed opportunities for grand scale.] ## RECOMMENDED MOVES [Specific: cut X, move Y before Z, expand W. These are suggestions for the writer. Do not execute them in the draft.] ## STRUCTURAL VERDICT [One paragraph. Call out if order or flow is a problem. Do not claim you fixed it. You did not reorder anything.] ## REVISED DRAFT [Return the draft EXACTLY as received. No reordering. No cuts. No moved sections. Pure original prose.]`, researcher: `You are The Researcher — content editor and fact-checker. You have the Style Brief. In Gonzo and experimental traditions, narrator unreliability IS content. Do not flag it as error. Evaluate: claims needing sources, gaps, unsupported assertions, unlabeled speculation. ## CONTENT FLAGS [Numbered list. For each: quote the exact phrase, then specify type: needs-source / needs-reporting / speculation-unlabeled / narrator-unreliability-check. Reference by paragraph if helpful. Keep all flags HERE in this section only.] ## GAPS IDENTIFIED [What's missing that the reader needs? Be specific.] ## CONTENT VERDICT [One paragraph.] ## REVISED DRAFT [Full clean draft — prose only. Do NOT insert inline flags, markers, or annotations into the draft text. The draft should read exactly as a reader would read it, with all content issues documented above in CONTENT FLAGS instead.]`, advocate: `You are The Advocate — you read for the stranger. Not the writer, not the editor. The person who picked this up cold, knows nothing about the subject, and has no obligation to keep reading. You have the Style Brief. Your job is not to smooth the voice — it's to ask whether the reader has any reason to stay. Evaluate three things only: **OPENING CONTRACT** — Does the first paragraph make a promise to the reader? Does it establish: something is at stake, something is unresolved, or something surprising is happening? If not, where is the first moment that does? **STAKES AUDIT** — What does the reader stand to lose by not finishing this? What question are they waiting to see answered? Is that question ever made explicit, or is it assumed? Mark every place where stakes are implied but never surfaced. **READER ENTRY POINTS** — Places where a cold reader would lose the thread. Unexplained references. Assumed context. Moments where insider knowledge is required. These are not content errors — they are entry failures. OUTPUT FORMAT: ## OPENING CONTRACT [Is the promise made? Where? Quote the moment. If not, what's the first candidate?] ## STAKES AUDIT [Numbered. What's at risk in this piece — for whom, why it matters. Mark each: SURFACED / IMPLIED / MISSING.] ## ENTRY FAILURES [Numbered. Quote the moment. Explain what a cold reader would think. Distinguish between intentional difficulty and accidental opacity.] ## ADVOCATE VERDICT [One honest paragraph: does a stranger have reason to stay?] ## REVISED DRAFT [Full clean draft with only the entry failures and stakes surfacing addressed — do not touch voice, structure, or content beyond this scope.]`, wordsmith: `You are The Wordsmith — line editor. Your job: make the voice MORE itself, not less. You have the Style Brief. Signature moves are protected. Do not flatten them. For Gonzo/HST, check the 10 mechanics: 1. Clinical observation of chaos 2. Declarative pivot 3. Confessional aside (once) 4. Slow zoom 5. Numbers exact (never approximate) 6. Ethical stance through observation 7. Inventory pile 8. Witnessed detail 9. Jazz rhythm 10. Room as cultural document Anti-patterns to kill: Homer voice, false neutrality, abstracted subject, compressed time skip, inspirational close, passive atrocity. ## LINE FLAGS [Numbered. Quote sentence. Name mechanic. Offer alternative. Mark [PROTECTED] if shielded by Style Brief.] ## MECHANICS SCORECARD [Each of 10: DEPLOYED / WEAK / MISSING] ## RHYTHM ANALYSIS [Where it sings. Where it drags.] ## LINE VERDICT [One paragraph.] ## REVISED DRAFT [Full draft. Protected choices remain. Weaknesses addressed.]`, sentinel: `You are The Sentinel — copy editor. Last gate before publication. You have the Style Brief. CRITICAL: fragments, run-ons, unconventional caps, profanity, interrupted thoughts — these are INTENTIONAL in Gonzo writing. Do NOT correct protected choices. Mark [STYLE CHOICE] instead. Check: genuine typos, spelling errors, punctuation consistency, unintentional tense shifts, pronoun agreement, number style consistency. ## COPY FLAGS [Numbered. Quote exact text. Show: "X" → "Y". Mark [STYLE CHOICE] for intentional choices.] ## CONSISTENCY NOTES [Style choices that need standardizing across the piece.] ## REVISED DRAFT [Full corrected draft. Style choices preserved.]`, designer: `You are The Designer — format editor. Format serves the voice. You have the Style Brief. A Gonzo dispatch has different format needs than an academic essay. Match the conventions to the identified tradition. Check: headers, section breaks, walls of text, dateline presence, artifact block formatting, paragraph length variation. ## FORMAT FLAGS [Numbered. Problem and fix.] ## STRUCTURE RECOMMENDATIONS [Specific changes to document architecture.] ## REVISED DRAFT [Full draft with formatting applied. Use markdown.]`, director_final: `You are The Director — you read this piece first and wrote the grand Style Brief. You will receive TWO drafts: the raw original and the final edited version. Compare them directly. Previous versions sanded voice flat and shuffled chapters. This time protect the writer's order. NON-NEGOTIABLE: DO NOT REORDER CHAPTERS OR SECTIONS. Flag flow problems in the report. Restore anything incorrectly flattened. Keep the writer's sequence. Deliver the strongest version that is still the book they wrote. ## VOICE DELTA [Compare raw to final. For each Signature Move from your Style Brief: STRENGTHENED / INTACT / WEAKENED / LOST. Quote evidence from both versions. Note any grand elevations.] ## EDITORIAL DAMAGE REPORT [What did editors flatten, over-correct, or homogenize? Quote the raw original and the final version side by side. Specifically call out any remaining out-of-order sections, confusing flow, or lost grandeur. Be specific and merciless.] ## GENUINE IMPROVEMENTS [What did the pipeline actually fix? Quote before/after. Credit the wins. Note any structural or visionary fixes that elevated it.] ## WHAT'S STILL DEAD [What the pipeline didn't fix — and why it matters for the grand vision. These are the remaining problems. Flag any order or coherence issues that remain, and what would make it legendary.] ## FINAL VERDICT [One honest, ambitious paragraph: is this better or worse than what came in? Is it ready for publication as a grand work? Was structure and flow fixed and elevated? What's the single remaining thing it needs to achieve masterpiece status?] ## FINAL DRAFT [The finished piece — restore anything incorrectly flattened. Do not reorder chapters or sections. Lock in genuine improvements. Keep the writer's sequence.]` }; const VOICE_DEFS = { hst: `You write in the pure Gonzo voice — Hunter S. Thompson architecture, fully deployed. CORE EQUATION: EVENT + ECONOMIC REALITY + SPECIFIC HUMAN DETAIL + EXACT NUMBER = TRUTH 5-LAYER STRUCTURE: L1 ARRIVAL: location + time + one sensory detail. No proper nouns except place. Reader smells/hears something by sentence three. L2 INTAKE: Institution named + annual revenue. One exact financial figure. One specific human: name, age, city, what they gave up. L3 FIELD REPORT: Present tense throughout. Clock visible. Wide/close alternation. One exact number per paragraph. Inventory pile once per 500 words. One witnessed detail. L4 ARTIFACT: Dateline + number + pull quote + verdict. Works standalone. L5 CONVERSATION: Name the structural cause. Do not resolve it. Stop. 10 MECHANICS: Clinical chaos observation. Declarative pivot. Confessional aside (once). Slow zoom. Numbers as credibility. Ethical stance via observation. Inventory pile. Witnessed detail. Jazz rhythm. Room as cultural document. ANTI-PATTERNS — KILL: Homer voice. False neutrality. Abstracted subject. Compressed time skip. Inspirational close. Passive atrocity. Profanity = punctuation. CAPS = screaming. Italics = emphasis. Fragments = where the thought crashes. DO NOT SANITIZE.`, uplift: `You write in the Amarillo Uplift voice — Jesse Welles storytelling meets gonzo prose mechanics on verified Panhandle research. - Sentences alternate: short declarative → long rolling → short. Jazz, not metronome. - Delayed subjects: "In the canyon, where the red walls catch the last hour of sun and hold it like a debt, the horses appeared." - Register mixing: academic precision next to working-class plainness. - Inventory lists: specific nouns piled. Not "supplies" but "a spool of barbed wire, a Bible cracked at Ecclesiastes, and a Winchester repeater." - Warm but not sentimental. Precise but not academic. Funny when earned, never forced. - Respects the land and the people who were here first. - BANNED: tapestry, rich history, nestled, boasts, vibrant, iconic, time-honored, legendary (unless earned), hidden gem, little-known, few people know.` }; // ─── PIPELINE STATE ──────────────────────── let pipelineStages = []; // ordered list of stage ids to run let currentStageIdx = -1; let currentDraft = ''; let rawDraft = ''; let styleBrief = ''; let allDrafts = {}; let chatHistory = {}; // stageId → [{role, content}] let stageResults = {}; // stageId → full text let pipelineRunning = false; // ─── PIPELINE BAR ────────────────────────── function buildPipeBar(stages) { const bar = document.getElementById('pipebar'); bar.innerHTML = ''; stages.forEach((sid, i) => { const p = PERSONAS.find(x => x.id === sid); if (!p) return; if (i > 0) { const conn = document.createElement('div'); conn.className = 'pipe-connector'; bar.appendChild(conn); } const el = document.createElement('div'); el.className = 'pipe-stage'; el.id = 'pipe-' + sid; el.innerHTML = `
${p.icon}
${p.name}
`; bar.appendChild(el); }); } function setPipeActive(sid) { document.querySelectorAll('.pipe-stage').forEach(el => { el.classList.remove('active'); }); const el = document.getElementById('pipe-' + sid); if (el) el.classList.add('active'); } function setPipeDone(sid) { const el = document.getElementById('pipe-' + sid); if (el) { el.classList.remove('active'); el.classList.add('done'); } } // ─── EDITOR PANEL ────────────────────────── function openEditorPanel(stageId) { const p = PERSONAS.find(x => x.id === stageId); if (!p) return; document.getElementById('epIcon').textContent = p.icon; document.getElementById('epIcon').style.color = p.color; document.getElementById('epIcon').style.borderColor = p.color; document.getElementById('epName').textContent = p.name; document.getElementById('epName').style.color = p.color; document.getElementById('epRole').textContent = p.role; // style chat input border to match editor document.getElementById('epPanel_color_target')?.style; document.querySelector('.ep-chat-input textarea').style.setProperty('--editor-color', p.color); document.getElementById('editorPanel').classList.add('open'); document.getElementById('draftPanel').classList.add('shifted'); // init chat history if (!chatHistory[stageId]) chatHistory[stageId] = []; renderChat(stageId); switchTab('report'); } function closeEditorPanel() { document.getElementById('editorPanel').classList.remove('open'); document.getElementById('draftPanel').classList.remove('shifted'); } function switchTab(tab) { document.getElementById('tabReport').classList.toggle('active', tab === 'report'); document.getElementById('tabChat').classList.toggle('active', tab === 'chat'); document.getElementById('paneReport').classList.toggle('active', tab === 'report'); document.getElementById('paneChat').classList.toggle('active', tab === 'chat'); if (tab === 'chat') document.getElementById('chatMessages').scrollTop = 9999; } // ─── CHAT ────────────────────────────────── function renderChat(stageId) { const msgs = chatHistory[stageId] || []; const container = document.getElementById('chatMessages'); container.innerHTML = ''; if (msgs.length === 0) { container.innerHTML = '
Ask questions, push back on flags,
defend your choices.
'; return; } msgs.forEach(m => { const isUser = m.role === 'user'; const el = document.createElement('div'); el.className = 'chat-msg ' + (isUser ? 'user' : 'editor'); const p = PERSONAS.find(x => x.id === currentStageId); el.innerHTML = `
${isUser ? 'You' : (p?.name || 'Editor')}
${escHtml(m.content)}
`; container.appendChild(el); }); container.scrollTop = container.scrollHeight; } function escHtml(s) { return s.replace(/&/g,'&').replace(//g,'>'); } function chatKeydown(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChat(); } } let currentStageId = ''; async function sendChat() { const inp = document.getElementById('chatInput'); const msg = inp.value.trim(); if (!msg || !currentStageId) return; inp.value = ''; if (!chatHistory[currentStageId]) chatHistory[currentStageId] = []; chatHistory[currentStageId].push({role:'user', content: msg}); renderChat(currentStageId); const p = PERSONAS.find(x => x.id === currentStageId); const stageResult = stageResults[currentStageId] || ''; const brief = styleBrief; const sysPrompt = `You are ${p.name} — ${p.role}. You just reviewed a draft and produced an editorial report. The writer is now discussing your feedback with you. Your editorial report was: ${stageResult.slice(0, 3000)} ${brief ? `\nStyle Brief context:\n${brief.slice(0,1000)}` : ''} Engage directly with the writer's question or pushback. Be specific. If they're defending a choice, evaluate whether their argument holds. If they're asking why you flagged something, explain precisely. You are a collaborator, not an authority.`; const messages = chatHistory[currentStageId].map(m => ({role: m.role === 'user' ? 'user' : 'assistant', content: m.content})); try { const responseText = await streamToElement(sysPrompt, messages, 'paneChat', true); chatHistory[currentStageId].push({role:'assistant', content: responseText}); renderChat(currentStageId); } catch(e) { toast('Chat error: ' + e.message.slice(0,60)); } } // ─── CLAUDE API ──────────────────────────── async function callClaude(system, messages, onToken) { const body = { model: 'claude-opus-4-6', max_tokens: 8192, stream: true, system, messages }; const resp = await fetch('/thetool-api', { method: 'POST', headers: {'Content-Type': 'application/json', 'X-Thetool-Key': 'itk-gw-4f8a9c2e7b3d'}, body: JSON.stringify(body) }); if (!resp.ok) throw new Error('API ' + resp.status); let full = ''; const reader = resp.body.getReader(); const dec = new TextDecoder(); let buf = ''; while (true) { const {done, value} = await reader.read(); if (done) break; buf += dec.decode(value, {stream:true}); const lines = buf.split('\n'); buf = lines.pop(); for (const line of lines) { if (!line.startsWith('data: ')) continue; const d = line.slice(6).trim(); if (d === '[DONE]') continue; try { const ev = JSON.parse(d); if (ev.type === 'content_block_delta' && ev.delta?.type === 'text_delta') { full += ev.delta.text; onToken(ev.delta.text, full); } } catch {} } } return full; } async function streamToElement(system, messages, targetId, isChat) { if (isChat) { // stream to a temp element then render let acc = ''; const tmp = document.createElement('div'); tmp.className = 'chat-msg editor'; const from = document.createElement('div'); from.className = 'chat-from'; from.textContent = PERSONAS.find(x=>x.id===currentStageId)?.name || 'Editor'; const bubble = document.createElement('div'); bubble.className = 'chat-bubble typing'; tmp.appendChild(from); tmp.appendChild(bubble); document.getElementById('chatMessages').appendChild(tmp); document.getElementById('chatMessages').scrollTop = 9999; const result = await callClaude(system, messages, (tok, full) => { bubble.textContent = full; bubble.classList.add('typing'); document.getElementById('chatMessages').scrollTop = 9999; }); bubble.classList.remove('typing'); tmp.remove(); // will be re-rendered by renderChat return result; } } // ─── EXTRACT HELPERS ─────────────────────── function extractSection(text, ...markers) { for (const m of markers) { const idx = text.indexOf(m); if (idx === -1) continue; const after = text.slice(idx + m.length); const next = after.search(/\n## /); const section = next === -1 ? after : after.slice(0, next); if (section.trim().length > 200) return section.trim(); } // Fallback for known errors where LLM varies headers: take the largest trailing prose block const lines = text.split('\n'); let best = ''; let current = ''; for (let i = lines.length-1; i>=0; i--) { if (lines[i].trim().startsWith('## ')) { if (current.trim().length > best.length) best = current; current = ''; } else { current = lines[i] + '\n' + current; } } if (current.trim().length > best.length) best = current; if (best.trim().length > 200) return best.trim(); return null; } function extractStyleBrief(text) { return extractSection(text, '## STYLE BRIEF FOR DOWNSTREAM EDITORS\n', '## STYLE BRIEF FOR DOWNSTREAM EDITORS' ) || ''; } function extractDraft(text) { return extractSection(text, '## REVISED DRAFT\n', '## REVISED DRAFT:', '## FINAL DRAFT\n', '## FINAL DRAFT:', '## PREPARED DRAFT\n', '## PREPARED DRAFT:' ); } // Simple heuristic to catch known order/flow errors function validateLogicalOrder(draft) { if (!draft || draft.trim().length < 150) return { ok: true, issues: [] }; const lower = draft.toLowerCase(); const issues = []; // Conclusion or resolution before any real setup const conclusionEarly = /conclusion|resolution|the end|epilogue/.test(lower.split(/setup|beginning|introduction|chapter 1|the start/)[0] || ''); if (conclusionEarly) issues.push('Conclusion/resolution appears before setup'); // Climax before setup const climaxEarly = /climax|final battle|turning point/.test(lower.split(/setup|beginning|introduction|chapter 1/)[0] || ''); if (climaxEarly) issues.push('Climax appears before setup'); // Chapter numbers decreasing const chapterMatches = [...lower.matchAll(/chapter\s+(\d+)/g)]; if (chapterMatches.length > 1) { let last = 0; for (const m of chapterMatches) { const num = parseInt(m[1]); if (num < last) { issues.push('Chapters out of numeric sequence'); break; } last = num; } } // Abrupt topic jumps (heuristic) const sentences = draft.split(/[.!?]+/).filter(s => s.trim().length > 15); let jumps = 0; for (let i = 1; i < Math.min(sentences.length, 20); i++) { const p = sentences[i-1].toLowerCase(), c = sentences[i].toLowerCase(); if ((p.includes('battle') || p.includes('dragon')) && (c.includes('village') || c.includes('daily life'))) jumps++; if ((p.includes('king') || p.includes('throne')) && (c.includes('modern') || c.includes('today'))) jumps++; } if (jumps >= 2) issues.push('Multiple unexplained topic jumps that break the reader thread'); return { ok: issues.length === 0, issues }; } async function enforceCoherence(draft, brief, prior) { // Flag-only. Never reorder. Architect used to shuffle chapters. return draft; } // ─── FORMAT REPORT ───────────────────────── function formatReport(text) { // Add visual styling to section headers and flags return text .replace(/^## (.+)$/gm, '
$1
') .replace(/\[PROTECTED[^\]]*\]/g, '$&') .replace(/\[STYLE CHOICE[^\]]*\]/g, '$&') .replace(/\[CONTENT FLAG[^\]]*\]/g, '$&') .replace(/\[STYLE CONFLICT[^\]]*\]/g, '$&'); } // ─── RUN STAGE ───────────────────────────── async function runStage(stageId, draft, brief, prior) { currentStageId = stageId; setPipeActive(stageId); const p = PERSONAS.find(x => x.id === stageId); const sys = SYSTEMS[stageId]; const priorBlock = prior ? `\nWRITER'S PRIOR INSTRUCTIONS (protect these):\n${prior}\n` : ''; const briefBlock = brief ? `\nSTYLE BRIEF:\n${brief}\n` : ''; const fullSys = sys + priorBlock + briefBlock; let userMsg; if (stageId === 'director_brief') { userMsg = `Read this draft cold and produce your Style Brief:\n\n${draft}`; } else if (stageId === 'director_final') { userMsg = `RAW ORIGINAL DRAFT (what came in before editing):\n\n${rawDraft || draft}\n\n${'─'.repeat(60)}\n\nFINAL EDITED DRAFT (after all editors):\n\n${draft}\n\nYour Style Brief is attached. Perform your final integrity check now — compare both drafts directly.`; } else { userMsg = `Draft to edit:\n\n${draft}\n\nApply your full editorial pass now.`; } const reportEl = document.getElementById('reportText'); reportEl.innerHTML = ''; reportEl.classList.add('typing'); // open panel openEditorPanel(stageId); let fullText = ''; await callClaude(fullSys, [{role:'user', content: userMsg}], (tok, full) => { fullText = full; reportEl.innerHTML = formatReport(full); reportEl.scrollTop = reportEl.scrollHeight; }); reportEl.classList.remove('typing'); stageResults[stageId] = fullText; allDrafts[stageId] = fullText; // update stats if draft changed const newDraft = extractDraft(fullText); if (newDraft && stageId !== 'architect') currentDraft = newDraft; if (stageId === 'director_brief') { styleBrief = extractStyleBrief(fullText); } if (stageId === 'architect') { currentDraft = draft; allDrafts[stageId + '_enforced'] = currentDraft; } setPipeDone(stageId); return fullText; } // ─── PROCEED TO NEXT ─────────────────────── async function proceedToNext() { // update draft from textarea before proceeding currentDraft = document.getElementById('draftText').value || currentDraft; // Architect no longer reorders. Leave the writer's sequence alone. closeEditorPanel(); currentStageIdx++; if (currentStageIdx >= pipelineStages.length) { // pipeline complete showFinal(); return; } const nextId = pipelineStages[currentStageIdx]; const prior = document.getElementById('priorText').value.trim(); try { if (nextId === 'director_final') { // Do not reorder before Final Director. } await runStage(nextId, currentDraft, styleBrief, prior); // update draft textarea with extracted draft if (currentDraft) document.getElementById('draftText').value = currentDraft; updateStats(currentDraft); } catch(e) { toast('Error: ' + e.message.slice(0,80)); pipelineRunning = false; } } // ─── START PIPELINE (DESK MODE) ──────────── async function startPipeline() { const draft = document.getElementById('draftText').value.trim(); if (!draft) { toast('Add your draft first.'); return; } if (pipelineRunning) return; const draftWordCount = draft.trim().split(/\s+/).length; if (!checkWordBudget(draftWordCount)) return; pipelineRunning = true; currentDraft = draft; rawDraft = draft; allDrafts = {}; stageResults = {}; chatHistory = {}; styleBrief = ''; recordWords(draftWordCount); pipelineStages = ['director_brief','architect','researcher','advocate','wordsmith','sentinel','designer','director_final']; buildPipeBar(pipelineStages); currentStageIdx = 0; const prior = document.getElementById('priorText').value.trim(); document.getElementById('startBtn').disabled = true; document.getElementById('nextEditorBtn').textContent = 'Next Editor →'; try { const sid = pipelineStages[0]; await runStage(sid, currentDraft, '', prior); if (currentDraft) document.getElementById('draftText').value = currentDraft; updateStats(currentDraft); } catch(e) { toast('Error: ' + e.message.slice(0,80)); pipelineRunning = false; } document.getElementById('startBtn').disabled = false; } // ─── THE FORGE (WRITE FROM SEED) ─────────── async function runForge() { const seed = document.getElementById('seedInput').value.trim(); if (!seed) { toast('Add a seed.'); return; } const wordTarget = parseInt(document.getElementById('wordTarget').value) || 2000; const voice = forgeVoice === 'custom' ? document.getElementById('customVoice').value : VOICE_DEFS[forgeVoice] || VOICE_DEFS.hst; const prior = document.getElementById('forgePrior').value.trim(); const priorBlock = prior ? `\nWRITER'S PRIOR INSTRUCTIONS:\n${prior}\n` : ''; if (!checkWordBudget(wordTarget)) return; document.getElementById('forgeRunBtn').disabled = true; setMode('desk'); allDrafts = {}; stageResults = {}; chatHistory = {}; styleBrief = ''; recordWords(wordTarget); pipelineStages = ['director_brief','architect','researcher','advocate','wordsmith','sentinel','designer','director_final']; buildPipeBar(['outline_gen','draft_write',...pipelineStages]); // show draft panel document.getElementById('draftTitle').textContent = 'Generating...'; document.getElementById('draftText').value = ''; document.getElementById('draftText').placeholder = ''; try { // Step 1: Outline setPipeActive('outline_gen'); const outlineSys = `You are a master narrative nonfiction outline architect.\n\n${voice}\n\nProduce a detailed scene-by-scene outline for approximately ${wordTarget} words. Working title, lede concept, section-by-section breakdown (heading, what happens, key facts/scenes/characters, approx word count per section), closing concept, total estimated word count. Be specific — name the exact scene, the exact quote, the exact fact.${priorBlock}`; let outline = ''; document.getElementById('draftText').value = 'Building outline...\n\n'; await callClaude(outlineSys, [{role:'user', content:`SEED: ${seed}\nTARGET: approximately ${wordTarget} words\n\nGenerate the full outline now.`}], (tok, full) => { outline = full; document.getElementById('draftText').value = '// OUTLINE\n\n' + full; }); allDrafts['outline'] = outline; setPipeDone('outline_gen'); // Step 2: Draft setPipeActive('draft_write'); const draftSys = `You are a master narrative nonfiction writer.\n\n${voice}\n\nWrite the FULL DRAFT from the outline. Do not summarize. Do not skip sections. Every scene, every paragraph, every sentence. Complete and fully realized.${priorBlock}`; let draft = ''; document.getElementById('draftText').value = 'Writing draft...\n\n'; await callClaude(draftSys, [{role:'user', content:`OUTLINE:\n${outline}\n\nWrite the complete draft now. All sections. Full prose. No placeholders.`}], (tok, full) => { draft = full; document.getElementById('draftText').value = full; document.getElementById('draftText').scrollTop = document.getElementById('draftText').scrollHeight; }); allDrafts['raw_draft'] = draft; currentDraft = draft; rawDraft = draft; setPipeDone('draft_write'); document.getElementById('draftTitle').textContent = 'Draft — ready for editorial pipeline'; updateStats(draft); // Now run full editorial pipeline pipelineRunning = true; currentStageIdx = 0; const sid = pipelineStages[0]; await runStage(sid, currentDraft, '', prior); updateStats(currentDraft); } catch(e) { toast('Error: ' + e.message.slice(0,80)); } document.getElementById('forgeRunBtn').disabled = false; } // ─── FINAL ───────────────────────────────── function showFinal() { pipelineRunning = false; const finalResult = stageResults['director_final'] || ''; const finalDraft = extractDraft(finalResult) || currentDraft; allDrafts['FINAL'] = finalDraft; document.getElementById('finalText').textContent = finalDraft; document.getElementById('finalOverlay').classList.add('show'); closeEditorPanel(); toast('Pipeline complete.'); } function copyFinal() { navigator.clipboard.writeText(document.getElementById('finalText').textContent) .then(() => toast('Copied to clipboard.')); } function downloadAll() { let bundle = '═'.repeat(60) + '\nINKWELL — ALL DRAFTS\n' + '═'.repeat(60) + '\n\n'; for (const [name, content] of Object.entries(allDrafts)) { bundle += '═'.repeat(60) + '\n' + name.toUpperCase().replace(/_/g,' ') + '\n' + '═'.repeat(60) + '\n\n' + content + '\n\n'; } const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([bundle], {type:'text/plain'})); a.download = 'inkwell_' + Date.now() + '.txt'; a.click(); } // ─── TOAST ───────────────────────────────── let toastTimer; function toast(msg) { const el = document.getElementById('toast'); el.textContent = msg; el.classList.add('show'); clearTimeout(toastTimer); toastTimer = setTimeout(() => el.classList.remove('show'), 3000); } // ─── INIT ────────────────────────────────── buildPipeBar([]);
Legal