158 lines
7.4 KiB
Markdown
158 lines
7.4 KiB
Markdown
# Working on this repo
|
|
|
|
## Read current context first
|
|
|
|
Before planning work, read [`docs/plan.md`](docs/plan.md) for the decisions
|
|
and the reasoning behind them, and
|
|
[`docs/implementation.md`](docs/implementation.md) for the dated record of what
|
|
actually went wrong and how it was resolved — that one is the faster read when
|
|
debugging, because most entries describe something that looked like one bug and
|
|
turned out to be another. Then
|
|
[`docs/NEXT_STEPS.md`](docs/NEXT_STEPS.md) for current recommendations and known
|
|
deferred fixes. Mobile interaction work also has its own detailed plan in
|
|
[`docs/mobile_enhance.md`](docs/mobile_enhance.md). The CDN-to-Vite move is
|
|
[`docs/vite_build.md`](docs/vite_build.md).
|
|
|
|
## Tests earn their place by catching a real bug
|
|
|
|
When a bug is found, ask whether a unit test could reasonably have caught it. If
|
|
yes, write that test with the fix. If no — a rendering artefact, a browser
|
|
quirk, a data-source oddity — say so and don't add one.
|
|
|
|
The bar is "would this have failed before the fix, and would it fail again if
|
|
someone reintroduced it". Tests that restate the implementation, assert
|
|
constructor defaults, or exercise paths nothing depends on are noise; they make
|
|
the suite slow to run and expensive to change, which is how a suite stops being
|
|
trusted.
|
|
|
|
What has actually paid off here: bar aggregation and bucket boundaries, the
|
|
store's replace-vs-append rules, level and alert arithmetic, parsing real
|
|
market-data payloads (fixtures are trimmed real responses, not invented), and
|
|
the invariants that would otherwise be silent — a comment must never become a
|
|
level, a tick must never overwrite a settled bar, volume must be counted once.
|
|
|
|
Name the test after the failure, not the function: `test_a_tick_cannot_overwrite
|
|
_a_settled_bar` beats `test_put`.
|
|
|
|
## Where things run
|
|
|
|
**The agent works on a remote machine over SSH. The user's browser runs on a
|
|
different machine.** Consequences, all learned the hard way:
|
|
|
|
- You cannot see the user's screen, console, or cursor. Screenshots and pasted
|
|
console output are the only window into it. Browser extensions that drive
|
|
"your" Chrome do not help — they attach to the machine the browser is on.
|
|
- Headless Chromium here renders on server hardware: different screen, window
|
|
size and device pixel ratio from the user's. "Works in my headless run" is not
|
|
evidence that it works for them. When a UI bug will not reproduce, match their
|
|
viewport and `deviceScaleFactor` explicitly before concluding anything.
|
|
- The dev stack is served to them over the network (e.g. `hera.local:8010`),
|
|
which is the same app the headless browser reaches as `http://api:8000`.
|
|
|
|
When a visual bug resists reproduction, prefer putting the numbers **on screen**
|
|
in the app over asking for another console paste — one screenshot then carries
|
|
the whole diagnosis.
|
|
|
|
## Verify UI in a real browser
|
|
|
|
Chart bugs are invisible from the outside — the API, the socket and the
|
|
frontend source can each be correct while the screen is wrong. Drive the
|
|
Playwright container against the dev stack:
|
|
|
|
```
|
|
docker exec -i chart-playwright-1 node - <<'EOF'
|
|
const { chromium } = require('/usr/lib/node_modules/playwright');
|
|
// launch with args:['--lang=en-US'] — see below
|
|
EOF
|
|
```
|
|
|
|
**Always launch Chromium with `args: ['--lang=en-US']`.** The container has no
|
|
usable locale, so Chromium reports `en-US@posix`, `Intl` throws, and the chart
|
|
renders as a blank canvas that looks exactly like a broken app.
|
|
|
|
`window.__chart` is a deliberate debug handle. Querying it separates "the data
|
|
is missing" from "the data is off-screen" — which is how a viewport bug that
|
|
three passing API checks had missed was finally found.
|
|
|
|
## Diagnostic mode
|
|
|
|
Chart geometry bugs live in the browser, which is usually on a different machine
|
|
from whoever is debugging them. Rather than asking for console pastes:
|
|
|
|
```
|
|
open the chart with ?diag=1 # remembered until ?diag=0
|
|
docker compose logs api | grep SNAPDBG
|
|
```
|
|
|
|
With it on, every snap the trendline tool computes is posted to
|
|
`/api/debug/snap` and logged server-side — the cursor's time, price and x, the
|
|
snapped time and price, how many bars were held, the first and last bar, and the
|
|
chart's width. Throttled to about one a second. It reads the client's own
|
|
numbers, which is exactly what "works in my headless run" cannot tell you.
|
|
|
|
Extend it when the next geometry puzzle appears; the endpoint takes whatever
|
|
fields `SnapReport` declares.
|
|
|
|
## Keep the two documents current
|
|
|
|
This is a running system under continual change, not a build being executed, so
|
|
both live documents decay unless updating them is part of finishing the work —
|
|
not a tidy-up afterwards.
|
|
|
|
- **`docs/plan.md`** — when a decision changes, change it here. A plan that
|
|
contradicts the code is worse than no plan, because someone believes it. If
|
|
you find a section describing behaviour that no longer exists, that is a bug
|
|
in the document; fix it in the same commit that revealed it.
|
|
- **`docs/implementation.md`** — append when a fix was not obvious. The bar is
|
|
"would this have saved me an hour": wrong theories that were measured and
|
|
killed, the evidence that settled it, the thing that looked like one bug and
|
|
was another. Not every fix. A log of trivia stops being read, and then the
|
|
useful entries go unread too.
|
|
|
|
Rule of thumb: if you needed a measurement to be sure, write down what it was.
|
|
Git records what changed; these record why it was hard.
|
|
|
|
## Direction of travel
|
|
|
|
Two live planning documents, both written to be refactored toward rather than
|
|
implemented in one go:
|
|
|
|
- `docs/async_refactor.md` — nothing blocks the event loop. P0 and P1 are done;
|
|
`/api/status` reports `loop_lag_ms`, and a rise there is the signal.
|
|
- `docs/multi_user.md` — separate people with their own drawings and alerts,
|
|
authenticated by OIDC. Read it before adding state to `Runtime`: new state is
|
|
either genuinely shared (market data) or belongs to a user, and knowing which
|
|
now is much cheaper than untangling it later.
|
|
- `docs/vite_build.md` — pin and hash the frontend, stay on Coolify, do not
|
|
split components on the way. A production Dockerfile first, then Vite;
|
|
never a root `package.json` while nixpacks is still the builder.
|
|
|
|
Do not build local user accounts. The destination is OIDC, so password storage
|
|
would be written and then deleted.
|
|
|
|
## Running tests
|
|
|
|
```
|
|
docker exec chart-api-1 sh -c "cd /app && python -m pytest -q"
|
|
```
|
|
|
|
pytest + pytest-asyncio, declared in `requirements-dev.txt`. Tests live in
|
|
`tests/`, import from `app.*`, and use `tmp_path` for anything that persists.
|
|
Async paths are driven with `asyncio.run(...)` directly rather than async test
|
|
markers.
|
|
|
|
## Things that will cost you an hour
|
|
|
|
- **Never write scratch `.py` files into the repo root.** It is bind-mounted, so
|
|
`--reload` restarts the app, and startup takes ~82 seconds. Pipe throwaway
|
|
scripts over stdin instead: `docker exec -i chart-api-1 python - <<'EOF'`.
|
|
Screenshots into `artifacts/` are safe; only `.py` triggers the reloader.
|
|
- **Dev and production keep separate drawing stores.** Dev writes
|
|
`data/manual_lines.json`; production has its own Coolify volume. A fix that
|
|
"didn't land" is often the other store.
|
|
- **Rebuild the image after touching `requirements.txt`.** The bind mount makes
|
|
source edits look live while an added dependency is simply absent.
|
|
- **A deploy resets alert cooldowns**, so production may re-alert on whatever
|
|
price is sitting on. There is no durable state yet.
|
|
- Times are epoch seconds, UTC, everywhere. Only the display is localised —
|
|
never shift the stored values.
|