Build Applications

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. Current Topaz provides this workflow, including explicit user-selected file input and text export.

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.8"
entry = "src/main.tpz"

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

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

[capabilities.web]
open_text = true
download_text = true
local_state = true

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

Keep the application in seven source modules:

src/main.tpz       lifecycle and messages
src/model.tpz      nominal rows, summary, model, and message enum
src/session.tpz    bounded JSON preferences for durable local state
src/parsing.tpz    JSON/CSV decoding and deterministic data-path errors
src/transform.tpz  filtering, sorting, and aggregates
src/exporting.tpz  deterministic UTF-8 CSV/JSON output
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 lifecycle v2. Browser details and local-data completions arrive through WebAppEvent; application messages remain nominal values:

TOPAZ
export function init() -> WebAppStep<Model, Msg> {
  WebAppStep { model: initialModel(), commands: [loadState("data-lens-session-load", "session", Msg.LocalCompleted)] }
}

export function update(model: Model, message: Msg, event: WebAppEvent) -> WebAppStep<Model, Msg> {
  match message {
    case OpenDocument => step(model, [openText("data-lens-open", ".csv,.json,text/csv,application/json", Msg.LocalCompleted)])
    case SaveSession => saveSession(model)
    case ForgetSession => step(model, [deleteState("data-lens-session-delete", "session", Msg.LocalCompleted)])
    case LocalCompleted => {
      match event {
        case LocalData(local) => completeLocalData(model, local)
        case LocalState(local) => completeLocalState(model, local)
        case Browser(_) => step(model, [])
      }
    }
    case DownloadResults => downloadResults(model)
    case Apply | Submit => step(applyModel(model), [])
    case Clear => step(clearModel(), [dom(focus("#source"))])
    case _ => updateFromBrowser(model, message, event)
  }
}

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.

OpenText supplies only a bounded UTF-8 document name, media type, byte count, and text after the user chooses it. It never supplies a path or reusable file handle. DownloadText reports DownloadStarted after browser dispatch; the application does not claim that the user completed a physical save.

Session persistence is deliberately narrower than the model. Save session writes only the query, active-only flag, and sort mode through saveState; reload restores those preferences through loadState. Source text, parsed rows, summaries, filenames, and errors are never persisted. Forget session uses deleteState, while corrupt, denied, quota, and unavailable storage results are rendered as typed application status instead of being hidden by the host.

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 test data-lens/tests/exporting.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 real CSV and JSON file selection, Unicode labels, text filtering, the active-only checkbox, both sort modes, session save/reload/forget, submit and Enter, cancellation, invalid UTF-8, clearing, one missing-field error, and downloaded CSV/JSON content. A failed edit keeps the last good development product. The maintained installed path includes a 256-row multilingual CSV and 128-row JSON document, Unicode filenames, eight sequential file and state operations, semantic summaries, focus recovery, and exact exported data.

The final directory contains HTML, the safe application host, the checked raw Web facade, WASM, declared styles, licenses, notices, and topaz-web-capabilities.json, 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.