No description
- Python 93.6%
- HTML 6.2%
- Dockerfile 0.2%
Two things, and the first is the one that cost a colleague a day. **The dev loop.** ADR-0055 made the container the default and `forge dev --native` an "escape hatch, opened by a person". The guidance every assistant reads then said the container path had "one path and no alternative" - so no assistant ever offered the other one. On Windows the repository is bind-mounted and the file events a dev server watches for do not reliably cross the WSL2 boundary, so **a saved edit changes nothing on screen**. Hot reload is most of what building an application is, and it was off, on the platform's most common machine. The decision's own reason still stands where it was aimed: somebody with an empty machine must not be refused, and the container is what makes that true. It was written as though uniformity were the goal everywhere. So the order is now stated, in the rules and in the ADR: native where node is present; **ask** to install node where it is not, saying what it is for; the container when the answer is no or the machine cannot have it. "Never step 3 first" is in the guidance in those words, because the previous wording was obeyed exactly as written. blueprints/fullstack-ts 0.9.7 -> 0.9.8, python-web 0.2.1 -> 0.2.2. **And the bar has a way back.** The chrome the platform draws into every Spark carried the person's name, the theme and the language, and no route to the platform - so a colleague on any page of any Spark, including one that 404s where nothing else is a link, could reach the catalogue only by typing the address. It is the first thing in the bar now. The address is derived from the base domain and scheme the shim already has for the preference cookie, rather than a second variable that can disagree. Absent without a domain, which is `forge dev` on a laptop - one Spark, no portal, and a bar with one fewer control beats a link to nowhere. That absence is half the test. Styled inline like the rest of the bar, with a fallback on every custom property: it is drawn into somebody else's page and an unstyled anchor in a header reads as a broken layout. [skip ci] - Actions budget. |
||
|---|---|---|
| .cursor/rules | ||
| src | ||
| tests | ||
| .gitignore | ||
| .python-version | ||
| CLAUDE.md | ||
| Dockerfile | ||
| pyproject.toml | ||
| README.md | ||
| spark.yaml | ||
| uv.lock | ||
python-web
The blueprint for a Spark that keeps its language. An application that arrived written in Python stays written in Python; the platform's four concerns are solved here, in Python's own idiom. → ADR-0046
src/app/routes.py the routes, jobs among them
src/app/templates/ Jinja2 - where the original's HTML lands
src/data/ the only place that touches the database
src/db/ connection and tables, one file per table
src/lib/identity.py require_actor() from the platform's headers
src/lib/problem.py expected failures are values, not exceptions
src/lib/source.py read / read_all, cannot_work vs failed
The example
One example, and it is the shape most imported applications actually are: a scheduled job
fetches from outside, replaces a table whole, and a page shows it.
refresh-rates → exchange_rate → /rates. Delete all three when you no longer need
them - src/db/exchange_rate.py, src/data/exchange_rate.py, the route, and
src/app/templates/rates.html.
Running it
uv sync
uv run pytest # the suite
uv run pyright # strict, and not optional - see pyproject.toml
forge dev # run it here, with a working login