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>
This commit is contained in:
parent
039e91b4e4
commit
0e32c0bbab
4 changed files with 75 additions and 4 deletions
70
AGENTS.md
Normal file
70
AGENTS.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# 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.
|
||||
1
CLAUDE.md
Symbolic link
1
CLAUDE.md
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
AGENTS.md
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>/ES Confluence</title>
|
||||
<title>/ESsence</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<!-- Font Awesome Free 7.3.1, from unpkg like the other two dependencies.
|
||||
Pinned deliberately: an unpinned icon set is a silent redesign on someone
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<body>
|
||||
<div id="app">
|
||||
<header>
|
||||
<div><span class="eyebrow">CME FUTURES</span><h1>/ES <strong>CONFLUENCE</strong></h1></div>
|
||||
<h1><strong>/ES</strong>sence</h1>
|
||||
<div class="status" :class="status.stream"><i></i>{{ status.stream }}<span v-if="status.delay_minutes"> ({{ status.delay_minutes }}min delay)</span> · {{ status.source || 'source' }}</div>
|
||||
</header>
|
||||
<main>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* { box-sizing:border-box; }
|
||||
body { margin:0; background:var(--bg); color:var(--fg); font:14px/1.45 "IBM Plex Mono", "SFMono-Regular", Consolas, monospace; }
|
||||
#app { min-height:100vh; padding:18px; }
|
||||
header { height:64px; display:flex; align-items:center; justify-content:space-between; border-bottom:1px solid var(--line); margin-bottom:16px; }
|
||||
header { height:44px; display:flex; align-items:center; justify-content:space-between; border-bottom:1px solid var(--line); margin-bottom:16px; }
|
||||
h1 { margin:0; font-size:22px; letter-spacing:-1px; } h1 strong { color:var(--accent); font-weight:600; }
|
||||
.eyebrow { color:var(--muted); font-size:9px; letter-spacing:2px; }
|
||||
.status { text-transform:uppercase; color:var(--muted); font-size:11px; }.status i { display:inline-block; width:7px; height:7px; border-radius:50%; background:var(--red); margin-right:8px; }.status.connected i,.status.replay i { background:var(--green); box-shadow:0 0 9px var(--green); }
|
||||
|
|
@ -51,7 +51,7 @@ aside { padding:16px; }h2 { margin:0 0 12px; color:var(--muted); font-size:11px;
|
|||
.armed-toggle { display:flex; align-items:center; gap:4px; font-size:10px; color:var(--accent); cursor:pointer; }
|
||||
.armed-toggle.off { color:var(--muted); text-decoration:line-through; }.optional { color:var(--muted); }.disabled { opacity:.45; }.score-hidden { margin-top:11px; color:var(--muted); line-height:1.25; }
|
||||
.cluster { margin:8px 0; padding:10px; border:1px solid var(--line); border-left:3px solid var(--green); background:var(--chart-bg); }.cluster.resistance { border-left-color:var(--red); }.cluster-top { display:flex; justify-content:space-between; text-transform:uppercase; font-size:10px; }.cluster-top strong { color:var(--accent); font-size:16px; }.zone { margin:4px 0; font-size:15px; }.members,.distance { color:var(--muted); font-size:9px; }.distance { margin-top:5px; }.alert-entry { white-space:pre-line; margin:8px 0; padding:9px; background:color-mix(in srgb,var(--accent) 8%,transparent); font-size:10px; }.alert-entry time { display:block; color:var(--accent); margin-bottom:4px; }
|
||||
@media (max-width:850px) { #app { padding:10px; }.chart-shell { min-width:0; }main { grid-template-columns:1fr; }.drawing-tools { flex-wrap:wrap; }.drawing-tools .line-name { width:110px; }#chart { height:55vh; min-height:360px; }aside { min-height:180px; }header { height:54px; }.chart-head { align-items:flex-start; flex-direction:column; }.timeframes { justify-content:flex-start; }.timeframes button { padding:5px 8px; } }
|
||||
@media (max-width:850px) { #app { padding:10px; }.chart-shell { min-width:0; }main { grid-template-columns:1fr; }.drawing-tools { flex-wrap:wrap; }.drawing-tools .line-name { width:110px; }#chart { height:55vh; min-height:360px; }aside { min-height:180px; }header { height:40px; }.chart-head { align-items:flex-start; flex-direction:column; }.timeframes { justify-content:flex-start; }.timeframes button { padding:5px 8px; } }
|
||||
/* --- chart comments ---------------------------------------------------- */
|
||||
.chart-comments { position:absolute; inset:0; pointer-events:none; z-index:4; }
|
||||
.chart-comment { position:absolute; max-width:210px; padding:4px 7px; border:1px solid var(--muted);
|
||||
|
|
|
|||
Loading…
Reference in a new issue