Commit graph

69 commits

Author SHA1 Message Date
a057341bbf Put Duplicate on the drawings list; do not require a time under the click.
The handler never ran — no event, no POST. Right-click in empty time
also hid the menu. List button uses the selected trendline.
2026-08-28 15:17:19 -05:00
c1d508cb4f Duplicate a line even when 10 source bars would pass the horizon.
Prod never saw POST /api/lines: the browser aborted the shift. Clamp to
the last owned source slot so Duplicate still creates a copy.
2026-08-28 15:01:47 -05:00
6f03560e74 Default confluence zone alerts off; Config can turn them back on.
Armed drawings and DMA bells still fire. The pref lives in user_prefs
so pushes follow it, not a display checkbox.
2026-08-27 17:09:23 -05:00
3626795e03 Say why Duplicate did nothing.
It aborted on unresolved geometry, a failed 10-bar shift, or HTTP
error with only a console.error.
2026-08-27 02:26:02 -05:00
a1567fe82a Restore the live-price animation default.
Off in Config is per-browser; do not change the default for that.
2026-08-26 03:46:02 -05:00
168352872e Default live-price animation off.
The overlay still costs a few percent. LWC LargeDashed marks the line
without it.
2026-08-26 03:45:37 -05:00
fe4e6b1ae8 Send only the live candle on a forming 1m tick.
Future calendars and HTF geometry wait until that timeframe advances.
The quote paints the header directly so Vue does not rerender the page
at 4 Hz.
2026-08-26 03:19:26 -05:00
60ffaf4657 Stop rebuilding the 1m time scale on every forming tick.
Identical future_times are now a no-op, and HTF ticks only resync lines
when a source bar is actually appended. Document the slotted-hole kink.
2026-08-26 03:12:00 -05:00
becbc0e0b4 Sample trendlines on their own timeframe and hit-test the painted line.
1m was putting a point on every bar for 30m/1d lines, which kinked at
tape holes and made hover/select miss. Clicks that were eaten by the
vertical pan gesture now select on pointer-up.
2026-08-26 01:55:47 -05:00
e94bd3d3d2 Keep both drawing-visibility rules when hiding manual levels
Resolves a conflict between hide_finer_trendlines and upstream's
drawingVisible helper. They gate on different prefs — hideLowerTfDrawings
versus hide_finer_trendlines — so both apply rather than one replacing
the other.
2026-08-26 06:14:59 +00:00
5468bbec60 fix future drawing interactions 2026-08-25 05:41:55 -05:00
90fc77df92 fix canonical future geometry 2026-08-25 05:03:42 -05:00
693d0873f8 diag delay so we can solve trendline slope problem 2026-08-24 01:43:35 -05:00
18585e42cd trendline selection issue 2026-08-23 23:25:55 -05:00
eaf118ef8b drawings from higher timeframes to lower fixes 2026-08-23 21:37:19 -05:00
913eec34ea lower time frame trendlines blocked from dma chart 2026-08-23 21:10:09 -05:00
dae60fdf67 undo 2026-08-21 03:10:31 +00:00
6e69b7337a current bar fix and ma features 2026-08-20 23:15:34 +00:00
58a64c1eeb fib levels 2026-08-19 09:09:47 +00:00
08cff3ce53 enable symbols and comments in the future whitespace 2026-08-19 08:36:31 +00:00
44cec38750 slope in tooltip 2026-08-19 05:30:16 +00:00
d9a272760b Add a Drawings layer toggle that hides lines and comments 2026-08-18 09:53:33 +00:00
fc09da7053 final pan fix 2026-08-16 23:25:36 -05:00
7f452703d3 some things only displayed optionally 2026-08-16 21:38:26 -05:00
73beae051a tweaks 2026-08-15 05:14:28 -05:00
11508e5325 tweaks 2026-08-15 04:48:49 -05:00
3adb98c1fe resizable drawing list 2026-08-15 04:33:06 -05:00
777ad4af91 version at bottom 2026-08-15 03:57:31 -05:00
aae47b6681 autoscroll optional 2026-08-14 13:58:44 -05:00
6e781ed163 options finding feature 2026-08-14 06:18:53 -05:00
a921db4610 horizontal line to start of trendline problem 2026-08-14 06:06:16 -05:00
8ab054cf67 timeframe trendline interactions 2026-08-14 04:58:08 -05:00
a09e4f1208 better select and visibility 2026-08-14 03:33:53 -05:00
087d9d9d2a changed ntfy string 2026-08-14 02:44:10 -05:00
f9e02fc3e4 fixed del key bug 2026-08-14 01:12:20 -05:00
54f4871757 key nudge for all drawing types 2026-08-13 19:02:49 -05:00
0a56d56261 reposition price level lines 2026-08-13 06:44:01 -05:00
32e25b84aa Number alerts, stamp them locally, and compact the zone list
Alerts get a number, assigned server-side and shown in both the push and the
Events list, so a notification on a phone can be matched to a row on a screen
when several fire together. It could not come from the browser: that counter
restarts on reload and differs between tabs. It is persisted next to the
cooldown state, because numbering restarting after a deploy would collide with a
phone's existing notification history — which changed that file from a list to
an object, with the loader still reading the old shape.

Pushes now carry a timestamp in the configured zone rather than the server's.
ALERT_TIMEZONE defaults to America/Chicago; containers run UTC, and a push
reading 02:14 to someone seeing 21:14 costs a translation every time. The
browser already formats its own times locally and is unchanged.

