No. 03

Web app · 2025–2026

Law Firm Workflow Systems

Internal systems for a large South African law firm: a docketing admin app and a renewals platform frontend rebuild.

Outcome

Built the docketing admin app front to back, writing 96% of its commits, and rebuilt the renewals platform's frontend, cutting its shipped static assets by 29.5%.

At a glance

Role
Junior Frontend Engineer
Client
A large South African law firm (anonymised)
Engagement
Employment
Timeline
Team
3 people: Docketing: 2 backend developers and me on the frontend; I wrote 96% of the admin app's commits. Renewals rebuild: a team of 7
Stack
  • Frontend React, TypeScript, Tailwind CSS, shadcn/ui, TanStack Query, Zustand
  • Backend C#, ASP.NET
  • Data SQL Server
  • Tooling Vite, Azure DevOps
Links

Internal system

Private: client code

Confidentiality
Client work, anonymised. Screenshots, code and system diagrams withheld under confidentiality.

Context

From April 2025 to September 2026 I worked in the development team of a large South African law firm, placed there by IQX. The firm runs its trademarks and patents work on internal systems its own teams build. I worked on two. The first was a new docketing system for the patents side, which tracks incoming work and its deadlines . The second was the firm's trademarks system, an older renewals platform whose frontend was being rebuilt. The docketing system as a whole, built by the whole team, reduced the number of emails that were missed .

My role

My title was Junior Frontend Engineer. On the docketing system I built the admin interface, front to back: the React client and the .NET 8 API behind it. I wrote 96% of the admin app's commits over 13 months; the rest are other people's. The API has 49 endpoints, and by files touched my work split roughly 60/40 between frontend and backend. The main processing application, which does the docketing itself, is the work of the two backend developers on the team . The admin app lets admins and team leads:

  • see the full audit trail of the decisions their docketing team members make
  • add new users to their teams without going through IT

On the renewals platform I was one of two frontend developers in a team of seven, with three backend developers, a business analyst and a solutions architect. I rebuilt the styling, replaced the frontend's data layer and built the AI integration for invoice ingestion.

Problem

Both systems made simple work slower than it needed to be. On the docketing side, adding someone to a team meant a request to IT, and team leads had no single place to see what their team members had decided . On the renewals side, the frontend carried years of Bootstrap styling, icon fonts and an API layer spread over 68 service files and 54 hooks, and the browser downloaded almost 2 MB of static assets. Invoices were captured by hand.

Constraints

  • An enterprise team. Work came through Azure DevOps, and each system had backend developers whose APIs and data I built against, so my changes had to fit their plans .
  • A system in use. The renewals platform stayed in use while its frontend was rebuilt, so new styling and data code had to land screen by screen without breaking the rest .
  • Little safety net. Neither frontend had automated tests, and the build pipeline had run five times in its whole history . Changes were checked by hand and in UAT.
  • A fixed end date. My placement ended in September 2026, while the renewals rebuild was still under way.

Approach and architecture

The docketing admin app

The admin app is a React and TypeScript single-page app that talks to an ASP.NET API on .NET 8, which reads and writes SQL Server . The processing application records each decision a team member makes, and the admin API reads those records to build the audit trail . Team membership lives in the system's own tables, so admins can add users to a team without IT. I took it through UAT to completion.

The renewals frontend rebuild

I replaced Bootstrap with Tailwind CSS and shadcn/ui, bringing in 14 shadcn primitives, and removed the icon fonts. I then replaced the old API layer with TanStack Query for server data and Zustand for the small amount of client state left.

Invoice ingestion

A user uploads an invoice in the renewals platform. The backend sends it to a general-purpose multimodal model, which returns 15 fields. A person reviews the fields before anything is saved .

Key decisions and trade-offs

Tailwind CSS and shadcn/ui over Bootstrap

Chose
Tailwind CSS with shadcn/ui components
Over
Keeping Bootstrap and restyling on top of it
Cost
Restyling every screen by hand, with no gain in build time

Shipped static assets fell from 1,919.6 kB to 1,354.1 kB, 29.5% less. The stylesheet shrank by 72.7%, and the icon fonts went away. Both builds come from the Vite build report: the last buildable commit with Bootstrap against the first one without it. It moved shipped bytes, not build time or the number of lines we committed .

TanStack Query and Zustand over Redux

Chose
TanStack Query for server data and Zustand for client state
Over
Keeping and extending the existing Redux API layer
Cost
Rewriting the whole data layer, with no tests to catch regressions

The old layer had 68 service files and 54 hooks. I replaced all of it with 45 API functions and 51 hooks. Most of what the old store held was server data, which TanStack Query caches and refetches without hand-written plumbing .

A general-purpose model for invoice extraction

Chose
A general-purpose multimodal model with a 512-token thinking budget
Over
A prebuilt invoice-extraction service, or the same model family's lighter variant
Cost
Owning the prompt and output handling, on evidence from only 10 invoices

I compared three options on 10 invoices and 15 fields each. Weighted accuracy was 0.887 for the model we shipped, 0.853 for its lighter variant and 0.600 for the prebuilt service . The prebuilt service returned nothing for 5 of the 15 required fields, at about 5 times the cost per 1,000 requests. The 512-token thinking budget was the balance I found between accuracy and latency. Ten invoices is a small sample, which is one reason every result goes through human review.

Local development sign-in over the shared remote server

Chose
A sign-in path that exists only when running the app locally
Over
Signing in through the shared remote server on every local run
Cost
A second sign-in path that must never reach a deployed build

Production sign-in did not change; this only affected how developers ran the app on their own machines. Depending on the shared server made local work slow and fragile .

Outcome

The docketing admin app went through UAT to completion. Admins and team leads can now follow their team members' decisions in one audit trail and add users to their teams themselves .

The renewals frontend was rebuilt before my contract ended, although the wider rebuild was still under way. Shipped static assets fell by 29.5%, the stylesheet by 72.7%, and the icon fonts are gone.

For invoice ingestion, the trademarks team reported that capturing an invoice took about 30 minutes by hand and at most 2 minutes in their testing of the service, with human review.

Reflection

The one thing I would do differently is write tests for the docketing admin app from the first week. Neither client had tests, so every change was checked by hand and in UAT. A small suite around the audit trail and user management would have cost little at the start and made every later change safer .

If you write almost every commit, nobody else is going to write the tests.

Creation of Adam

Interested in working together?