Build Applications

Data Lens

Build, test, package, and run the maintained local-first CSV and JSON Web Application.

Data Lens is the maintained multi-module Web Application example. It opens CSV or JSON chosen by the user, parses and filters the rows locally, shows typed summaries, exports text, and persists only view preferences. Imported documents and parsed rows are not sent to a server or stored as durable browser state.

Complete First Application before this guide. You should already be comfortable with modules, Result, tests, topaz.lock, and --locked. Building the final browser product also 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 for quick source experiments, not for deploying Data Lens.

The maintained source is divided by responsibility rather than placed in one entry file:

Output
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.tpz

data-lens-core owns CSV and JSON parsing, filtering, sorting, summaries, and export. The Web package owns 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, and a visible error separate:

TOPAZ
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 Result with a stable location such as JSON $[0].category: missing field. The update function stores the message in error; the view renders it as text. Source content is never inserted with innerHTML.

The lifecycle starts with an in-memory sample and requests only the saved view preferences:

TOPAZ
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 an explicit user choice. Saving a session writes only the query, active-only filter, and sort mode. 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 the sibling core package while authoring the application. Then vendor once. vendor verifies and copies the registry dependency and writes the lock; do not run lock first for a registry dependency that has not been vendored.

BASH
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-product

topaz run is the direct path for a command entry with main; Data Lens has a Web lifecycle, so its observable development command is topaz dev.

In the browser, verify both CSV and JSON, Unicode labels and filenames, filtering, both sort modes, text export, save/reload/forget preference state, cancellation, one malformed document, and recovery after a failed edit. topaz dev keeps serving the last successful product until the source is valid again.

Product and run without the Topaz package source

The managed product contains:

Output
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.wasm

Copy the entire directory to a fresh static root and serve it over HTTP. The product does not need 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.