# Choosing an App Pattern

Start with the smallest blessed template that matches the delivery shape; copy
it, then change the manifest and source. Run local validation from the
repository root: `node scripts/artifacts/validate-local --dir <appDir>`.

| Situation | Start from | Data approach |
| --- | --- | --- |
| Landing page, simple website, or app without runtime data | `artifact-draft` | No dataset until one is actually needed. |
| Chart-heavy dashboard | `react-dashboard` | Add declared live datasets as needed. |
| Dashboard with joins, derived metrics, or multiple read-only relations | `multi-source-dashboard` | Bounded live SQL datasets; prototype with catalog tools and `run_psql`. |
| Private operational action surface | `internal-tool` | Keep private UI and any server work scoped to declared artifact routes. |
| Private API-first tool | `python-api` | Declare `/_artifact/api/*`; keep viewer auth and external calls server-side. |
| Website or app with its own backend API (two parts, possibly two languages) | `web-and-api` | One artifact with `parts`: a static website part and a server API part, routed by path under one address. |
| MCP server exposed to Topline chat or Topline Coding Agent tasks | `fastmcp-server` | Exact FastMCP 3.4.7 lock, stateless JSON Streamable HTTP at `/_artifact/mcp`; connect and review after live promotion in Developer Access. |
| External webhook receiver | Platform service | Current artifact routing does not expose provider-verifiable machine ingress. |
| Customer API that must be synced on a schedule | Data Sources build, then its data pipeline artifact | Create a source, connection, connector runtime contract, materialized catalog resources, and a pipeline; with the workspace on it becomes a data pipeline artifact. Build an artifact app only as a downstream consumer. |
| Change which tables a connector syncs or when | Data pipeline artifact | Edit the `pipelineManifest` as a draft; a person publishes. See `examples/data-pipeline-artifact/EXAMPLE.md`. |
| Exact narrow count aggregate | `live-aggregate-dashboard` | `service_aggregate` binding with `aggregate.type: "count"` only. |
| A customer viewing only their own customer row | `viewer-scoped-dashboard` | Viewer binding with required session and `customers.email` filter. |

## One app, several parts

One app is always one artifact, even when it has a website and a separate
backend, or code in two languages. Declare `parts` in the manifest:

- A `static` part serves built files (`build.outputDir`) straight from storage.
  Set `spa: true` to send `index.html` for page navigations that match no file.
- A `server` part runs its own `entryPoint` (`language` node or python) on its
  own machine size (`resources` on the part, starting from the top-level ones).
- Each part's `build.command` runs in its own `dir`; the top-level `build` is
  optional and runs first, at the app root.
- Routes pick the part with `part`. Paths are exact or end in `/*`; the longest
  match wins, and unmatched paths go to the first static part. Platform routes
  (`/_artifact/data/*`, `/_artifact/knowledge/*`) are always served by Topline.
- Use relative asset URLs (`./app.js`) and hash routes (`#/reports`) so the
  same files work on the draft, live, and version addresses.

Up to four parts; at least one must be a server part. `mcp_server` apps stay
single-part.

## Data-pattern decision

Use a **live SQL dataset** for ordinary interactive reporting, joins, ratios,
averages, time series, and metrics that must reflect the source within the
declared TTL. It is the default for private dashboards.

Use a **source binding** only for its two narrow supported cases: viewer-scoped
customer rows or a count aggregate. Do not force a multi-table metric through a
binding.

Use a **precomputed dataset** when a report is dense or its bounded query is too
expensive for a request. It still exposes the same declared data route, but
reads platform-managed private JSON after warming. Declare `warmParams` for all
parameter variants that must be ready.

Use an **upstream materialized table or view** when the source-side preparation
itself needs a durable data product, shared reuse, or a cadence independent of
one app. This is artifact-adjacent/platform data work: make it available and
understood through the approved catalog/read-only path first, then have the app
query it as a bounded live dataset. Do not create or manage upstream
materialization from artifact runtime code.

Use a **Data Sources connector pipeline** when the customer is introducing a
new API or system that must be ingested repeatedly. The conversation drives the
build; the Data Source owns the connection, secrets, checkpoints and rows, and
with the Data Sources workspace on its data pipeline artifact owns which tables
sync on which schedule. Start with
`examples/customer-api-source-pipeline/EXAMPLE.md`, then
`examples/data-pipeline-artifact/EXAMPLE.md`.

## UI contract

For a live view, show the user an explicit last-updated/freshness state and
poll only at a cadence consistent with the TTL. Model empty, unavailable,
partial, stale, and error states; a local handler may return an empty response
or a clear error, never template/sample tenant rows that disguise a failed live
route.

Keep the package reproducible. The trusted builder provides the ordinary React
dashboard toolchain; do not commit or rely on `node_modules` in the app package.
