An AI-assisted writing tool designed to promote writer autonomy rather than passive AI consumption, built as a research prototype for controlled user studies.
- Goal-driven writing sessions -- Modal prompt at session start captures writing intent, with editable goal display throughout the session
- 7 distinct text states -- Visual decorations distinguish user-written, AI-generated, AI-pending, user-edited, marked-preserve, marked-delete, and original-removed text
- Inline diff system -- Side-by-side original and AI replacement text rendered inline with click-to-restore and click-to-reject interactions
- Provenance tracking -- Every user interaction is logged as a typed event for research data collection and analysis
- Session persistence -- Debounced localStorage writes with automatic restore on page reload and JSON export for research data
- Adaptive layout -- 70/30 split editor and side panel with collapsible panel for focused writing
- Loading state management -- Skeleton placeholders and read-only editor during AI generation, with interactive side panel
- Click-based selection granularity -- Single click highlights a word immediately (no delay), double click selects a phrase, triple click selects a sentence; click handlers set visual decorations only, with no text state changes
- AI alternatives tooltip -- Drag-select any text in the plain editor to see 3-4 GPT-4o-generated alternative phrasings in a floating panel; click an alternative to replace the selected text instantly
- Clause-level snap selection in diff view -- Drag selection in the split diff view auto-expands to clause boundaries using phrase boundary detection, with Keep/Delete tooltip for accepting or rejecting clauses
- Regenerate button -- Collects all
marked-deletesegments as gaps, sends preserved text as constraints alongside the writing goal to OpenAI GPT-4o; disabled when no segments are marked - Inline diff display -- AI replacements appear as inline diffs (original in red strikethrough, replacement in green highlight); each diff is resolved independently by clicking
- Dual-mode prompt bar -- Persistent input bar at the bottom of the editor with two modes: selection replacement (when text is selected) and cursor continuation (when no text is selected)
- Server-side AI integration -- OpenAI GPT-4o communication via Next.js route handlers at
/api/generateand/api/alternatives, keeping the API key secure on the server side - Edge case handling -- Regenerating with all text deleted generates a fresh draft from the writing goal; the Regenerate button stays disabled when no marks exist
- Dark mode support -- All tooltip and selection interaction styles adapt to both light and dark themes
| Component | Technology | Version |
|---|---|---|
| Framework | Next.js (App Router) | 15 |
| UI Library | React | 19 |
| Editor Engine | TipTap (ProseMirror-based) | 2.x |
| State Management | Zustand | 5.x |
| Styling | Tailwind CSS | 4.x |
| Language | TypeScript | 5.x |
| Persistence | localStorage (browser) | -- |
| AI Provider | OpenAI GPT-4o (server-side) | -- |
| Testing | Vitest + Testing Library | 3.x |
- Node.js 20 or later
- npm
- An OpenAI API key with GPT-4o access
npm installCreate a .env.local file in the project root with your OpenAI API key:
OPENAI_API_KEY=sk-your-api-key-here
This key is used exclusively on the server side via the Next.js route handler at /api/generate. It is never exposed to the client browser.
npm run devThe application will be available at http://localhost:3000.
npm run test # Watch mode
npm run test:run # Single runnpm run typechecknpm run lintsrc/
app/
api/
generate/route.ts # OpenAI GPT-4o server-side route handler for regeneration
alternatives/route.ts # Lightweight GPT-4o endpoint for alternative phrasings
page.tsx # Main application page
layout.tsx # Root layout
globals.css # Global styles including marking, diff, and tooltip CSS
components/
goal/ # Goal prompt modal, mode selector, goal display
editor/
CoWriThinkEditor.tsx # TipTap editor wrapper with forwardRef and tooltip integration
AlternativesTooltip.tsx # Floating panel for AI alternative suggestions on drag selection
KeepDeleteTooltip.tsx # Compact Keep/Delete tooltip for split diff view
DiffSplitView.tsx # Side-by-side diff view with clause-level snap selection
DiffToolbar.tsx # Toolbar controls for the diff view
PromptBar.tsx # Dual-mode AI prompt input (selection / continuation)
RegenerateButton.tsx # Regeneration trigger button
SkeletonPlaceholder.tsx# Loading skeleton during generation
sidebar/ # Collapsible side panel with goal, history, outline
layout/ # Split layout container and app header
shared/ # Export button, storage warning
extensions/
TextStateExtension.ts # Custom TipTap mark for 7 text states
MarkingExtension.ts # Click-based selection (word/phrase/sentence) and drag detection
ProvenancePlugin.ts # Transaction-level event logging
DiffDecorationPlugin.ts # Inline diff rendering with decorations
hooks/
useGeneration.ts # React hook managing generation lifecycle
useAlternatives.ts # React hook for fetching AI alternative phrasings with abort control
useSyncScroll.ts # Synchronized scrolling for split diff panels
services/
generation.ts # Document scanning, prompt construction, API calls
stores/
useSessionStore.ts # Session lifecycle, goal, persistence
useProvenanceStore.ts # Provenance event logging
useEditorStore.ts # Text states, diffs, read-only mode
useLoadingStore.ts # AI generation flags
useLayoutStore.ts # Side panel collapse state
lib/
boundaries.ts # Word, phrase, and sentence boundary detection
diffCompute.ts # Diff computation utilities for split view
storage.ts # localStorage service with capacity monitoring
export.ts # Session export to JSON file
__tests__/
boundaries.test.ts # 22 boundary detection unit tests
types/
session.ts # Session, GoalChange type definitions
provenance.ts # ProvenanceEvent, EventType type definitions
generation.ts # Generation pipeline interfaces (GapInfo, GenerateRequest, etc.)
editor.ts # TextState, DiffEntry, SegmentScore definitions
index.ts # Consolidated type exports
User Input --> TipTap Editor --> Zustand Stores --> localStorage
| |
v v
ProvenancePlugin Session Export (JSON)
Marking Flow:
User Click --> MarkingExtension (word/phrase/sentence highlight) --> Visual Decoration
User Drag --> MarkingExtension (drag detection) --> AlternativesTooltip --> /api/alternatives --> Replace Text
Regeneration Flow:
Regenerate Button --> useGeneration Hook --> GenerationService (scan + prompt)
--> /api/generate (server) --> OpenAI GPT-4o --> Parse Response
--> Apply Inline Diffs --> User Resolves Diffs
Prompt Bar Flow:
User Prompt --> Detect Mode (selection vs. continuation)
--> GenerationService --> /api/generate --> Apply Result
User interactions flow through the TipTap editor, which dispatches ProseMirror transactions. The ProvenancePlugin intercepts these transactions to log events. Zustand stores manage application state and persist to localStorage with debounced writes.
The marking interaction adds a second layer: the MarkingExtension handles click-based selection (word on single click, phrase on double click, sentence on triple click) as visual decorations only. Drag selection triggers the useAlternatives hook, which fetches AI-generated alternative phrasings from /api/alternatives and displays them in the AlternativesTooltip for click-to-replace. In the split diff view, drag selection snaps to clause boundaries and presents Keep/Delete actions via the KeepDeleteTooltip. When the user triggers regeneration, the useGeneration hook orchestrates document scanning, prompt construction, server-side API calls, and inline diff application.
The editor tracks 7 distinct text states, each with a specific visual treatment:
| State | Description | Visual Treatment |
|---|---|---|
user-written |
Text typed directly by the user | Default (no highlight) |
ai-generated |
Text produced by the AI and accepted by the user | Green highlight |
ai-pending |
AI replacement text awaiting user decision | Green highlight in diff |
user-edited |
AI text manually modified by the user | Subtle underline |
marked-preserve |
Text explicitly marked to keep during regeneration | Green background |
marked-delete |
Text explicitly marked for removal | Red strikethrough |
original-removed |
Original text shown in diff view | Red strikethrough |
Every user interaction is recorded as a ProvenanceEvent with a unique ID, timestamp, event type, and data payload. The 11 event types cover the full interaction lifecycle:
text-typed-- User keyboard inputai-generation-requested/ai-generation-received-- AI round-tripmark-applied-- Text state marking actionsedit-in-place-- Direct text modificationpushback-shown/pushback-response-- AI pushback interactionsprocess2-shown/process2-response-- Process 2 interactionsawareness-toggled-- Awareness layer togglegoal-changed-- Writing goal modification
This provenance data enables research analysis of writer-AI interaction patterns and reliance behaviors.
Server-side route handler for AI text generation via OpenAI GPT-4o.
Request Body:
{
"goal": "string (required) - The user's writing goal",
"document": "string (required) - Document text with [GAP:id] markers",
"gaps": [
{
"id": "string",
"position": { "from": 0, "to": 10 },
"originalText": "string"
}
],
"constraints": [
{
"text": "string",
"position": { "from": 0, "to": 10 }
}
],
"userRequest": "string (optional) - Natural language request from the prompt bar",
"mode": "regenerate | selection | continuation"
}Success Response (200):
{
"gaps": [
{ "id": "gap-id", "text": "generated text" }
]
}Error Response (4xx/5xx):
{
"error": "Human-readable error message",
"retryable": true
}Modes:
regenerate-- Fill gaps from marked-delete segments using surrounding context and the writing goal.selection-- Replace the user-selected text region based on the prompt bar request.continuation-- Generate continuation text at the cursor position based on the prompt bar request and surrounding context.
Lightweight endpoint for generating alternative phrasings of selected text via OpenAI GPT-4o.
Request Body:
{
"selectedText": "string (required, max 500 chars) - The text to rephrase",
"context": "string (optional) - Surrounding text for context",
"goal": "string (optional) - Writing style guidance"
}Success Response (200):
{
"alternatives": ["alternative 1", "alternative 2", "alternative 3"]
}Error Response (4xx/5xx):
{
"error": "Human-readable error message",
"retryable": true
}Details:
- Uses GPT-4o with
max_tokens: 256for fast response times - 15-second request timeout
- Returns 3-4 alternative phrasings
- Selected text is limited to 500 characters
| SPEC ID | Title | Status |
|---|---|---|
| SPEC-CORE-001 | Editor Foundation & Data Model | Completed |
| SPEC-INTERACT-001 | Marking & AI Generation | Completed |
| SPEC-INTERACT-002 | Editor Interaction Redesign | Completed |
| SPEC-GUARD-001 | Pushback System | Planned |
| SPEC-PROC2-001 | Process 2 Objective Surfacing | Planned |
| SPEC-AWARE-001 | Awareness & Reliance Overlay | Planned |
This is a research prototype developed for academic study. Not intended for production use.