Metadata-Version: 2.5
Name: snackapp
Version: 0.2.0
Summary: Python application framework with SnackBase embedded
License: MIT
Requires-Python: >=3.12
Requires-Dist: click>=8.1.0
Requires-Dist: fastapi>=0.127.0
Requires-Dist: httpx>=0.28.1
Requires-Dist: python-dotenv>=1.0.1
Requires-Dist: snackbase>=0.12.1
Requires-Dist: uvicorn[standard]>=0.50.0
Description-Content-Type: text/markdown

# SnackApp

A Python application framework with SnackBase embedded. One install, one command, a working multi-user app.

```bash
pip install snackapp
snackapp init demo
cd demo
snackapp run
```

Open http://localhost:8000 and log in with the credentials printed on first boot.

![SnackApp home after first login](docs/quickstart.png)

See `examples/crm` for a Twenty-inspired CRM built only on the public API, and
`examples/crm/PARITY.md` for what it does and does not match.

## Contributing

The renderer lives in `frontend/` and the built bundle is committed under
`src/snackapp/static/`, so a wheel always carries a renderer. **Rebuild and
commit the bundle whenever you change `frontend/src/`:**

```bash
make build-frontend
git add src/snackapp/static
```

CI fails the `bundle` job when the committed bundle does not match the source.

For day-to-day work on the renderer, run both processes together:

```bash
make dev
```

That starts Vite with hot module replacement and `snackapp run --reload`
against it. The Python process serves a shell pointing at the dev server
(`SNACKAPP_DEV_FRONTEND=1`), so a renderer change needs no wheel rebuild, and
Vite proxies `/_sa` and `/api` to the Python process so the browser sees one
origin. `SNACKAPP_DEV_FRONTEND_URL` overrides the dev-server origin
(default `http://localhost:5173`).

Under `--reload`, edits to `pages/`, `actions/`, `models.py`, and `app.py`
restart the worker; nothing else does. `--reload` also implies
`SNACKAPP_DEV=1`, which makes a render error return its traceback to the
browser -- set `SNACKAPP_DEV=0` to opt out.

## Documentation

The reference pages live in `docs/guides/` **in this repository**, because the
tests that keep them from drifting from the code run here:

| Page | What it covers |
| --- | --- |
| [`docs/guides/snackapp.mdx`](docs/guides/snackapp.mdx) | Install, the tutorial, the state model, queries |
| [`docs/guides/snackapp-ui.mdx`](docs/guides/snackapp-ui.mdx) | Every `ui.*` primitive, its parameters, and a runnable example |
| [`docs/guides/snackapp-fields.mdx`](docs/guides/snackapp-fields.mdx) | Every field type: storage, JSON contract, operands, sortability |

`tests/unit/test_docs_reference.py` fails when a primitive or a field type has no
section, when a documented parameter or default does not match the signature, or
when the generated registry table is stale. `tests/integration/test_docs_examples.py`
writes every documented example into a freshly scaffolded project and renders it
against a live backend. So:

```bash
make docs         # regenerate the generated blocks
make check-docs   # fail if they are stale
make sync-docs    # copy the three pages into the Mintlify site (../docs)
```

The published site is [SnackApp in the SnackBase docs](https://docs.snackbase.dev/guides/snackapp);
deployment notes are in [`docs/DEPLOY.md`](docs/DEPLOY.md).

## Versioning

SnackApp is `0.2.0`. Before `1.0`, breaking changes may appear in minor releases. Pin `snackapp~=0.2` if you need a freeze.

## Publishing

`snackbase 0.12.1` and `snackapp 0.1.1` are on [PyPI](https://pypi.org/project/snackapp/). CI publishes from a `v*` tag using [trusted publishing](https://docs.pypi.org/trusted-publishers/) (`id-token: write`, no API token in the repo). Publish `snackbase` first, then `snackapp`.

`publish.yml` gates the upload: the Python suite, the renderer suite, and the
browser suite all run on the tag, and the job refuses a tag that does not match
`pyproject.toml`. `snackbase` on PyPI still predates three fixes this suite needs,
so `[tool.uv.sources]` resolves it from the sibling `../SnackBase` checkout; see
[`docs/DEPLOY.md`](docs/DEPLOY.md) for the order a release goes in.

PyPI trusted publisher settings:

| Field | snackbase | snackapp |
| --- | --- | --- |
| Owner | `lalitgehani` | `lalitgehani` |
| Repository | `SnackBase` | `snackapp` |
| Workflow | `publish.yml` | `publish.yml` |
| Environment | `pypi` | `pypi` |

Do not push a `v*` tag until those publishers are saved on pypi.org. The first production upload used a local token that is not stored in the repository or in CI.

