Unify Trader Mode workbench shell
This commit is contained in:
+3
-1
@@ -10,7 +10,7 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
|
||||
## Last Updated
|
||||
|
||||
2026-05-18 UTC
|
||||
2026-05-20 UTC
|
||||
|
||||
## Automation Status
|
||||
|
||||
@@ -118,6 +118,7 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_workspace_refinement_slice: continuous_workspace_summary_navigation
|
||||
- latest_decision_assistant_digest_browser_slice: digest_history_browser_review_sequence
|
||||
- latest_decision_assistant_digest_preference_slice: digest_preference_profile_local_mode
|
||||
- latest_trader_mode_shell_slice: unified_workbench_left_tree_route
|
||||
- latest_decision_assistant_workflow_queue_slice: assistant_loop_queue
|
||||
- latest_decision_assistant_workspace_loop_slice: workflow_section_navigation_loop_queue
|
||||
- latest_decision_assistant_learning_focus_slice: learning_focus_queue
|
||||
@@ -800,6 +801,7 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- Workbench UX + read-only live usefulness recovery passed on 2026-05-20 after real intraday testing showed the root workbench was too long, hard to use, and runtime-dependent panels could appear ineffective. `/` now uses a left-menu/right-workspace layout with hash/localStorage section switching, default `Decision Assistant`, grouped `Readiness` and `Alerts` pages, and `/trader-mode` remains the fast-read entry. Frontend `fetchJson` now has request timeout fallback so slow review/readiness endpoints no longer keep the entire workbench stuck on Loading. Remote blockers were also recovered: PostgreSQL `pg_hba` accepted the Docker network, compose now maps `host.docker.internal`, non-root deploy env backup stays inside the remote app path, DB object ownership was reassigned to the migration/app user, the local proxy was relaunched, remote runtime env was restored with `TRADING_RUNTIME_ENABLED=1` / market and broker proxy URLs / `LIVE_TRADING_ENABLED=0`, and read-only launch readiness now treats fresh REST market data plus gap-clear state as usable while surfacing stream/news/account issues as warnings. Validation passed focused workbench/readiness/maintenance/deploy tests, secret/static script checks, Playwright screenshots at 1366x768 and 390x844 plus an Alerts hash view, full unit discovery, deploy smoke over 11 endpoints, standard remote ops smoke including `unattended_harness`, and remote `run_readonly_live_analysis_smoke.py` with `ready_for_readonly_live_analysis=true`, `must_not_trade=true`, `trade_permission_required=false`, and `submit_cancel_or_live_execution_called=false`. Current warning state: market stream may still be connecting while REST candles are fresh, news provider may be stale, and OKX account context may be unavailable; these warnings do not change `execution_allowed`, `decision_support_ready`, `real_live_ready`, risk governor, trust bucket, or operator confirmation protocol.
|
||||
- Trader Mode usefulness compression passed on 2026-05-20: `/trader-mode` and the embedded Decision Assistant watchlist now prioritize trader-language first-screen answers: `Why No Trade`, `Watch Next`, `Focus Reason`, `Data State`, and `No Candidate`. Watchlist queue cards now say why not to trade, what to watch next, and why the unique focus deserves attention instead of exposing internal reason buckets first. The stale/freshness gate no longer freezes the page just because an old candidate timestamp exists when fresh market/review timestamps are available, and read-only runtime fallback now treats market stream / broker sync degradation as warnings for analysis-only mode while strict execution/readiness gates stay unchanged. Profile filtering now preserves the server-generated readout when the current focus already matches the trader profile, avoiding fallback text that degraded `FVG retrace` into generic `confirmation`. State-change alerts now show readable trader messages instead of raw internal signatures. Validation passed 23 focused operator/static tests, `node --check`, Python compile checks, full unit discovery (`787` tests), secret static checks, script static checks, `git diff --check` with only LF/CRLF warnings, deploy smoke over 11 endpoints, standard remote ops smoke including `unattended_harness`, remote `run_readonly_live_analysis_smoke.py` with `ready_for_readonly_live_analysis=true` and `must_not_trade=true`, and gstack browser screenshots at `output/playwright/trader-mode-usefulness-readout-remote-final3-390x844.png` plus `output/playwright/trader-mode-usefulness-readout-remote-final3-1366x768.png`.
|
||||
- First real Trader Mode R2 use loop executed on 2026-05-20: used remote `/trader-mode` as the primary screen, observed `Observe Only: Watch Candidate #4610: FVG retrace`, and recorded a real `No Trade` action from the UI because Candidate `4610` remained `blocked` / `executable=false`. This correctly updated `shadow_paper_observations` instead of creating a fake paper ticket: `operator_feedback_count` increased from `1` to `2`, `shadow_observation_count` increased from `14` to `15`, while `paper_ticket_count=0` and `paper_execution_count=0` stayed unchanged. Saved after-close draft note `operator_notes.id=3` with scope `weekly_review_draft`; `/trading/journal/generate` returned `created=0`, so `weekly_review_entry_count` remains `0` until a real execution-derived journal entry exists. Evidence screenshot saved at `output/playwright/trader-mode-r2-real-use-loop-390x844.png`. Current R2 gate remains `paper_ticket_sample_gate`; do not mark R2 completed.
|
||||
- Trader Mode unified shell closure passed on 2026-05-20: `/trader-mode` now resolves to the same left-menu/right-workspace `index.html` shell instead of the standalone page, with the Trader Mode section active from the route and the sidebar `Trader Mode` tree item selected. The embedded Trader Mode surface keeps the fast-read status, live readout, fast queue, freshness, focus, action buttons, after-close draft, R2 evidence, and read-only boundary controls, while the generic workbench filter bar is hidden only for this section. `trader_mode.js` is scoped so it can run inside the workbench without leaking globals into `app.js`; keyboard shortcuts and auto-refresh are active only while the Trader Mode section is visible. Remote validation passed deploy smoke over 11 endpoints, gstack browser checks on `http://192.168.3.90:8000/trader-mode` (`bodySection=trader-mode`, active nav=`Trader Mode`, sidebar present, generic controls hidden, loaded status=`Observe Only`), `run_readonly_live_analysis_smoke.py`, and `run_remote_ops_smoke.py`. Evidence screenshot saved at `output/playwright/trader-mode-unified-shell-1366x768-loaded.png`. No change was made to `execution_allowed`, `decision_support_ready`, `real_live_ready`, risk governor, trust bucket, or operator confirmation protocol.
|
||||
- Current assisted-analysis next gate: keep R1/R2 focused on `decision_support_ready` trust/sample/quality evidence and shadow/paper/operator feedback collection. OKX trade-permission sandbox smoke is deferred until the project explicitly re-enters execution adapter or real-trading preparation.
|
||||
|
||||
## Exact Next Step
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
- [x] R2 shadow / paper continuous validation ledger starter slice implemented and deployed
|
||||
- [x] R2 sample collection closure slice implemented and deployed: persistent daily observation ledger, runtime/API/cockpit observation actions, validation count fields, and remote sample writeback
|
||||
- [x] R2 evidence backfill audit and guarded shadow-observation backfill complete: remote dry-run found 10 eligible historical blocked-candidate days, remote apply created observations `1449` through `1458`, and the live gate moved from `shadow_candidate_day_coverage` to `paper_ticket_sample_gate`
|
||||
- [ ] R2 evidence window still collecting: live state has `observed_shadow_day_count=12`, `shadow_observation_count=13`, `shadow_observation_day_count=11`, `paper_ticket_count=0`, `paper_execution_count=0`, `operator_feedback_count=1`, `weekly_review_entry_count=0`, and Trader Mode now surfaces these missing evidence types without marking `decision_support_ready=true`
|
||||
- [ ] R2 evidence window still collecting: live state has `observed_shadow_day_count=12`, `shadow_observation_count=15`, `shadow_observation_day_count=11`, `paper_ticket_count=0`, `paper_execution_count=0`, `operator_feedback_count=2`, `weekly_review_entry_count=0`, and Trader Mode now surfaces these missing evidence types without marking `decision_support_ready=true`
|
||||
- [x] Recover remote API responsiveness blocker: no-proxy `/health` returned 200, SSH command execution recovered, and standard remote ops smoke passed all 9 checks including `unattended_harness`
|
||||
- [x] Restore OKX read-only market/broker proxy before continuing R2 sampling: remote no-proxy readiness now has `market_truth_ready`, `broker_state_ready`, and `runtime_ready`; standard remote ops smoke passed
|
||||
- [x] Read-only live analysis launch closure complete: `live_analysis_assistant_ready=true`, cockpit boundary/health summary visible, `decision_support_ready=false`, `execution_assist_ready=false`, `real_live_ready=false`, deploy smoke passed, `run_readonly_live_analysis_smoke.py` passed
|
||||
@@ -162,6 +162,7 @@
|
||||
- [x] Low-risk sensitive credential cleanup complete: removed tracked expect scripts with embedded password sends, switched rollback docs/checker to `deploy_with_password.py` + runtime env, added redacted `run_secret_static_checks.py`, wired it into script static checks and Trading Delivery Checklist, and kept all real remote/runtime credentials unchanged
|
||||
- [x] Workbench frontend split-navigation closure: converted the long root workbench into a left-menu/right-workspace layout, added hash/localStorage section switching, added frontend request timeout fallback so slow review/readiness APIs no longer freeze the page, and validated desktop/narrow rendering
|
||||
- [x] Live usefulness blocker recovery: root workbench split-navigation is usable, slow frontend requests time out instead of freezing the page, remote DB/compose/deploy/migration/proxy/runtime blockers were recovered, standard remote ops smoke including `unattended_harness` passed, and readonly live analysis smoke passed without enabling order submission
|
||||
- [x] Trader Mode unified workbench shell closure: `/trader-mode` now opens inside the same left-tree/right-workspace framework with the Trader Mode tree item active, hides the generic workbench filters only for this section, scopes Trader Mode JS to avoid app globals/hidden-section shortcuts, deploy smoke passed, remote gstack browser verification passed, readonly live analysis smoke passed, and remote ops smoke passed
|
||||
- [ ] Next trader-usefulness refinement: compress Trader Mode / watchlist language around why no trade, what to watch next, unique focus quality, stale data, and no-candidate states so the first screen answers live operator questions faster
|
||||
- [ ] R2 next collection action after readonly launch: wait for or surface a real executable candidate, then build shadow paper ticket / confirm paper execution / record operator feedback / add weekly review evidence without changing execution/live gates
|
||||
- [x] R3 OKX sandbox preflight starter implemented and deployed: signed dry-run request previews, idempotency key preview, error normalization catalog, `/trading/okx-sandbox-preflight`, live-readiness summary, cockpit surface, deploy smoke, remote ops smoke, and remote field/UI smoke
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
+1
-1
@@ -1299,7 +1299,7 @@ def _resolve_static_file(path: str) -> Optional[Path]:
|
||||
if path in {"/training", "/training.html"}:
|
||||
return STATIC_DIR / "training.html"
|
||||
if path in {"/trader-mode", "/trader-mode.html"}:
|
||||
return STATIC_DIR / "trader_mode.html"
|
||||
return STATIC_DIR / "index.html"
|
||||
relative = path.removeprefix("/static/")
|
||||
resolved = (STATIC_DIR / relative).resolve()
|
||||
try:
|
||||
|
||||
+11
-1
@@ -170,6 +170,14 @@ function getWorkbenchSection(sectionId) {
|
||||
return workbenchSections.find((section) => section.dataset.workbenchSection === sectionId) || null;
|
||||
}
|
||||
|
||||
function getPathWorkbenchSection() {
|
||||
const normalizedPath = window.location.pathname.replace(/\/+$/, '') || '/';
|
||||
if (normalizedPath === '/trader-mode' || normalizedPath === '/trader-mode.html') {
|
||||
return 'trader-mode';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function activateWorkbenchSection(sectionId, options = {}) {
|
||||
if (!workbenchSections.length) return;
|
||||
const fallback = getWorkbenchSection('decision-assistant') || workbenchSections[0];
|
||||
@@ -180,6 +188,7 @@ function activateWorkbenchSection(sectionId, options = {}) {
|
||||
section.classList.toggle('is-active', isActive);
|
||||
section.toggleAttribute('hidden', !isActive);
|
||||
});
|
||||
document.body.dataset.workbenchSection = activeId;
|
||||
workbenchNavButtons.forEach((button) => {
|
||||
const isActive = button.dataset.workbenchTarget === activeId;
|
||||
button.classList.toggle('is-active', isActive);
|
||||
@@ -195,6 +204,7 @@ function activateWorkbenchSection(sectionId, options = {}) {
|
||||
if (options.scrollToTop && workbenchContent) {
|
||||
workbenchContent.scrollTo({ top: 0, behavior: options.smooth ? 'smooth' : 'auto' });
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('workbench-section-change', { detail: { sectionId: activeId } }));
|
||||
}
|
||||
|
||||
function initializeWorkbenchNavigation() {
|
||||
@@ -209,7 +219,7 @@ function initializeWorkbenchNavigation() {
|
||||
});
|
||||
});
|
||||
const hashSection = window.location.hash ? window.location.hash.replace(/^#\/?/, '') : '';
|
||||
activateWorkbenchSection(hashSection || getStoredWorkbenchSection() || 'decision-assistant', {
|
||||
activateWorkbenchSection(hashSection || getPathWorkbenchSection() || getStoredWorkbenchSection() || 'decision-assistant', {
|
||||
persist: false,
|
||||
scrollToTop: false,
|
||||
});
|
||||
|
||||
+136
-2
@@ -13,7 +13,7 @@
|
||||
<p>Live plan, candidate, ticket, position, and review flow.</p>
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<a class="nav-link" href="/trader-mode">Trader Mode</a>
|
||||
<a class="nav-link" href="/trader-mode">Open Trader Mode</a>
|
||||
<div id="healthStatus" class="health-card">Loading workspace...</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -25,7 +25,7 @@
|
||||
<div id="workbenchViewLabel" class="workbench-view-label">Decision Assistant</div>
|
||||
</div>
|
||||
<nav id="workbenchNav" class="workbench-nav">
|
||||
<a class="workbench-nav-link workbench-nav-primary" href="/trader-mode">Trader Mode</a>
|
||||
<button class="workbench-nav-primary" type="button" data-workbench-target="trader-mode">Trader Mode</button>
|
||||
<button type="button" data-workbench-target="overview">Overview</button>
|
||||
<button type="button" data-workbench-target="decision-assistant">Decision Assistant</button>
|
||||
<button type="button" data-workbench-target="trading-status">Trading Status</button>
|
||||
@@ -98,6 +98,139 @@
|
||||
</section>
|
||||
|
||||
<main id="workbenchMain" class="grid workbench-main">
|
||||
<section class="workspace-section trader-mode-workspace-section" data-workbench-section="trader-mode" data-workbench-label="Trader Mode">
|
||||
<div class="panel trader-mode-workspace-header">
|
||||
<div>
|
||||
<h2>Trader Mode</h2>
|
||||
<p>Read-only live brief for fast intraday review.</p>
|
||||
</div>
|
||||
<div id="traderModeHealth" class="health-card">Loading Trader Mode...</div>
|
||||
</div>
|
||||
|
||||
<div class="trader-mode-shell trader-mode-shell-embedded">
|
||||
<section id="traderChangeAlert" class="trader-change-alert hidden"></section>
|
||||
|
||||
<section id="traderStatusPanel" class="trader-status-panel">
|
||||
<div>
|
||||
<div class="metric">Status</div>
|
||||
<div id="traderStatusLabel" class="trader-status-label">Loading</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="metric">Decision Now</div>
|
||||
<div id="traderConclusion" class="trader-conclusion">Waiting for live brief.</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="metric">Next Action</div>
|
||||
<div id="traderNextAction" class="trader-next-action">n/a</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="trader-mode-band trader-readout-band">
|
||||
<h2>Live Readout</h2>
|
||||
<div id="traderReadoutPanel" class="trader-readout-grid"></div>
|
||||
</section>
|
||||
|
||||
<section class="trader-mode-band">
|
||||
<h2>Fast Queue</h2>
|
||||
<div id="traderQueuePanel" class="trader-queue-panel"></div>
|
||||
</section>
|
||||
|
||||
<section class="controls trader-mode-controls">
|
||||
<label>Profile
|
||||
<select id="traderProfileSelect"></select>
|
||||
</label>
|
||||
<label>Profile Name <input id="traderProfileName" type="text" value="BTC London" /></label>
|
||||
<label>Instrument ID <input id="traderInstrumentId" type="number" value="1" min="1" /></label>
|
||||
<label>Timeframe <input id="traderTimeframe" type="text" value="1m" /></label>
|
||||
<label>Sessions <input id="traderSessionFilter" type="text" value="LONDON,NY_AM" /></label>
|
||||
<label>Setups <input id="traderSetupFilter" type="text" value="SWEEP,MSS,FVG" /></label>
|
||||
<label>Auto Refresh
|
||||
<select id="traderAutoRefreshSeconds">
|
||||
<option value="0">Off</option>
|
||||
<option value="5" selected>5s</option>
|
||||
<option value="10">10s</option>
|
||||
<option value="30">30s</option>
|
||||
</select>
|
||||
</label>
|
||||
<button id="traderRefreshBtn" type="button">Refresh</button>
|
||||
<button id="traderSaveProfileBtn" type="button">Save Profile</button>
|
||||
<button id="traderResetProfileBtn" type="button">Reset Profile</button>
|
||||
<button id="traderAlertBtn" type="button">Enable Alerts</button>
|
||||
<div id="traderLastUpdated" class="last-updated">Waiting for first refresh</div>
|
||||
<div id="traderKeyboardStatus" class="last-updated">Auto refresh active</div>
|
||||
</section>
|
||||
|
||||
<section class="trader-mode-band">
|
||||
<div>
|
||||
<h2>Freshness</h2>
|
||||
<div id="traderFreshnessGrid" class="trader-metric-grid">
|
||||
<div class="trader-metric"><div class="metric">Market Last Updated</div></div>
|
||||
<div class="trader-metric"><div class="metric">Review Packet Updated</div></div>
|
||||
<div class="trader-metric"><div class="metric">Runtime Online</div></div>
|
||||
<div class="trader-metric"><div class="metric">Stale Seconds</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="trader-mode-band trader-mode-two-col">
|
||||
<div>
|
||||
<h2>Chart Bridge</h2>
|
||||
<div id="traderChartBridgePanel" class="trader-metric-grid"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Duty Panel</h2>
|
||||
<div id="traderOpsDutyPanel" class="trader-metric-grid"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="trader-mode-band trader-mode-two-col">
|
||||
<div>
|
||||
<h2>Focus</h2>
|
||||
<div id="traderFocusPanel" class="trader-focus-panel"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Three Reasons</h2>
|
||||
<ol id="traderReasonList" class="trader-reason-list"></ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="trader-mode-band">
|
||||
<h2>Recovery</h2>
|
||||
<div id="traderRecoveryPanel" class="trader-recovery-panel"></div>
|
||||
</section>
|
||||
|
||||
<section class="trader-mode-band">
|
||||
<h2>Record Action</h2>
|
||||
<div id="traderActionBar" class="trader-action-bar">
|
||||
<button data-trader-action="agree" type="button">Agree</button>
|
||||
<button data-trader-action="disagree" type="button">Disagree</button>
|
||||
<button data-trader-action="no_trade" type="button">No Trade</button>
|
||||
<button data-trader-action="need_more_evidence" type="button">Need More Evidence</button>
|
||||
<button data-trader-action="write_quick_note" type="button">Write Quick Note</button>
|
||||
</div>
|
||||
<textarea id="traderQuickNote" class="trader-quick-note" rows="3" placeholder="Optional quick note for this live brief."></textarea>
|
||||
<div id="traderActionStatus" class="metric">No action recorded in this browser session.</div>
|
||||
</section>
|
||||
|
||||
<section class="trader-mode-band">
|
||||
<h2>After-Close Draft</h2>
|
||||
<div id="traderPostMarketDraftPanel" class="trader-post-market-draft"></div>
|
||||
<button id="traderSaveDraftBtn" type="button">Save Draft Note</button>
|
||||
</section>
|
||||
|
||||
<section class="trader-mode-band trader-mode-two-col">
|
||||
<div>
|
||||
<h2>R2 Evidence Window</h2>
|
||||
<div id="traderEvidenceGrid" class="trader-metric-grid"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Boundary</h2>
|
||||
<div id="traderBoundaryBadges" class="trader-boundary-badges"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel panel-wide workspace-section" data-workbench-section="overview" data-workbench-label="Overview">
|
||||
<h2>Overview</h2>
|
||||
<div id="overviewCards" class="cards overview-cards"></div>
|
||||
@@ -188,5 +321,6 @@
|
||||
</div>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script src="/static/trader_mode.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -156,6 +156,10 @@ body {
|
||||
.workbench-content {
|
||||
min-width: 0;
|
||||
}
|
||||
body[data-workbench-section="trader-mode"] #workbenchContent > .controls,
|
||||
body[data-workbench-section="trader-mode"] #workbenchContent > .filters-bar {
|
||||
display: none;
|
||||
}
|
||||
.workbench-content .controls,
|
||||
.workbench-content .filters-bar {
|
||||
margin-left: 0;
|
||||
@@ -404,6 +408,29 @@ th { color: #9db0d3; }
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.trader-mode-workspace-section {
|
||||
min-width: 0;
|
||||
}
|
||||
.trader-mode-workspace-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.trader-mode-workspace-header h2 {
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
.trader-mode-workspace-header p {
|
||||
margin: 0;
|
||||
color: #93a4c3;
|
||||
}
|
||||
.trader-mode-shell-embedded {
|
||||
padding: 0;
|
||||
}
|
||||
.trader-mode-shell .trader-mode-controls {
|
||||
margin: 0;
|
||||
}
|
||||
.trader-change-alert {
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #38bdf8;
|
||||
@@ -639,6 +666,7 @@ th { color: #9db0d3; }
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.topbar,
|
||||
.trader-mode-workspace-header,
|
||||
.trader-status-panel,
|
||||
.trader-mode-two-col {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
+47
-5
@@ -1,3 +1,4 @@
|
||||
(() => {
|
||||
const traderHealth = document.getElementById('traderModeHealth');
|
||||
const traderProfileSelect = document.getElementById('traderProfileSelect');
|
||||
const traderProfileName = document.getElementById('traderProfileName');
|
||||
@@ -33,6 +34,10 @@ const traderSaveDraftBtn = document.getElementById('traderSaveDraftBtn');
|
||||
const traderEvidenceGrid = document.getElementById('traderEvidenceGrid');
|
||||
const traderBoundaryBadges = document.getElementById('traderBoundaryBadges');
|
||||
|
||||
if (!traderHealth || !traderStatusPanel || !traderActionBar || !traderProfileSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
const TRADER_SIGNATURE_KEY = 'ai_ict_trader_mode_signature_v1';
|
||||
const TRADER_ALERTS_KEY = 'ai_ict_trader_mode_alerts_enabled_v1';
|
||||
const TRADER_PROFILE_KEY = 'ai_ict_trader_mode_profile_key_v1';
|
||||
@@ -88,6 +93,7 @@ const DEFAULT_TRADER_PROFILES = [
|
||||
|
||||
let traderAutoRefreshTimer = null;
|
||||
let traderAutoRefreshPaused = false;
|
||||
let traderModeInitialized = false;
|
||||
let latestTraderPayload = null;
|
||||
let traderAlertsEnabled = localStorage.getItem(TRADER_ALERTS_KEY) === '1';
|
||||
|
||||
@@ -108,6 +114,11 @@ function createElement(tag, className, text) {
|
||||
return element;
|
||||
}
|
||||
|
||||
function isTraderModeActive() {
|
||||
const section = traderStatusPanel.closest('[data-workbench-section]');
|
||||
return !section || !section.hidden || section.classList.contains('is-active');
|
||||
}
|
||||
|
||||
function queryString(params) {
|
||||
const search = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
@@ -868,12 +879,22 @@ function configureTraderAutoRefresh() {
|
||||
if (traderAutoRefreshTimer) clearInterval(traderAutoRefreshTimer);
|
||||
traderAutoRefreshTimer = null;
|
||||
const seconds = Number(traderAutoRefreshSeconds.value || 0);
|
||||
if (!isTraderModeActive()) {
|
||||
setText(traderKeyboardStatus, 'Auto refresh waiting for Trader Mode');
|
||||
return;
|
||||
}
|
||||
if (!traderAutoRefreshPaused && seconds > 0) {
|
||||
traderAutoRefreshTimer = setInterval(refreshTraderMode, seconds * 1000);
|
||||
}
|
||||
setText(traderKeyboardStatus, traderAutoRefreshPaused ? 'Auto refresh paused' : 'Auto refresh active');
|
||||
}
|
||||
|
||||
function stopTraderAutoRefresh() {
|
||||
if (traderAutoRefreshTimer) clearInterval(traderAutoRefreshTimer);
|
||||
traderAutoRefreshTimer = null;
|
||||
setText(traderKeyboardStatus, 'Auto refresh waiting for Trader Mode');
|
||||
}
|
||||
|
||||
function toggleTraderAutoRefresh() {
|
||||
traderAutoRefreshPaused = !traderAutoRefreshPaused;
|
||||
const shortcutAction = 'pause_or_resume_auto_refresh';
|
||||
@@ -917,6 +938,7 @@ function configureKeyboardShortcuts() {
|
||||
}
|
||||
document.addEventListener('keydown', async (event) => {
|
||||
if (event.ctrlKey || event.altKey || event.metaKey || isTypingTarget(event.target)) return;
|
||||
if (!isTraderModeActive()) return;
|
||||
const key = event.key.toLowerCase();
|
||||
try {
|
||||
if (key === 'r') {
|
||||
@@ -973,8 +995,28 @@ traderSessionFilter.addEventListener('change', refreshTraderMode);
|
||||
traderSetupFilter.addEventListener('change', refreshTraderMode);
|
||||
traderAutoRefreshSeconds.addEventListener('change', configureTraderAutoRefresh);
|
||||
|
||||
setText(traderAlertBtn, traderAlertsEnabled ? 'Disable Alerts' : 'Enable Alerts');
|
||||
renderProfileOptions();
|
||||
configureKeyboardShortcuts();
|
||||
configureTraderAutoRefresh();
|
||||
refreshTraderMode();
|
||||
function initializeTraderMode() {
|
||||
if (!isTraderModeActive()) {
|
||||
stopTraderAutoRefresh();
|
||||
return;
|
||||
}
|
||||
if (!traderModeInitialized) {
|
||||
traderModeInitialized = true;
|
||||
setText(traderAlertBtn, traderAlertsEnabled ? 'Disable Alerts' : 'Enable Alerts');
|
||||
renderProfileOptions();
|
||||
configureKeyboardShortcuts();
|
||||
}
|
||||
configureTraderAutoRefresh();
|
||||
refreshTraderMode();
|
||||
}
|
||||
|
||||
window.addEventListener('workbench-section-change', (event) => {
|
||||
if (event.detail?.sectionId === 'trader-mode') {
|
||||
initializeTraderMode();
|
||||
} else {
|
||||
stopTraderAutoRefresh();
|
||||
}
|
||||
});
|
||||
|
||||
initializeTraderMode();
|
||||
})();
|
||||
|
||||
@@ -22,7 +22,7 @@ class ApiStaticServerTests(unittest.TestCase):
|
||||
|
||||
def test_trader_mode_route_is_treated_as_static(self) -> None:
|
||||
self.assertTrue(_should_serve_static("/trader-mode"))
|
||||
self.assertTrue(str(_resolve_static_file("/trader-mode")).replace("\\", "/").endswith("src/web/trader_mode.html"))
|
||||
self.assertTrue(str(_resolve_static_file("/trader-mode")).replace("\\", "/").endswith("src/web/index.html"))
|
||||
|
||||
def test_static_asset_path_is_resolved(self) -> None:
|
||||
self.assertTrue(_should_serve_static("/static/app.js"))
|
||||
@@ -36,7 +36,7 @@ class ApiStaticServerTests(unittest.TestCase):
|
||||
self.assertIsNotNone(index_file)
|
||||
index_html = index_file.read_text(encoding="utf-8")
|
||||
|
||||
for asset in ("/static/styles.css", "/static/app.js"):
|
||||
for asset in ("/static/styles.css", "/static/app.js", "/static/trader_mode.js"):
|
||||
with self.subTest(asset=asset):
|
||||
self.assertIn(asset, index_html)
|
||||
asset_file = _resolve_static_file(asset)
|
||||
@@ -60,7 +60,7 @@ class ApiStaticServerTests(unittest.TestCase):
|
||||
self.assertIsNotNone(trader_mode_file)
|
||||
trader_mode_html = trader_mode_file.read_text(encoding="utf-8")
|
||||
|
||||
for asset in ("/static/styles.css", "/static/trader_mode.js"):
|
||||
for asset in ("/static/styles.css", "/static/app.js", "/static/trader_mode.js"):
|
||||
with self.subTest(asset=asset):
|
||||
self.assertIn(asset, trader_mode_html)
|
||||
asset_file = _resolve_static_file(asset)
|
||||
@@ -131,6 +131,9 @@ class ApiStaticServerTests(unittest.TestCase):
|
||||
for expected in (
|
||||
'class="workbench-layout"',
|
||||
'id="workbenchNav"',
|
||||
'data-workbench-target="trader-mode"',
|
||||
'data-workbench-section="trader-mode"',
|
||||
'id="traderStatusPanel"',
|
||||
'data-workbench-target="decision-assistant"',
|
||||
'data-workbench-section="decision-assistant"',
|
||||
'data-workbench-section="readiness"',
|
||||
@@ -141,6 +144,8 @@ class ApiStaticServerTests(unittest.TestCase):
|
||||
for expected in (
|
||||
"activateWorkbenchSection",
|
||||
"initializeWorkbenchNavigation",
|
||||
"getPathWorkbenchSection",
|
||||
"workbench-section-change",
|
||||
"aiIctActiveWorkbenchSection",
|
||||
"request_timeout",
|
||||
"AbortController",
|
||||
|
||||
Reference in New Issue
Block a user