CCWork
CC
Email me

Work

Gavel

Gavel is a no-code platform for legal document automation. I built billing and plan gating, owned the Python generator every workflow runs through, and built the AI review screen.

Role
Full-stack software engineer
When
2023–2025
Client
Gavel
Stack
React 18, TypeScript, Mantine, TanStack Query, React Hook Form + Zod, NestJS, and 10 more
Status
Acquired by Relativity, 2026
Gavel AI variable review: accept or reject a suggested question for a divorce-petition form, with the tagged fields highlighted on the PDF
The variable-review UI I built for Gavel AI.

Context

Gavel (formerly Documate) is a no-code platform for legal document automation. Lawyers build questionnaire workflows without writing code. Their clients answer a guided interview, and Gavel applies the logic, pulls in outside data, and assembles finished Word and PDF documents, with e-signature, payments, Zapier and AI on top.

I was a software engineer there from 2023 to 2025. Most of my time went into billing and plan gating and the Python generator that turns a workflow into a runnable interview. I also built field types for the workflow builder and the AI variable-review screen, with smaller pieces in the client portal. Relativity acquired Gavel in June 2026, after I'd left.

The billing work started from a concrete problem. People who had never paid could reach paid features, and there was no clean way to prompt an upgrade.

What I built

  • Billing and plan gating. React paywalls and entitlement hooks, a NestJS tier guard and a shared feature-to-plan contract, policy checks in OPA, and an AWS Lambda that reconciles Stripe subscription events into DynamoDB, including trials and past-due accounts. When an account is blocked, admins are sent to the billing portal and everyone else is told who to ask.
  • The document engine's code generator. I owned the Python generator that turns a workflow into docassemble YAML, and every workflow runs through it. It compiles the builder's pages, fields, logic and formulas into runnable legal interviews, including show/hide rules, repeating lists and conditional PDF fills written as Mako expressions, with a pytest suite behind it.
  • The questionnaire builder's field types: choices, CSV lookups, Clio contacts and matters, signatures and repeating items, each on a shared base component with React Hook Form and Zod. A workflow error system collects field and formula errors, and its "Resolve" button jumps to the problem.
  • Gavel AI variable review, the screen where people accept, reject and correct the variables AI pulled out of their documents. The PDF annotations and the variables stay in sync in both directions. I also built the SQS queue that hands uploaded files to the extraction service.
  • A four-tab settings module for webhooks, Zapier, DocuSign OAuth, Clio and access permissions, backed by a mutation layer that deep-merges partial updates into the cache.
  • The Mantine v6 to v7 migration across the frontend, with the browser tests kept green through it.

Some of these, like CSV lookups and Clio fields, crossed three repositories and four languages, from the React field through the shared validation contract to the Python that generates the interview. I built those through every layer.

Architecture

Gavel’s billing and entitlements, with the document engine I owned underneath.

The entitlement path, step by step:

  1. A React paywall or entitlement hook asks whether this workspace can use a feature.
  2. The shared contract maps that feature to the plans that include it.
  3. The NestJS tier guard enforces the same answer on the server, with OPA evaluating the policy.
  4. Stripe sends subscription events to a Lambda.
  5. The Lambda validates each event and writes the subscription state, including trial and past-due flags, to DynamoDB.
  6. The UI shows the matching screen: the feature, an upgrade prompt, or a trial-expired or past-due view.
Gavel questionnaire builder: a list of pages and questions beside the palette of field types, information blocks and integrations
Field types from the builder's type system, including the Clio and CSV fields, which I built across all three repositories. Image: Gavel.

Decisions

Before I changed any code, I got everyone onto one shared contract, a single mapping from each feature to the plans that include it. The React hooks, the tier guard and the OPA policy all read the same mapping.

Server-side checks

A gate in the browser only shapes what people see, so the tier guard and the OPA check enforce entitlements on the server, where they can't be bypassed. The UI hooks show a loading state while they check, so someone who's allowed in never sees a paywall flash first.

Subscription state

A dedicated Lambda turns Stripe's subscription events into state in DynamoDB, and a mocked AWS test suite covers the transitions, including trials and past-due accounts.

Builder performance

Field errors live in a WeakMap, outside React state, and each field reads its data from context. Validating a large workflow doesn't re-render it, and editing stayed responsive as workflows got bigger.

gavel.io header in Aug 2026 with the banner announcing the acquisition by Relativity, above the site headline
gavel.io today, with the acquisition banner. Gavel Exec, which the site leads with, isn't my work.

What went wrong

The paywall bug looked like a billing bug at first. It turned out to be three teams with three different ideas of what each plan included, which is why the fix started with the shared contract and only then moved to the paywall code.

Screenshots

  • Gavel workflow step one: upload Word or PDF forms, with three court forms listed as selected documents
    The upload step that feeds the AI review. Image: Gavel.

My part

Gavel Exec, the Word add-in gavel.io leads with today, isn't my work. Mine is in Gavel Workflows and Gavel AI document review. A teammate wrote the chevrotain formula parser, and I worked on the formula editor on top of it. The AI extraction service isn't mine either. I built the queue that feeds it and the review UI around its output, and I built some shared state code, like the computed-state layer, with teammates.

Numbers

FigureWhat it measures
2,000+law firms and legal teams using Gavel, by Gavel's count (gavel.io)
~1,840commits of mine across four repositories
116+product tickets shipped in the main monorepo
Questions