Press ESC to exit fullscreen
📖 Lesson ⏱️ 90 minutes

Building Operational Applications

Workshop-style app construction: forms, tables, maps, workflows that operators actually use

What an operational app is

An operational application is software an operator uses to do their job during the workday. It is the opposite of a dashboard, a report, or an analytical notebook — those are read-only and aimed at decision-making over hours or days. An operational app is read-and-write, aimed at decision-making over minutes or seconds.

The criteria:

  • A specific role uses it daily (dispatcher, technician, analyst, agent)
  • Its job is to drive a workflow (not just display state)
  • It writes to the system of record through typed actions
  • Adoption matters — if operators don’t use it, it doesn’t exist

The Northbound morning view Maria has been waiting for is the canonical example: a single screen, used every morning between 6:00 and 7:00, that drives the 6:45 call with Doug. Iteration 1 is to ship it.

The four building blocks

Almost every operational app is composed from four building blocks. Learn to think in these.

   ┌──────────────────┐    click row →    ┌──────────────────┐
   │       LIST       │ ───────────────▶  │      DETAIL      │
   │  (table or grid) │                   │  (one object)    │
   └──────────────────┘                   └──────────────────┘
           │                                       │
           │ filter / search                       │ open form
           ▼                                       ▼
   ┌──────────────────┐                   ┌──────────────────┐
   │      FORM        │  ────submit────▶  │   ACTION         │
   │  (parameter set) │                   │  (semantic-layer │
   └──────────────────┘                   │   mutation)      │
                                          └──────────────────┘

Block 1 — List

A table, grid, or map showing many objects of one type, with filtering, sorting, and selection. This is the most-used screen in any operational app, usually by an order of magnitude.

Block 2 — Detail

A single object expanded — all properties, related objects, a timeline of changes, and the actions available on this thing.

Block 3 — Form

A typed parameter set for an action. Inputs validated against the action’s declared types, with defaults pre-filled from context where possible.

Block 4 — Action

The semantic-layer mutation we designed in Designing the Semantic Layer. Triggered by a form submit or a button click on a detail page.

Every operational app you build is a composition of these four blocks. The Northbound dispatch app, fully built out by iteration 6, will have maybe 20 screens — all of them are one of the four.

The eight UX rules of operational software

Consumer UX best practices do not transfer cleanly to operational software. Operators are not visitors — they are power users spending 8 hours a day in your app, often with elevated cognitive load and adversarial time pressure. The rules are different.

Rule 1 — Density over whitespace

A dispatcher needs to see 80 loads at a glance. Tailwind defaults give you 8. Tune your information density up — tight rows, abbreviated labels, compact controls. Operators will tell you when it is too dense; they will not tell you when it is too sparse, they will just quietly open a spreadsheet on the other monitor.

Rule 2 — Keyboard first, mouse second

Power users live in the keyboard. Every common action needs a shortcut. Tabbing through a form should produce the expected ordering. The first screen should be reachable in one keystroke, not three clicks. Hidden tip: provide a ? overlay that lists every shortcut.

Rule 3 — Show staleness, always

Every value derived from a refresh-on-cadence source has a freshness. Show it — visibly, near the value, not buried in a tooltip. The single biggest source of “the system is wrong” complaints is operators looking at stale data without knowing it’s stale.

Load NB-87412
  ETA delta:  +22 min  (GPS observed 4 min ago)
  Status:     IN_TRANSIT  (SAP synced 7h ago)

The operator now knows which numbers to trust.

Rule 4 — Surface the source

Operators need to know where the data came from to trust it. Hover on a value, or click a small “source” icon, and get back: “this came from SAP nightly export, run at 02:03.” Boring detail. Builds trust massively. Tells the operator whether the spreadsheet is more recent than your app.

Rule 5 — Make every action audited and reversible

Every state-changing action emits an audit record (already true if you followed the semantic layer lesson). The app surfaces this: a “Recent activity” feed on the detail screen, an “Undo last action” button where reversal is safe, a “View change history” link where it isn’t.

Operators do not panic about making mistakes if the app makes mistakes recoverable. They panic — and then they avoid the app — when actions feel one-way.

Rule 6 — One way to do each thing

Three ways to reassign a stop = an operator who never quite knows which one is the “real” one. Pick one canonical path. If a second exists for power users (a keyboard shortcut, a bulk action), it must invoke exactly the same underlying action.

Rule 7 — Default to read-only

The first version of every screen is read-only. Adding edit and action capability is a deliberate decision, made after the read flow is working. Operational apps that ship with editing on day one are almost always wrong about what should be editable.

Rule 8 — Error states are first-class

What happens when the GPS portal is down? When SAP is stale? When an action validation fails? Design these states explicitly — with a named visual treatment, an explanation of what and why, and a recovery path. “Something went wrong” is not a design.

A good error state for the morning view:

GPS portal unavailable.  Last known positions are 23 minutes old.

[Refresh]  [Use last-known ETAs (less accurate)]

Reported to IT at 06:12.  ETA to restore: not yet known.

