chart/AGENTS.md
Chris Amow 0e32c0bbab Rename to /ESsence and write down the house rules
The header spent a whole line on a "CME FUTURES" eyebrow that told you nothing
the chart didn't — it only ever shows /ES. Dropping it takes the header from
64px to 44px and hands the space to the chart. The title becomes /ESsence.

AGENTS.md records the rules worth keeping, chief among them that a bug should
prompt the question of whether a unit test could reasonably have caught it —
written when the answer is yes, skipped when it is a rendering or data-source
quirk, and named after the failure rather than the function.

It is AGENTS.md rather than CLAUDE.md deliberately: opencode's instruction
loader walks up looking for AGENTS.md only and never reads CLAUDE.md, and the
ask-opencode skill asks the calling agent to distil house rules by hand rather
than forwarding a file. CLAUDE.md is a symlink to it so both tools resolve to
one source of truth.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 17:10:24 -05:00

3.1 KiB

Working on this repo

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.

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.

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.