Skip to Content
GuideScaling Up

Scaling Up

In the previous pages, we deliberately stayed with very small examples. The goal was not to avoid real problems. The goal was to let you internalize Ansiq’s core chain first:

  • signal / computed / effect
  • components and view!
  • focus / input / layout
  • viewport / history / patch emission

But once an app grows from an example into a long-running terminal program, a different class of problems appears:

  • scenario files become too large
  • system sampling, network work, business state, and UI composition get mixed together
  • subtree replacement needs to preserve more interaction state
  • viewport and history semantics become tightly coupled to product behavior

This page is not about making your folders look elegant. It is about deciding which things should continue to live together, and which ones must be separated if you want the app to remain understandable.

A common wrong starting point

Many terminal apps grow like this:

fn render(&mut self, cx: &mut ViewCtx<'_, Msg>) -> Element<Msg> { // 1. read system state // 2. mutate business state // 3. assemble widgets // 4. switch between several modes with if/else }

This looks fine when the app is very small. Everything is in one place, and iteration is fast.

But once the app grows to include:

  • multiple panes or pages
  • background tasks
  • history and a live viewport
  • non-trivial focus and routing
  • real system sampling or network IO

that structure becomes hard to reason about. The usual symptoms are:

  • a layout change breaks part of the state machine
  • a sampler change leaks into UI code
  • focus and scroll continuity become hard to explain after a subtree replacement

What a larger Ansiq app should be split into

A stable split in Ansiq usually looks like this:

  1. app shell / scenario
  2. domain state
  3. composition / widgets
  4. services / samplers / loaders

These layers answer different questions.

1. App shell answers “what shape does this screen have?”

This layer decides:

  • which major regions exist
  • which pane is header, body, or footer
  • which route or tab is currently active
  • which region should own focus

It should not directly perform:

  • system command parsing
  • network fetching
  • complex business-state transitions

For example, in activity_monitor, the scenario layer should decide:

  • top Tabs
  • center process table
  • bottom summary panels

It should not parse raw ps, top, or vm_stat output itself.

2. Domain state answers “what does the app know?”

This layer models:

  • which process is selected
  • which tab is active
  • which operation is selected in the OpenAPI explorer
  • which transcript entries belong to the current turn

In Ansiq, this layer should mostly use:

  • signal
  • computed
  • a small amount of effect

rather than ad hoc mutable fields and duplicated caches.

A useful test is this:

If you removed the UI, could you still describe the state and rules clearly?

If not, the domain model is probably too entangled with the rendering structure.

3. Composition / widgets answer “how is state translated into UI?”

This is one of the most important layers in a long-lived Ansiq codebase.

Its job is not to invent new business rules. Its job is to package lower-level widgets into more stable and reusable UI structures.

Examples include:

  • SessionHeader
  • ComposerBar
  • BottomPane
  • SessionTranscript

These are not runtime internals, but they should also not be rewritten in every scenario from scratch.

4. Services / samplers / loaders answer “how do we get data from outside the app?”

This layer includes:

  • system monitoring samplers
  • file loading
  • remote URL fetching
  • OpenAPI parsing
  • persistence

It should not know about widgets, focus, layout, or viewport semantics.

Its ideal shape is simple:

  • input: system environment, files, network
  • output: structured data models

That keeps the UI layer focused on presentation and interaction.

A realistic growth path

Ansiq tends to scale better if you follow this path:

Step 1: start from one small but real example

Begin with something like list_navigation:

  • one main area
  • one selected item
  • one footer

At this stage, the most important thing is to become comfortable with:

  • signal
  • view!
  • focus and routing

Step 2: extract an app shell

Once you start repeating structures like:

  • header / body / footer
  • left pane / detail pane
  • top tabs / content / bottom info

extract them into a reusable shell instead of keeping them inline in render().

Step 3: move system-specific work into services/samplers

If your app depends on:

  • ps
  • nettop
  • remote URLs
  • file parsing

those behaviors should move out of the scenario layer.

This immediately helps:

  • testing
  • reuse
  • documentation clarity

Step 4: only then add advanced runtime-facing behavior

Use these when the app truly needs them:

  • continuity keys
  • focus scopes
  • viewport / history policies
  • finer subtree replacement reasoning

Do not push them into every app by default.

A medium-sized Ansiq app might look like this:

src/ app/ mod.rs shell.rs messages.rs domain/ state.rs actions.rs widgets/ session_header.rs composer_bar.rs process_table.rs services/ sampler.rs loader.rs parser.rs scenarios/ activity_monitor.rs openapi_explorer.rs

The important part is not the folder names. The important part is the dependency direction:

  • services should not depend on widgets
  • widgets should not depend on services
  • scenarios should be the place where layers get connected

When to extract a new composition

A good rule of thumb:

Extract a composition when both are true:

  • it appears in more than one scenario
  • it has its own clear meaning, not just “some nested Boxes”

Good candidates are things like:

  • a bottom input area
  • a session header
  • a split detail explorer
  • a summary card group

The most common scaling mistakes

Mixing runtime behavior into business logic

For example:

  • focus repair
  • continuity keys
  • viewport reanchor plans

being spread through business reducers or app state.

That almost always makes the app harder to reason about.

Putting business concerns into composition widgets

If a composition starts deciding:

  • when to fetch data
  • how to parse external formats
  • which background task should retry

it is no longer just a composition widget.

Designing an oversized architecture too early

Many codebases invent large abstractions before they even have two real scenarios.

Ansiq tends to work better with the opposite path:

  • make one small example real
  • extract repeated patterns
  • then promote those patterns into widgets or runtime-facing abstractions

The real conclusion of this page

Scaling up is not about splitting files for the sake of neatness. It is about keeping this boundary clear:

  • runtime owns scheduling, layout, patching, and terminal session behavior
  • app shells own scenario structure
  • domain state owns application meaning
  • widgets/compositions translate state into interface
  • services own external data access

As long as those boundaries stay readable, a Ansiq app can grow without turning into a pile of coupled render logic.

Finish by returning to Best Practices, which compresses the architectural guidance from this page into everyday engineering rules.

Last updated on