[06]
LEX AI — WORD ADD-IN FOR LAWYERS
█ TIER A · YEAR 2026 · STATUS: WIP · LANGUAGES: TYPESCRIPT
A Word add-in fusing hard templates with model-generated fields
[FIG. 1] MISSION
I build add-ins for office applications (Office.js), an interface right where the recipient already works — here inside Word itself. My client, a lawyer at a firm, automated templates in ClauseBase, but hard automation can't handle individually negotiated clauses, while pure-AI tools generate the whole document and turn the lawyer into a full-time reviewer. So I fuse both paradigms in one document: hard placeholders (string/date/amount) filled from a form with no review at all, and instruction-driven AI placeholders whose content Claude streams live into a task-pane preview. The product thesis: 90% of the document is a deterministic template, AI fills the ~10% of 'unknown unknowns' — the lawyer verifies 8–10 clauses instead of 20 pages, which makes their professional responsibility (bar-association guidance) physically workable. I encoded the hard-vs-AI split directly in the type system; a scanner walks the document via Office.js, and a prompt builder composes document context with the placeholder's instruction, trimming it deliberately: about 12 thousand characters of the whole text, or just the tail of the section, depending on the field. I stream the model's answer over SSE so the lawyer watches the text appear. I work here with a client from a regulated industry and design for their process, not for a technology demo — hence the competitive analysis (ClauseBase/Spellbook/Libra) and staged pricing, MVP at 100–180k PLN net.
[FIG. 2] ARCHITECTURE
hover a block to see its description
[FIG. 3] CHALLENGES
[+][CH-01]
Two paradigms in one document isn't a UI problem but a responsibility problem: if the lawyer can't tell deterministic content from generated content at a glance, they have to review everything — and the product's entire value evaporates. So I separated the paradigms at the type-system level (types/placeholder.ts) and built a scanner that classifies every placeholder in the document; hard fields get a form, AI ones get a card with the instruction and a preview. Only ~10% of the content needs review — and it's precisely visible which part.
[+][CH-02]
The law firm already used ClauseBody — a closed template tool the add-in had to coexist with in the same documents, and which exposes no API. The only 'interface' turned out to be the document's appearance: ClauseBody fields are text in the {{NAME}} pattern highlighted in cyan. So I wrote a detector that searches with a {{*}} wildcard, filters hits by highlightColor === 'Cyan' and validates the name with a regex — and the whole scan runs in layers, 'most reliable source first': the add-in's native ContentControls, then a regex fallback for text markers, finally the foreign-field detector, deduplicated by name. The same world brought a second constraint: field identifiers can't contain Polish characters, so instead of rejecting 'Nazwa firmy' I built a transliterator (Ą→A … Ż→Z) and split the model into a technical name and a human displayName — the lawyer writes in Polish, the document gets safe ASCII.
[+][CH-03]
Office.js is a closed, temperamental environment: every document mutation goes through an asynchronous load → sync → write cycle, and merely setting a highlight color takes two separate sync() calls — first loading the font, then writing. Meanwhile the template had to live inside the .docx itself, no external database — and CustomXmlParts have no 'replace' operation: only add and delete. So I built a manual upsert: load all XML parts, filter by namespace as the identity key, delete the old ones, add the new one — four sync() calls in a row — and wrapped the template JSON in CDATA to avoid fighting XML escaping; the read path has two fallbacks before giving up with a readable domain error. On top of that, a readiness gate: Office.onReady sometimes never fires outside Word, so after 3 seconds the hook renders the panel anyway, distinguishing Word/Browser/Standalone/Timeout platforms — the UI never hangs on a spinner regardless of the host's mood.
[+][CH-04]
A lawyer won't trust a black box that spits out a finished clause after 20 seconds — they need to see the content take shape. I built Claude streaming straight into the task pane (useClaudeStream, StreamingPreview): the clause writes itself on screen live, and the prompt builder fuses the whole document's context with the specific placeholder's instruction so the generated fragment matches the rest in tone and terminology. The live preview changes the psychology of review — the lawyer reads along, not after the fact.
[FIG. 4] AI LAYER
The model stream-generates only the AI placeholders' content — the ~10% of the document no template can predict; the prompt builder combines the field's instruction with trimmed document context (~12 thousand characters for the whole document or the section tail, selectable per field). I decoupled the product layer from the provider with an OpenAI-compatible OpenRouter gateway: Claude Sonnet works by default, but switching to GPT is a settings field, not a code change. The boundary is hard and encoded in the types: AI never touches the deterministic part, because every such touch would mean extra lawyer verification — the death of the product thesis.
[FIG. 4A] PRESALE — STAGED PRICING
REAL CLIENT · LAW FIRM · COMPETITIVE ANALYSIS: CLAUSEBASE / SPELLBOOK / LIBRA
[FIG. 2A] THE 90/10 THESIS
→ pure AI: the lawyer reads everything from scratch