diff --git a/mac/src/App/ContentView.swift b/mac/src/App/ContentView.swift index 281b5c0..3b282d6 100644 --- a/mac/src/App/ContentView.swift +++ b/mac/src/App/ContentView.swift @@ -21,6 +21,7 @@ struct ContentView: View { @AppStorage("mt_enable_auto_refresh") private var storedEnableAutoRefresh = false @AppStorage("mt_refresh_sec") private var storedRefreshSeconds = 60 @State private var showSetupSheet = false + @State private var showHelpSheet = false @State private var setupDraft = UserSetupPreferences.default #if DEBUG @State private var showDebugPanel = false @@ -54,6 +55,9 @@ struct ContentView: View { .onDisappear { host.stop() } .toolbar { ToolbarItemGroup(placement: .primaryAction) { + Button("Help") { + showHelpSheet = true + } Button("Setup") { syncStateFromSharedSettingsFileIfAvailable() setupDraft = storedWebPreferences.normalized().setupDefaults @@ -74,6 +78,9 @@ struct ContentView: View { .sheet(isPresented: $showSetupSheet) { setupSheet } + .sheet(isPresented: $showHelpSheet) { + HelpSheetView() + } } private var sharedSettingsURL: URL { @@ -330,6 +337,64 @@ struct ContentView: View { #endif } +private struct HelpSheetView: View { + @Environment(\.dismiss) private var dismiss + + private var helpFileURL: URL? { + let fm = FileManager.default + let candidates = [ + Bundle.main.url(forResource: "help", withExtension: "html", subdirectory: "Help"), + Bundle.main.url(forResource: "help", withExtension: "html"), + Bundle.main.resourceURL?.appendingPathComponent("Help/help.html"), + Bundle.main.resourceURL?.appendingPathComponent("help.html"), + ].compactMap { $0 } + return candidates.first(where: { fm.fileExists(atPath: $0.path) }) + } + + var body: some View { + NavigationStack { + Group { + if let helpFileURL { + HelpDocumentWebView(fileURL: helpFileURL) + } else { + VStack(alignment: .leading, spacing: 8) { + Text("Help file not found.") + .font(.title3.weight(.semibold)) + Text("Expected bundled file: help.html") + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .padding(24) + } + } + .navigationTitle("Help & Quick Start") + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("Done") { dismiss() } + } + } + } + .frame(minWidth: 860, minHeight: 640) + } +} + +private struct HelpDocumentWebView: NSViewRepresentable { + let fileURL: URL + + func makeNSView(context: Context) -> WKWebView { + let webView = WKWebView(frame: .zero) + webView.allowsBackForwardNavigationGestures = true + webView.loadFileURL(fileURL, allowingReadAccessTo: fileURL.deletingLastPathComponent()) + return webView + } + + func updateNSView(_ webView: WKWebView, context: Context) { + if webView.url != fileURL { + webView.loadFileURL(fileURL, allowingReadAccessTo: fileURL.deletingLastPathComponent()) + } + } +} + #Preview { ContentView() } diff --git a/mac/src/App/EmbeddedBackend/WebBackend b/mac/src/App/EmbeddedBackend/WebBackend index 4819df2..c31469b 100755 Binary files a/mac/src/App/EmbeddedBackend/WebBackend and b/mac/src/App/EmbeddedBackend/WebBackend differ diff --git a/mac/src/App/Help/help.html b/mac/src/App/Help/help.html new file mode 100644 index 0000000..027daa5 --- /dev/null +++ b/mac/src/App/Help/help.html @@ -0,0 +1,196 @@ + + + + + + Help & Quick Start + + + +
+

Help & Quick Start

+

A quick guide to reading signals, choosing settings, and troubleshooting.

+ +
+

Start in 60 Seconds

+
    +
  1. Set a symbol like AAPL or BTC-USD.
  2. +
  3. Choose Timeframe (1d is a good default) and Period (6mo).
  4. +
  5. Keep Ignore potentially live last bar enabled.
  6. +
  7. Review trend status and chart markers.
  8. +
  9. Use Export to download CSV/PDF outputs.
  10. +
+
+ +
+

Signal Rules

+

+ real_bull close above previous high + real_bear close below previous low + fake close inside previous range +

+ +
+ +
+

Data Settings

+

Core fields

+ +

Optional filters

+ +
+ +
+

Chart Reading

+ +
+ +
+

Troubleshooting

+ +
+
+ + diff --git a/mac/src/MacShell.xcodeproj/project.pbxproj b/mac/src/MacShell.xcodeproj/project.pbxproj index 1804bec..045f0a6 100644 --- a/mac/src/MacShell.xcodeproj/project.pbxproj +++ b/mac/src/MacShell.xcodeproj/project.pbxproj @@ -30,17 +30,17 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - EAB6606E2F3FD5C000ED41BA /* ManeshTraderMac */ = { + EAB6606E2F3FD5C000ED41BA /* App */ = { isa = PBXFileSystemSynchronizedRootGroup; path = App; sourceTree = ""; }; - EAB6607C2F3FD5C100ED41BA /* ManeshTraderMacTests */ = { + EAB6607C2F3FD5C100ED41BA /* AppTests */ = { isa = PBXFileSystemSynchronizedRootGroup; path = AppTests; sourceTree = ""; }; - EAB660862F3FD5C100ED41BA /* ManeshTraderMacUITests */ = { + EAB660862F3FD5C100ED41BA /* AppUITests */ = { isa = PBXFileSystemSynchronizedRootGroup; path = AppUITests; sourceTree = ""; @@ -75,9 +75,9 @@ EAB660632F3FD5C000ED41BA = { isa = PBXGroup; children = ( - EAB6606E2F3FD5C000ED41BA /* ManeshTraderMac */, - EAB6607C2F3FD5C100ED41BA /* ManeshTraderMacTests */, - EAB660862F3FD5C100ED41BA /* ManeshTraderMacUITests */, + EAB6606E2F3FD5C000ED41BA /* App */, + EAB6607C2F3FD5C100ED41BA /* AppTests */, + EAB660862F3FD5C100ED41BA /* AppUITests */, EAB6606D2F3FD5C000ED41BA /* Products */, ); sourceTree = ""; @@ -108,7 +108,7 @@ dependencies = ( ); fileSystemSynchronizedGroups = ( - EAB6606E2F3FD5C000ED41BA /* ManeshTraderMac */, + EAB6606E2F3FD5C000ED41BA /* App */, ); name = ManeshTraderMac; packageProductDependencies = ( @@ -131,7 +131,7 @@ EAB6607B2F3FD5C100ED41BA /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( - EAB6607C2F3FD5C100ED41BA /* ManeshTraderMacTests */, + EAB6607C2F3FD5C100ED41BA /* AppTests */, ); name = ManeshTraderMacTests; packageProductDependencies = ( @@ -154,7 +154,7 @@ EAB660852F3FD5C100ED41BA /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( - EAB660862F3FD5C100ED41BA /* ManeshTraderMacUITests */, + EAB660862F3FD5C100ED41BA /* AppUITests */, ); name = ManeshTraderMacUITests; packageProductDependencies = ( @@ -186,7 +186,7 @@ }; }; }; - buildConfigurationList = EAB660672F3FD5C000ED41BA /* Build configuration list for PBXProject "ManeshTraderMac" */; + buildConfigurationList = EAB660672F3FD5C000ED41BA /* Build configuration list for PBXProject "MacShell" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -540,7 +540,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - EAB660672F3FD5C000ED41BA /* Build configuration list for PBXProject "ManeshTraderMac" */ = { + EAB660672F3FD5C000ED41BA /* Build configuration list for PBXProject "MacShell" */ = { isa = XCConfigurationList; buildConfigurations = ( EAB6608B2F3FD5C100ED41BA /* Debug */, diff --git a/mac/src/README.md b/mac/src/README.md index 6ffd69a..2fec4d0 100644 --- a/mac/src/README.md +++ b/mac/src/README.md @@ -6,6 +6,7 @@ Native macOS shell around the web app in `web/src/`. - Starts/stops bundled backend executable from app resources - Hosts UI in `WKWebView` at local `127.0.0.1` URL - Keeps user inside app window (no external browser) +- Provides a native toolbar `Help` button that opens bundled quick-start docs in-app ## Build Self-Contained App From repo root: diff --git a/web/src/app.py b/web/src/app.py index 9a62571..af43560 100644 --- a/web/src/app.py +++ b/web/src/app.py @@ -155,6 +155,19 @@ def save_web_settings(settings: dict[str, Any]) -> None: SETTINGS_PATH.write_text(json.dumps(normalize_web_settings(settings), indent=2), encoding="utf-8") +@st.cache_data(show_spinner=False) +def load_help_markdown() -> str: + onboarding_path = Path(__file__).with_name("ONBOARDING.md") + if onboarding_path.exists(): + return onboarding_path.read_text(encoding="utf-8") + return "Help content not found." + + +@st.dialog("Help & Quick Start", width="large") +def help_dialog() -> None: + st.markdown(load_help_markdown()) + + @st.cache_data(show_spinner=False, ttl=3600) def lookup_symbol_candidates(query: str, max_results: int = 10) -> list[dict[str, str]]: cleaned = query.strip() @@ -226,51 +239,19 @@ def resolve_symbol_identity(symbol: str) -> dict[str, str]: return {"symbol": normalized_symbol, "name": "", "exchange": ""} -@st.cache_data(show_spinner=False) -def load_onboarding_markdown() -> str: - onboarding_path = Path(__file__).with_name("ONBOARDING.md") - if onboarding_path.exists(): - return onboarding_path.read_text(encoding="utf-8") - return "ONBOARDING.md not found in project root." - - -@st.dialog("Onboarding Guide", width="large") -def onboarding_dialog() -> None: - st.markdown(load_onboarding_markdown()) - - def main() -> None: st.set_page_config(page_title="Real Bars vs Fake Bars Analyzer", layout="wide") st.title("Real Bars vs Fake Bars Trend Analyzer") st.caption( "Price-action tool that classifies closed bars, filters fake bars, and tracks trend persistence using only real bars." ) - if st.button("Open ONBOARDING.md", type="tertiary"): - onboarding_dialog() - - with st.expander("Help / Quick Start", expanded=False): - st.markdown( - """ - **Start in 60 seconds** - 1. Set a symbol like `AAPL` or `BTC-USD`. - 2. Choose `Timeframe` (`1d` is a good default) and `Period` (`6mo`). - 3. Keep **Ignore potentially live last bar** enabled. - 4. Review trend status and markers: - - Green triangle: `real_bull` - - Red triangle: `real_bear` - - `fake` bars are noise and ignored by trend logic - 5. Use **Export** to download CSV/PDF outputs. - - **Rule summary** - - `real_bull`: close > previous high - - `real_bear`: close < previous low - - `fake`: close inside previous range - - Trend starts/reverses only after 2 consecutive real bars in that direction. - """ - ) with st.sidebar: st.header("Data Settings") + if st.button("Help / Quick Start", use_container_width=True): + help_dialog() + st.divider() + query_params = st.query_params persisted_settings = load_web_settings()