Confluence zones are one line each, ordered by price rather than by proximity,
so the list reads top to bottom the way the chart does and all of them fit on
screen — sixteen zones in 394px, about 25px each, where each previously took a
four-line block. Ordering is a display concern only: the server still returns
them nearest-first, which is what the alert path wants.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 21:44:57 -05:00
e957991242 symbol improvements 2026-08-11 19:51:39 -05:00
0bca5d3bb7 color palette improved handling 2026-08-11 19:10:12 -05:00
81045e2128 trendlines should only show numbers until selected or hover 2026-08-11 18:59:12 -05:00
c653e65d5b diagnostic capture feature 2026-08-11 16:08:32 -05:00
a395818581 Post cross-thread events through the loop, and measure how late it runs
P0 from docs/async_refactor.md. The mutating routes are sync `def`, so FastAPI
runs them in a threadpool, and they reach Runtime.broadcast through
rebuild_levels — writing asyncio.Queue directly from there. That queue is not
thread-safe: it wakes a consumer by resolving a Future, which only the loop
thread may do. A dropped wakeup means a drawing made in one browser does not
reach another until the next market tick.

broadcast now posts through call_soon_threadsafe when it is off the loop, and
publishes directly when it is on it, so the stream's own path pays nothing.

Worth being straight about the tests: the race is timing-dependent and did not
reproduce in twenty attempts — a foreign-thread put_nowait usually lands in the
ready queue before the loop sleeps, and a tick every second covers the rest.
Even asyncio's debug thread-affinity check stays quiet unless a consumer is
parked on the Future at that instant. So the tests assert the contract rather
than provoke the failure: a broadcast from a worker thread must go through
call_soon_threadsafe, one from the loop must deliver synchronously, and both
must arrive.

Also adds the loop-lag probe, which reports scheduling drift as loop_lag_ms on
/api/status. It found P1 on its first run: 19,441ms worst against 1.5ms in
steady state, which is seeding blocking the loop. "The chart feels laggy" is now
a number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:30:45 -05:00
8ca774f821 compact drawing list 2026-08-11 15:13:48 -05:00
e02f919b27 better default names 2026-08-11 14:36:58 -05:00
3b3c06a1f3 - Drag a selected trendline body to reposition the entire line.
- Duplicate and delete from the line context menu.
     - Copies shift ten bars right.
     - Default names are up and down; copies become up 2, down 2, etc.
     - Exact local data receipt time including seconds.
     - Deployment timestamp removed.
     - Test cleanup no longer deletes drawings created from your browser.
     - JWT password session flow.
2026-08-11 05:46:09 -05:00
4488c7d4b9 Fix freshness and daily date labels 2026-08-11 04:23:19 -05:00
91677127a0 Unify chart geometry and deepen 30m history 2026-08-11 04:08:02 -05:00
9ab68f6cff Add chart comments and turn Lines & levels into Drawings
A comment is a ManualLine with kind="comment", so it inherits persistence, the
shared drawing-number sequence, the list, filtering and deletion rather than
needing a parallel set of endpoints. The rule that must never bend is that
ManualLineStore.levels() excludes them: a comment reaching the level list would
join a confluence cluster and push a phone notification about a piece of text.
It is created with armed=False, and PATCH returns to_dict() rather than
to_level() for a comment, so nothing is ever handed a level-shaped comment.
Three tests cover the exclusion, the shared numbering and the kind derived for
drawings saved before comments existed.

Pinned comments carry anchor_t and anchor_p and travel with the chart; floating
ones carry x and y as fractions of the pane, hold their place through any zoom
and can be dragged. They render as DOM rather than canvas because they hold
arbitrary text, collapse to a numbered dot, and a floating one must ignore the
time scale entirely. A pinned comment scrolled out of view parks on the edge it
left, pointing back toward itself, so it never simply disappears.

Lines & levels becomes Drawings, filtered by type and by text. The text match
covers the label, the kind and the #number, so "comment", "cpi" and "7" all
narrow the list, and Delete acts on whatever the filter shows — which is what
makes deleting by type or by string one button.

Verified in a browser: placing a comment renders it at the click, clicking it
collapses it, scrolling away parks it on the edge, a floating one holds its
pixel position through a 300-bar scroll, and the filters cut 3 rows to 1 by type,
1 by text and 0 for a miss, with no console errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 15:58:21 -05:00
a13a54bc3f Drop stale bar events instead of letting the chart throw
Cannot update oldest data appeared in the console once ticks were live.
Switching timeframe races: the server answers subscribe with a fresh snapshot
from one coroutine while another is still draining bar events for the timeframe
just left, so a 1m bar can arrive after the 1h snapshot. Against the 1h series
it is older than every point in it, and Lightweight Charts throws rather than
ignoring it, which takes the app down instead of dropping one bar.

The race predates the tick feed. Level 1 made bar events about fifteen times
more frequent, which is what surfaced it.

Guarded at both ends. app.js honours the tf each event already carries and drops
anything for a timeframe no longer selected. chart.js refuses a bar older than
the series' last point whatever its origin, since a bar behind the last one has
nothing to contribute.

Verified: 36 rapid timeframe switches under a live tick feed produce zero
console errors, and calling candles.update() directly with a stale bar still
throws while the guarded updateBar() does not.

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