The dispatcher knows what’s broken, what she can do, who’s been told. She makes the call.

Designing the Northbound morning view

Iteration 1 of the dispatch app is a single screen: the morning view. Let’s design it concretely.

What it needs to show

From discovery: every weekday morning, Maria identifies which loads are slipping ETAs by >15 min before her 6:45 call with Doug. She does this today across three screens (SAP, GPS portal, spreadsheet).

The morning view replaces those three screens with one.

Layout sketch

┌─────────────────────────────────────────────────────────────────────────────┐
│  MORNING DISPATCH   2026-05-15 Thursday  06:14 CST    SAP: 8h | GPS: 4m     │
├─────────────────────────────────────────────────────────────────────────────┤
│  Filter:  ⏷ All hubs       Driver: any        ⊙ Slipping only   ⟳          │
├─────────────────────────────────────────────────────────────────────────────┤
│  ▲ Load    Customer      Origin → Dest    Plan ETA   ETA Δ   Driver    ⋯   │
├─────────────────────────────────────────────────────────────────────────────┤
│  NB-87412  Acme Corp     CHI → CLE        08:45     ▲ +22m   Hernandez ⋯   │
│  NB-87418  Riverpoint    CHI → IND        09:10     ▲ +18m   Watson    ⋯   │
│  NB-87423  Acme Corp     CHI → DET        10:30     ▲ +47m   Petrov    ⋯   │
│  NB-87431  Linden Logs   STL → CHI        11:15        +3m   Adesina   ⋯   │
│  NB-87435  Acme Corp     IND → CHI        12:00        on    Watson    ⋯   │
│                                                                             │
│  47 active loads · 4 slipping · 12 within 30 min of plan · 31 on plan      │
└─────────────────────────────────────────────────────────────────────────────┘

Density-tuned. Freshness shown in the header. Slipping loads highlighted. A row-context menu () for actions. A footer summary line for the call with Doug.

Mapping to the building blocks

  • List: the table of active loads, filterable, sortable, slip-highlighted
  • Detail: click a load row → drawer (not full-screen) with stops, GPS trail, status history
  • Form: “Reassign this stop” form in the row context menu (iteration 2; defer for iteration 1)
  • Action: reassignStop() from the semantic layer (iteration 2; not in iteration 1)

Iteration 1 is list only. Read-only. One screen. That is intentional and exactly what MVP scoping said we’d build.

Wiring it to the semantic layer

The list query is one call to the ontology:

ontology.loads({
  filter: {
    current_status: ["TENDERED", "ACCEPTED", "IN_TRANSIT"],
    planned_pickup_at: { between: [todayStart, todayEnd] },
    origin_hub: filter.hub_id ? [filter.hub_id] : undefined,
  },
  sort: [
    { field: "live_eta_delta_min", order: "desc" },
    { field: "planned_delivery_at", order: "asc" },
  ],
  include: {
    customer: ["name"],
    origin_hub: ["hub_code"],
    destination_hub: ["hub_code"],
    current_driver_assignment: {
      include: { driver: ["name"] },
    },
  },
});

What this gets us, for free, from having a real semantic layer:

  • The same query is callable from a CLI, a notebook, a future agent
  • The query is typed, so a bad filter caught at compile time
  • The result includes freshness metadata for each property
  • The query is replayable for any past timestamp (subject to retention)

This is the leverage of investing in Phase 3. The app code is tiny because the platform underneath does the work.

The first build

Walking through what iteration 1 actually looks like on the build side. This is what your Tuesday-Wednesday of week 3 looks like, after plumbing landed Monday.

Step 1 — Scaffold the screen (30 min)

A single page in your platform’s app framework. One container, header, body, footer. No data yet. Wire the route so Maria can hit a URL.

Step 2 — Wire the list query (1 hour)

Call the ontology query above. Render the rows raw — JSON dump, no styling. Verify the data is right by eye-balling against what Maria saw in her spreadsheet yesterday morning.

This is the cheapest correctness check in the world. The semantic layer is supposed to canonicalize the world; the first time you render it, you find out whether it actually did.

Step 3 — Tune density and columns (1 hour)

Now style. Get the table looking like the sketch above. Pick reasonable column widths. Highlight slipping rows in a single color (you can refine later).

Step 4 — Add freshness display (30 min)

Header chips showing SAP and GPS freshness. Per-row staleness in the ETA cell.

Hub filter, “slipping only” toggle, the footer summary line for Doug. These are the affordances that turn a table into Maria’s tool, not just a data viewer.

Step 6 — Add the error states (1 hour)

What does the screen look like when GPS is down? When SAP is stale beyond the alert threshold? When there are zero active loads (a Sunday morning)? Design each, intentionally.

Step 7 — Install on Maria’s laptop (30 min)

Bookmark it in her browser. Walk her through. Watch her use it for 15 minutes. Take notes silently.

That is roughly a Tuesday plus half of Wednesday. Thursday and Friday are for iteration on what you saw her do — and the Friday demo.

The “watch them use it” hour

The most valuable hour of the iteration. Once you have the screen installed, sit next to Maria the next morning at 6:00 AM and watch.

