Data Lens is a maintained multi-module web application example. It opens user-selected CSV or JSON files, parses and filters rows locally, displays typed summaries, exports text, and persists only view preferences. Imported documents and parsed rows are neither sent to a server nor stored in durable browser state.
Complete First Application before reading this guide. You should already be comfortable with modules, Result, tests, topaz.lock, and the --locked flag. Building the final browser product requires Rust with the wasm32-unknown-unknown target.
Why this is a Web Application
Choose web-app when Topaz owns the model, messages, update function, and view. Choose raw web or web-worker only when an existing JavaScript host owns the UI or worker protocol. The Playground is designed for quick source experiments, not for deploying Data Lens.
The maintained source is organized by responsibility rather than contained in a single entry file:
data-lens/
topaz.toml
topaz.lock
src/main.tpz
src/model.tpz
src/session.tpz
src/view.tpz
tests/application.tpz
tests/exporting.tpz
tests/parsing.tpz
tests/transform.tpz
styles/app.css
registry/lens_labels/1.0.0/...
vendor/lens_labels/1.0.0/...
data-lens-core/
topaz.toml
src/lib.tpzdata-lens-core handles CSV and JSON parsing, filtering, sorting, summaries, and text export. The Web package manages browser messages, local-state encoding, and the view. lens_labels is a locked registry dependency copied into vendor.
Model the data before the UI
The application model keeps source text, controls, typed rows, summary state, and visible errors separate:
import data_lens_core { DataFormat, DataRow, SortMode, Summary }
export record Model {
source: string,
query: string,
activeOnly: bool,
format: DataFormat,
sortMode: SortMode,
rows: Array<DataRow>,
summary: Summary,
status: string,
error: Option<string>,
}Parsing returns a Result containing a stable error location such as JSON $[0].category: missing field. The update function stores the message in error, which the view renders as plain text. Source content is never inserted with innerHTML.
The lifecycle initializes with an in-memory sample and requests only saved view preferences:
export function init() -> WebAppStep<Model, Msg> {
let base = Model {
source: CSV_SAMPLE,
query: "",
activeOnly: false,
format: DataFormat.Csv,
sortMode: SortMode.ByName,
rows: [],
summary: emptySummary(),
status: "Ready",
error: None,
}
WebAppStep {
model: applyModel(base),
commands: [loadState("data-lens-session-load", "session", Msg.LocalCompleted)],
}
}Opening a document returns bounded UTF-8 text after explicit user selection. Saving a session writes only the query, active-only filter, and sort mode. User cancellation, malformed UTF-8, unavailable storage, quota exhaustion, and corrupt saved state remain typed application outcomes.
Shortest maintained task path
Create the registry input and sibling core package while authoring the application, then run the vendor workflow once. vendor verifies and copies the registry dependency while writing the lock file. Do not run lock first for a registry dependency that has not been vendored.
topaz vendor --root data-lens --from data-lens/registry
topaz fmt --check --root data-lens
topaz check --root data-lens --locked
topaz test data-lens/tests/parsing.tpz --root data-lens --locked
topaz test data-lens/tests/transform.tpz --root data-lens --locked
topaz test data-lens/tests/application.tpz --root data-lens --locked
topaz test data-lens/tests/exporting.tpz --root data-lens --locked
topaz dev --root data-lens --port 8000
topaz build --root data-lens --locked --release --out-dir data-lens-producttopaz run is the direct execution path for command entries containing main. Because Data Lens follows a Web lifecycle, its observable development command is topaz dev.
In the browser, verify CSV and JSON format handling, Unicode labels and filenames, filtering, both sort modes, text export, preference state management (saving, reloading, and resetting), cancellation, malformed document handling, and recovery after a failed edit. topaz dev continues serving the last successful product until the source is valid again.
Product and run without the Topaz package source
The managed product directory contains:
data-lens-product/
├── GENERATED-OUTPUT-NOTICE.txt
├── LICENSE
├── NOTICE
├── index.html
├── styles
│ └── app.css
├── topaz-app.js
├── topaz-artifact.json
├── topaz-web-capabilities.json
├── topaz-web.d.ts
├── topaz-web.js
└── topaz-web.wasmCopy the entire directory to a fresh static root and serve it over HTTP. The product does not require the package source, Topaz CLI, registry, npm runtime, CDN, or a data service. If ES modules or WASM fail under file://, use a local static HTTP server. If the build rejects the output directory because a managed file changed or another target owns it, keep the directory intact and choose a new empty --out-dir.