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:
app shell / scenariodomain statecomposition / widgetsservices / 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:
signalcomputed- 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:
SessionHeaderComposerBarBottomPaneSessionTranscript
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:
signalview!- 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:
psnettop- 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 recommended directory shape
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.rsThe important part is not the folder names. The important part is the dependency direction:
servicesshould not depend onwidgetswidgetsshould not depend onservicesscenariosshould 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.