aboutsummaryrefslogtreecommitdiff
path: root/app.py
diff options
context:
space:
mode:
Diffstat (limited to 'app.py')
-rw-r--r--app.py101
1 files changed, 81 insertions, 20 deletions
diff --git a/app.py b/app.py
index 8145d03..87a6716 100644
--- a/app.py
+++ b/app.py
@@ -220,6 +220,16 @@ button[kind="secondary"]:hover {
padding-bottom: 3rem !important;
}
+/* ── Sticky market bar ──────────────────────────────────────────────────── */
+.st-key-market_bar_sticky {
+ position: sticky !important;
+ top: 0 !important;
+ z-index: 200 !important;
+ background: var(--ink-0) !important;
+ padding-bottom: 0.5rem !important;
+ margin-bottom: -0.5rem !important;
+}
+
/* ── Tabs ───────────────────────────────────────────────────────────────── */
.stTabs [data-baseweb="tab-list"] {
background: var(--ink-2) !important;
@@ -404,9 +414,17 @@ hr {
::-webkit-scrollbar-thumb { background: var(--ink-3); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--ink-4); }
-/* Hide the watchlist click receiver input */
-[data-testid="stSidebar"] [data-testid="stTextInput"]:has(input[aria-label="wl_click_receiver"]) {
- display: none !important;
+/* Hide click receiver inputs without removing them from React's event system */
+[data-testid="stSidebar"] [data-testid="stTextInput"]:has(input[aria-label="wl_click_receiver"]),
+[data-testid="stTextInput"]:has(input[aria-label="qt_click_receiver"]),
+[data-testid="stTextInput"]:has(input[aria-label="persist_wl"]),
+[data-testid="stTextInput"]:has(input[aria-label="persist_tk"]) {
+ visibility: hidden !important;
+ height: 0 !important;
+ min-height: 0 !important;
+ overflow: hidden !important;
+ margin: 0 !important;
+ padding: 0 !important;
}
/* ── Ticker Header Band ──────────────────────────────────────────────────── */
@@ -584,9 +602,11 @@ _prism_template = go.layout.Template(layout=_prism_layout)
pio.templates["prism"] = _prism_template
pio.templates.default = "prism"
+from components.persistence import render_persistence_bridge
from components.market_bar import render_market_bar
from components.top_movers import render_top_movers
from components.watchlist import render_watchlist
+from components.quotetable import render_quotetable
from components.overview import render_overview
from components.financials import render_financials
from components.valuation import render_valuation
@@ -594,6 +614,7 @@ from components.insiders import render_insiders
from components.filings import render_filings
from components.news import render_news
from components.options import render_options
+from components.macro import render_macro
from services.data_service import get_company_info, search_tickers, get_latest_price
import streamlit.components.v1 as components
@@ -607,6 +628,7 @@ if "watchlist" not in st.session_state:
if "active_tab" not in st.session_state:
st.session_state["active_tab"] = "overview"
+render_persistence_bridge()
# ── Sidebar ───────────────────────────────────────────────────────────────────
@@ -712,6 +734,12 @@ with st.sidebar:
ticker = st.session_state["ticker"]
+ # ── Exit to landing page ──────────────────────────────────────────────
+ if st.session_state.get("ticker"):
+ if st.button("← Watchlist", key="exit_ticker", use_container_width=True):
+ st.session_state["ticker"] = None
+ st.rerun()
+
# ── Workspace nav ─────────────────────────────────────────────────────
st.markdown("<div class='psm-nav-section'>Workspace</div>", unsafe_allow_html=True)
_nav = [
@@ -722,6 +750,7 @@ with st.sidebar:
("insiders", "○ Insiders"),
("filings", "▤ Filings"),
("news", "◉ News"),
+ ("macro", "⬡ Macro"),
]
for _tab_id, _tab_label in _nav:
_is_active = st.session_state["active_tab"] == _tab_id
@@ -757,30 +786,62 @@ with st.sidebar:
# ── Market Bar ────────────────────────────────────────────────────────────────
-with st.container():
+with st.container(key="market_bar_sticky"):
render_market_bar()
st.divider()
+# ── ⌘K / Ctrl+K shortcut — focuses sidebar ticker search ─────────────────────
+components.html(
+ "<script>"
+ "document.addEventListener('keydown', function(e) {"
+ " if (e.key === '/' && !e.metaKey && !e.ctrlKey && !e.altKey"
+ " && document.activeElement.tagName !== 'INPUT'"
+ " && document.activeElement.tagName !== 'TEXTAREA') {"
+ " var inputs = window.parent.document.querySelectorAll('input');"
+ " for (var i = 0; i < inputs.length; i++) {"
+ " if (inputs[i].placeholder && inputs[i].placeholder.indexOf('AAPL') > -1) {"
+ " e.preventDefault();"
+ " inputs[i].focus(); inputs[i].select(); break;"
+ " }"
+ " }"
+ " }"
+ "});"
+ "</script>",
+ height=0,
+ scrolling=False,
+)
+
# ── Main Content ──────────────────────────────────────────────────────────────
+if st.session_state["active_tab"] == "macro":
+ try:
+ render_macro()
+ except Exception as e:
+ st.error(f"Macro data failed to load: {e}")
+ st.stop()
+
if not ticker:
- st.markdown("""
- <div style="padding:48px 0 32px;text-align:center;">
- <div style="
- font-family:'EB Garamond',Georgia,serif;
- font-style:italic;font-size:2.375rem;
- color:#F2ECDC;font-weight:400;
- letter-spacing:-0.01em;line-height:1.1;
- margin-bottom:12px;
- ">Search for a ticker to begin.</div>
- <div style="
- font-family:'IBM Plex Sans',sans-serif;
- font-size:0.875rem;color:#5E5849;
- letter-spacing:0.01em;
- ">Enter a company name or symbol in the sidebar.</div>
- </div>
- """, unsafe_allow_html=True)
+ _watchlist = st.session_state.get("watchlist", [])
+ if _watchlist:
+ render_quotetable(_watchlist)
+ else:
+ st.markdown("""
+ <div style="padding:48px 0 32px;text-align:center;">
+ <div style="
+ font-family:'EB Garamond',Georgia,serif;
+ font-style:italic;font-size:2.375rem;
+ color:#F2ECDC;font-weight:400;
+ letter-spacing:-0.01em;line-height:1.1;
+ margin-bottom:12px;
+ ">Search for a ticker to begin.</div>
+ <div style="
+ font-family:'IBM Plex Sans',sans-serif;
+ font-size:0.875rem;color:#5E5849;
+ letter-spacing:0.01em;
+ ">Enter a company name or symbol in the sidebar.</div>
+ </div>
+ """, unsafe_allow_html=True)
st.stop()
# ── Ticker Header + KPI Strip ─────────────────────────────────────────────────