Start

Data Lens

Build, test, package, and run a multi-module local-first Topaz Web application offline.

Data Lens is the full Web Application Loop in one maintained application. It parses JSON and CSV in the browser, keeps rows and summaries in nominal types, normalizes multilingual labels through a locked package dependency, and renders filtering, sorting, selection, errors, and keyboard interaction without sending the dataset to a server. Topaz 5.7.0 provides this workflow in language mode topaz-5.7.

Choose the application product

Use web-app when Topaz owns application state, events, updates, and the view. The compiler checks one init/update/view lifecycle and emits a complete static product with a bounded safe DOM host. Use raw web or web-worker instead when an existing JavaScript host owns the UI or worker protocol and calls selected typed Topaz exports. The Playground is a compiler sandbox; it is not either deployment model.

Data Lens uses web-app because its model and interactions belong in Topaz.

Package shape

Start with the installed scaffold, then split parsing, transformation, model, view, and lifecycle code into modules:

BASH
topaz init --target web-app --root data-lens
mkdir -p data-lens/src data-lens/tests registry/lens_labels/1.0.0/src

The application manifest declares the effective target, managed stylesheet, and one local dependency:

TOML
[package]
name = "data_lens"
version = "0.1.0"
language = "5.7"
entry = "src/main.tpz"

[build]
target = "web-app"
deterministic = true

[web]
title = "Topaz Data Lens"
styles = ["styles/app.css"]
assets = []

[dependencies]
std = "5.7"
lens_labels = "1.0.0"

Keep the application in five source modules:

src/main.tpz       lifecycle and messages
src/model.tpz      nominal rows, summary, model, and message enum
src/parsing.tpz    JSON/CSV decoding and deterministic data-path errors
src/transform.tpz  filtering, sorting, and aggregates
src/view.tpz       typed Html<Msg> construction

The model makes state transitions explicit:

TOPAZ
export record DataRow {
  name: string,
  category: string,
  value: int,
  active: bool,
}

export record Summary {
  totalRows: int,
  activeRows: int,
  totalValue: int,
}

export enum SortMode { ByName, ByValue }

export record Model {
  source: string,
  query: string,
  activeOnly: bool,
  sortMode: SortMode,
  rows: Array<DataRow>,
  summary: Summary,
  status: string,
  error: Option<string>,
}

The entry exports exactly one compatible lifecycle. Browser details arrive as the bounded BrowserEvent; application messages remain nominal values:

TOPAZ
export function init() -> AppStep<Model, Msg> {
  AppStep { model: initialModel(), commands: [] }
}

export function update(model: Model, message: Msg, event: BrowserEvent) -> AppStep<Model, Msg> {
  match message {
    case QueryChanged => step(applyQuery(model, event.value ?? ""), [])
    case ActiveChanged => step(applyActive(model, event.checked ?? false), [])
    case Apply | Submit => step(applyModel(model), [])
    case SourceKey => {
      if (event.key ?? "") == "Enter" {
        return step(applyModel(model), [])
      }
      step(model, [])
    }
    case Clear => step(clearModel(), [focus("#source")])
  }
}

export function view(model: Model) -> Html<Msg> {
  render(model)
}

Parsing returns Result with stable paths such as JSON $[0].category: missing field. The update stores that error in the model; the view renders it as text. Do not catch it in JavaScript or inject source through innerHTML.

Lock, check, and test

Populate the local lens_labels registry package, then vendor it once. All later commands use the lockfile and vendored bytes:

BASH
topaz vendor --root data-lens --from registry
rm -rf registry
topaz check --root data-lens --locked
topaz fmt --root data-lens --check
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 doc --root data-lens --locked --out-dir data-lens-docs

Selected test files still run in package context, so imports, dependency identity, language mode, and the module root match the application.

Develop and package

Use the loopback development server while authoring, then build the managed product:

BASH
topaz dev --root data-lens --port 8000
topaz build --root data-lens --locked --release --out-dir data-lens-product

Exercise CSV and JSON samples, Unicode labels, text filtering, the active-only checkbox, both sort modes, submit and Enter, selection, clearing, and one missing-field error. A failed edit keeps the last good development product.

The final directory contains HTML, the safe application host, the checked raw Web facade, WASM, declared styles, licenses, notices, and topaz-artifact.json. Copy only data-lens-product/ to a fresh static root, remove package source and build storage, disable network access, and serve that directory. All scenarios above must continue to work: the product needs no Topaz installation, registry, source file, npm runtime, CDN, or remote data service.

Next steps