Author's Sparring Partner Author's Sparring Partner
A local-first writing companion built for serious authors, not generic prompts.Ein Local-First-Schreibbegleiter für ernsthafte Autoren statt generischer Prompts.
Conversation-first authoring product with a macOS host app, iPhone companion, local SQLite persistence, retrieval-driven context, WhisperKit speech input, Piper voice output, and a Flutter + Supabase rebuild path. Conversation-First-Autorenprodukt mit macOS-Host-App, iPhone-Companion, lokaler SQLite-Persistenz, retrieval-getriebenem Kontext, WhisperKit-Spracherkennung, Piper-Ausgabe und Flutter-plus-Supabase-Rebuild.
// challenge // ausgangslage
Most AI writing tools still feel like chat boxes stapled onto document editors. The challenge here was different: build a serious creative workstation for authors where conversation, memory, chapter context, and voice all work together without forcing writers into a cloud-first workflow. Die meisten KI-Schreibtools fühlen sich immer noch wie Chatfenster an, die an Editoren geklebt wurden. Die Herausforderung hier war anders: eine ernsthafte kreative Workstation für Autoren bauen, in der Gespräch, Memory, Kapitelkontext und Stimme zusammenspielen, ohne Autoren in einen Cloud-First-Workflow zu zwingen.
// approach // vorgehen
The product was shaped around conversation first, not editor chrome. Writers move through projects and chapters in a sidebar, keep the active transcript at the center, and pull lore, memory, and system context from a dedicated side panel. Under the hood, the stack stays intentionally local-first: SQLite for projects, chapters, turns, and memory; retrieval-driven prompt assembly instead of prompt stuffing; WhisperKit for speech-to-text; Piper for voice output; MLX + Qwen as the default runtime. In parallel, the Flutter + Supabase rebuild maps the same product into a synced cross-device architecture without throwing away what makes the desktop app strong.
Das Produkt wurde um Konversation herum aufgebaut, nicht um Editor-Chrome. Autoren bewegen sich über Projekte und Kapitel in einer Sidebar, halten den aktiven Transcript-Fokus in der Mitte und ziehen Lore, Memory und Systemkontext aus einem eigenen Seitenpanel. Unter der Haube bleibt der Stack bewusst Local-First: SQLite für Projekte, Kapitel, Turns und Memory; retrieval-getriebener Prompt-Aufbau statt Prompt-Stuffing; WhisperKit für Speech-to-Text; Piper für Voice-Output; MLX plus Qwen als Standard-Runtime. Parallel dazu kartiert der Flutter-plus-Supabase-Rebuild dasselbe Produkt in eine sync-fähige Cross-Device-Architektur, ohne die Stärken der Desktop-App zu verlieren.
// code // code
Conversation orchestration with retrieval before generation Conversation-Orchestrierung mit Retrieval vor der Generierung
swiftactor ConversationOrchestrator {
func respond(
in project: WritingProject,
chapter: Chapter?,
latestUserTurn: ConversationTurn,
mode: AuthorMode,
settings: AppSettings
) async throws -> ConversationResult {
let priorTurns = try await database.fetchConversationTurns(projectID: project.id, chapterID: chapter?.id)
let retrievalQuery = makeRetrievalQuery(
project: project,
chapter: chapter,
recentTurns: Array(priorTurns.suffix(6)),
latestTurn: latestUserTurn
)
let retrievedMemories = try await retrievalService.retrieveRelevantMemories(
projectID: project.id,
chapterID: chapter?.id,
query: retrievalQuery,
configuration: settings.retrieval
)
let request = ConversationRequest(
mode: mode,
project: project,
chapter: chapter,
recentTurns: Array(priorTurns.suffix(8)),
latestUserTurn: latestUserTurn,
retrievedMemories: retrievedMemories,
settings: settings
)
let provider = providerFactory.makeProvider(configuration: settings.provider)
let response = try await provider.generateResponse(for: request)
let assistantTurn = ConversationTurn(
projectID: project.id,
chapterID: chapter?.id,
role: .assistant,
content: response.text,
mode: mode,
inputSource: .typed
)
try await database.saveConversationExchange(userTurn: latestUserTurn, assistantTurn: assistantTurn)
}
} The app does not send a naked prompt to the model. Each reply is assembled from project context, chapter state, recent turns, and retrieved memory before generation. Die App schickt keinen nackten Prompt an das Modell. Jede Antwort wird erst aus Projektkontext, Kapitelstatus, letzten Turns und abgerufenem Memory zusammengesetzt.
Local memory retrieval with on-demand embeddings Lokales Memory-Retrieval mit Embeddings on demand
swiftactor MemoryRetrievalService {
func retrieveRelevantMemories(
projectID: UUID,
chapterID: UUID?,
query: String,
configuration: RetrievalConfiguration
) async throws -> [RetrievedMemory] {
var notes = try await database.fetchMemoryNotes(projectID: projectID, chapterID: chapterID)
guard !notes.isEmpty else { return [] }
for index in notes.indices where notes[index].embedding == nil {
notes[index].embedding = try await embeddingProvider.embed(
text: notes[index].searchableText,
dimensions: configuration.embeddingDimensions
)
try await database.upsert(memoryNote: notes[index])
}
await index.replace(with: notes)
let queryVector = try await embeddingProvider.embed(
text: query,
dimensions: configuration.embeddingDimensions
)
let matches = await index.query(vector: queryVector, limit: configuration.maxResults)
let noteLookup = Dictionary(uniqueKeysWithValues: notes.map { ($0.id, $0) })
return matches.compactMap { result in
guard result.score >= 0.45 else { return nil }
guard let note = noteLookup[result.id] else { return nil }
return RetrievedMemory(id: note.id, note: note, score: result.score)
}
}
} Relevant notes are embedded lazily, stored locally, and filtered by a confidence threshold so the assistant gets context that helps instead of noise that derails the draft. Relevante Notizen werden bei Bedarf eingebettet, lokal gespeichert und über einen Score-Threshold gefiltert, damit der Assistent hilfreichen Kontext statt störendem Rauschen bekommt.
// outcome // ergebnis
Production-oriented product foundation: macOS host app, local-network iPhone companion, chapter-aware conversation workspace, retrieval-backed memory system, export pipeline for markdown/plain text/JSON, and a clear roadmap toward a synced Flutter companion. Produktionsorientiertes Fundament: macOS-Host-App, iPhone-Companion im lokalen Netzwerk, kapitelbewusster Conversation-Workspace, retrieval-gestütztes Memory-System, Export für Markdown/Plain-Text/JSON und eine klare Roadmap hin zu einem synchronisierten Flutter-Companion.
// brand // marke
Audience
Zielgruppe
Authors and serious fiction writers who want an AI collaborator that respects project memory, chapter context, and creative rhythm. They are more interested in continuity and depth than novelty.
Autoren und ernsthafte Fiction-Schreibende, die einen KI-Kollaborateur wollen, der Projekt-Memory, Kapitelkontext und kreativen Rhythmus respektiert. Kontinuität und Tiefe sind ihnen wichtiger als Neuheit.
Positioning
Positionierung
A writing workstation, not a prompt toy. The product treats conversation, retrieval, and voice as part of one creative system built around the author rather than around a generic LLM demo.
Eine Schreib-Workstation statt eines Prompt-Spielzeugs. Das Produkt behandelt Konversation, Retrieval und Stimme als ein kreatives System, das um den Autor herum gebaut ist, nicht um eine generische LLM-Demo.
Tone of Voice
Tonalität
Serious, creative, and quietly technical. It speaks to authors as craft-driven people, not as productivity hackers.
Ernsthaft, kreativ und leise technisch. Es spricht Autoren als handwerklich arbeitende Menschen an, nicht als Produktivitäts-Hacker.
Visual Direction
Visual Direction
Studio-like interface with focused panels, conversation-led hierarchy, and accent changes by specialist mode. The UI aims to feel like a real writing room rather than a chatbot dashboard.
Studioartige Oberfläche mit fokussierten Panels, konversationsgeführter Hierarchie und Akzentwechseln je Specialist-Mode. Die UI soll sich wie ein echter Schreibraum anfühlen, nicht wie ein Chatbot-Dashboard.
// stack
- SwiftUI
- SQLite
- WhisperKit
- Piper
- MLX
- Flutter + Supabase