What you are looking for:

  • What does she look at first? That goes top-left on the screen.
  • What does she scroll past? That column is too wide or unimportant; cut or compress.
  • Where does she pause? Something is unclear — likely a value she does not trust.
  • What does she do on the screen, then immediately do on another screen? You are missing context that should be on yours.
  • When does she sigh? A friction point. Note it.
  • When does she lean in? It is working. Note it.

What you are not doing in this hour:

  • Asking questions (mostly silence, with a few “what are you doing now?”)
  • Defending the design
  • Adding new ideas in real time

Take notes. Re-design at your desk in the afternoon. Reship the morning after.

Anti-patterns specific to operational apps

A few common mistakes:

Designing for the demo, not the day

A screen optimized for a 5-minute walkthrough looks slick. A screen optimized for 8 hours of use is dense, terse, and ugly to the uninitiated. Optimize for the day. Brief the executive separately.

Hiding power behind progressive disclosure

Consumer UX hides power behind menus and modals to reduce visual complexity. Operational UX surfaces power, because the operator needs it constantly. Resist the urge to hide.

Generic data tables

The temptation to drop in a default-styled DataTable component and move on. Resist. The 80% of value in an operational app is in the specific visual treatment of this domain — the highlight for slipping, the freshness chip, the abbreviated hub codes Maria recognizes. Generic tables produce ignored apps.

Slow loads, unspoken

A screen that takes 4 seconds to load every morning is a screen that is opened less every morning. Set explicit latency budgets — p50 under 500ms, p99 under 2s for a list view — and instrument them. If you cannot meet them, you have a semantic-layer query problem, not a UI problem.

Auth and permissions added late

If permissions show up in iteration 4, you will discover the permissions model is wrong and rebuild. Better: start with a trivial permissions model in iteration 1 (one user, full access) but design the app’s query layer so adding real permissions later is mechanical.

One screen per object type

The instinct to mirror the ontology: a screen for Load, a screen for Stop, a screen for Driver. But operators do not think in object types — they think in workflows. The morning view is one workflow that touches Load, Stop, GPSPing, DriverAssignment and renders the relevant slice of each. Build workflow-shaped screens, not type-shaped ones.

A second screen: the reassignment flow

For iteration 2, Maria asks for: “When I see a load slipping by more than an hour, I want to reassign it without leaving the morning view.”

This is when forms and actions enter the app. The flow:

  1. Maria right-clicks a slipping row → “Reassign stops”
  2. A drawer slides in: the load’s stops on the left, a candidate driver picker on the right
  3. The picker shows nearby drivers (live position + remaining capacity)
  4. She clicks “Assign” → typed form opens with prefilled fields
  5. She reviews, optionally adds a reason, clicks submit
  6. The action runs through the semantic layer’s reassignStop() action
  7. The list refreshes; the row’s driver updates; the audit log shows the change

Every step maps to a building block:

  • Step 1-2: detail (the drawer)
  • Step 3-4: list (driver picker) → form (assignment params)
  • Step 5-6: action (semantic layer mutation)
  • Step 7: back to the list, with audit visible

Notice: no new app code patterns. Just composition of the four building blocks against the semantic layer.

What you ship to the customer

By end of iteration 1, you walk into the Friday demo with three artifacts:

  • The morning view live at a URL the dispatchers can hit from their laptops
  • A short measurement plan for the next week — when does Maria open it, does she stop updating the spreadsheet, does Doug’s 6:45 call go faster
  • A one-pager documenting the screen, the data it pulls, the actions it does not yet support, and the iteration-2 candidates

The customer’s senior stakeholders see a screen that maps to a workflow they can recognize. The dispatcher sees a tool that obviously was designed by someone who watched her work. The IT director sees a deployment plan that does not yet touch production. Everyone leaves the demo aligned.

The longer arc

Across the 6 iterations of the Northbound engagement, the app accretes screens:

IterationNew surfaceBuilding blocks
1Morning view (read-only)List
2Stop reassignment flowList + Detail + Form + Action
3Doug’s hub view (east hub)List variant
4Slip alerts (mobile push)Outbound notification surface
5Multi-dispatcher rolloutRBAC + per-user filters
6Customer-facing tracking (read-only)List + Detail, external auth

Six iterations. Six well-bounded slices. Each one driven by a measured outcome from the previous. None of them complex when taken on its own.

Key terms to remember

  • Operational app — software an operator uses daily to drive a workflow
  • The four building blocks — list, detail, form, action
  • Density-tuned — UI calibrated for power users in operational sessions, not visitors
  • Freshness in the UI — visibly surface staleness, never silently
  • Source affordance — let operators see where each value came from
  • The “watch them use it” hour — observing live use after install, the single highest-leverage feedback ritual
  • Workflow-shaped screen — built around what the operator is doing, not the object types they touch

What’s next

Many platforms split surfaces between low-code composition (drag-and-drop, declarative configs) and pro-code custom development. Operational apps live across both. The next lesson covers when each is right, when to mix them, and how to keep both maintainable.