From 8e35de0d435fec5ac552783130ef04aee33159f4 Mon Sep 17 00:00:00 2001 From: Tyler Date: Sat, 16 May 2026 01:52:32 -0700 Subject: Sidebar chrome: vertical nav, live clock, brand v1.2, drop snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace horizontal st.tabs() with session-state-driven vertical nav buttons in the sidebar (Workspace section). Remove company snapshot entirely — ticker info is covered by the persistent TickerHeader. Add NYSE live clock between brand and search. Update brand sub-label to "v 1.2". Co-Authored-By: Claude Sonnet 4.6 --- app.py | 252 ++++++++++++++++++++++++++++++----------------------------------- 1 file changed, 117 insertions(+), 135 deletions(-) diff --git a/app.py b/app.py index f8acd3c..156d0bd 100644 --- a/app.py +++ b/app.py @@ -489,6 +489,51 @@ hr { font-family: var(--font-sans); font-size: 10px; color: var(--fg-4); display: block; margin-top: 2px; } + +/* ── Sidebar nav buttons ─────────────────────────────────────────────────── */ +.psm-nav-section { + font-family: 'IBM Plex Mono', monospace; + font-size: 9px; text-transform: uppercase; + letter-spacing: 0.15em; color: #5E5849; + padding: 14px 16px 4px; +} +section[data-testid="stSidebar"] [data-testid="stBaseButton-secondary"] { + background: transparent !important; + border: none !important; + border-radius: 0 !important; + box-shadow: none !important; + color: #8E8676 !important; + font-family: 'IBM Plex Mono', monospace !important; + font-size: 12px !important; + font-weight: 400 !important; + text-align: left !important; + justify-content: flex-start !important; + padding: 7px 16px !important; + letter-spacing: 0.02em !important; + border-left: 2px solid transparent !important; +} +section[data-testid="stSidebar"] [data-testid="stBaseButton-secondary"]:hover { + background: rgba(194,170,122,0.06) !important; + color: #C7C0AE !important; +} +section[data-testid="stSidebar"] [data-testid="stBaseButton-primary"] { + background: rgba(194,170,122,0.08) !important; + border: none !important; + border-radius: 0 !important; + box-shadow: none !important; + border-left: 2px solid #C2AA7A !important; + color: #C2AA7A !important; + font-family: 'IBM Plex Mono', monospace !important; + font-size: 12px !important; + font-weight: 400 !important; + text-align: left !important; + justify-content: flex-start !important; + padding: 7px 16px !important; + letter-spacing: 0.02em !important; +} +section[data-testid="stSidebar"] [data-testid="stBaseButton-primary"]:hover { + background: rgba(194,170,122,0.12) !important; +} """, unsafe_allow_html=True) @@ -550,6 +595,7 @@ from components.filings import render_filings from components.news import render_news from components.options import render_options from services.data_service import get_company_info, search_tickers, get_latest_price +import streamlit.components.v1 as components if "ticker" not in st.session_state: @@ -558,6 +604,9 @@ if "ticker" not in st.session_state: if "watchlist" not in st.session_state: st.session_state["watchlist"] = [] +if "active_tab" not in st.session_state: + st.session_state["active_tab"] = "overview" + # ── Sidebar ─────────────────────────────────────────────────────────────────── @@ -593,11 +642,48 @@ with st.sidebar: font-family: 'IBM Plex Mono', 'SF Mono', monospace; font-size: 10px; color: #5E5849; letter-spacing: 0.12em; text-transform: uppercase; - ">Research Terminal + ">v 1.2 """, unsafe_allow_html=True) + _clock_html = ( + "" + "
" + " " + " NYSE · Closed" + " --:--:-- ET" + "
" + "" + ) + components.html(_clock_html, height=32, scrolling=False) + with st.form("ticker_search_form", clear_on_submit=False): query = st.text_input( "Ticker or company", @@ -626,117 +712,27 @@ with st.sidebar: ticker = st.session_state["ticker"] - # Company snapshot - if ticker: - st.divider() - info = get_company_info(ticker) - if info: - co_name = info.get("longName", ticker) - price = get_latest_price(ticker) - prev_close = info.get("previousClose") or info.get("regularMarketPreviousClose") - ticker_html = escape_html(ticker) - co_name_html = escape_html(co_name) - - # Ticker + name - st.markdown(f""" -
-
{ticker_html}
-
{co_name_html}
-
- """, unsafe_allow_html=True) - - # Price + change - if price is not None: - if prev_close and prev_close > 0: - chg = price - prev_close - chg_pct = chg / prev_close * 100 - sign = "+" if chg >= 0 else "" - px_color = "#4F8C5E" if chg >= 0 else "#B5494B" - st.markdown(f""" -
- ${price:,.2f} - {sign}{chg_pct:.2f}% -
- """, unsafe_allow_html=True) - else: - st.markdown(f""" -
- ${price:,.2f} -
- """, unsafe_allow_html=True) - - # Company meta - _EXCHANGE_NAMES = { - "NYQ": "NYSE", "NMS": "NASDAQ", "NGM": "NASDAQ", - "NCM": "NASDAQ", "ASE": "AMEX", "PCX": "NYSE Arca", - "BTS": "BATS", "TSX": "TSX", "LSE": "LSE", - } - raw_exchange = info.get("exchange", "") - exchange = _EXCHANGE_NAMES.get(raw_exchange, raw_exchange) or "—" - sector = info.get("sector", "—") - currency = info.get("currency", "USD") - employees = info.get("fullTimeEmployees") - emp_str = f"{employees:,}" if isinstance(employees, int) else "—" - - rows = [ - ("Exchange", escape_html(exchange)), - ("Sector", escape_html(sector)), - ("Currency", escape_html(currency)), - ("Employees", escape_html(emp_str)), - ] - rows_html = "".join(f""" -
- {k} - {v} -
- """ for k, v in rows) - - st.markdown(f""" -
{rows_html}
- """, unsafe_allow_html=True) - - website = validate_outbound_url(info.get("website", "")) - if website: - st.markdown(f""" -
- Website ↗ -
- """, unsafe_allow_html=True) - - elif ticker: - st.caption(f"Viewing: **{ticker}**") + # ── Workspace nav ───────────────────────────────────────────────────── + st.markdown("
Workspace
", unsafe_allow_html=True) + _nav = [ + ("overview", "◎ Overview"), + ("financials", "▦ Financials"), + ("valuation", "◈ Valuation"), + ("options", "◇ Options"), + ("insiders", "○ Insiders"), + ("filings", "▤ Filings"), + ("news", "◉ News"), + ] + for _tab_id, _tab_label in _nav: + _is_active = st.session_state["active_tab"] == _tab_id + if st.button( + _tab_label, + key=f"nav_{_tab_id}", + use_container_width=True, + type="primary" if _is_active else "secondary", + ): + st.session_state["active_tab"] = _tab_id + st.rerun() # ── Save / Remove watchlist toggle ──────────────────────────────────── if ticker: @@ -897,53 +893,39 @@ st.markdown( unsafe_allow_html=True, ) -tab_overview, tab_financials, tab_valuation, tab_options, tab_insiders, tab_filings, tab_news = st.tabs([ - "Overview", - "Financials", - "Valuation", - "Options", - "Insiders", - "Filings", - "News", -]) - -with tab_overview: +_active = st.session_state["active_tab"] + +if _active == "overview": try: render_overview(ticker) except Exception as e: st.error(f"Overview failed to load: {e}") - -with tab_financials: +elif _active == "financials": try: render_financials(ticker) except Exception as e: st.error(f"Financials failed to load: {e}") - -with tab_valuation: +elif _active == "valuation": try: render_valuation(ticker) except Exception as e: st.error(f"Valuation failed to load: {e}") - -with tab_options: +elif _active == "options": try: render_options(ticker) except Exception as e: st.error(f"Options data failed to load: {e}") - -with tab_insiders: +elif _active == "insiders": try: render_insiders(ticker) except Exception as e: st.error(f"Insider data failed to load: {e}") - -with tab_filings: +elif _active == "filings": try: render_filings(ticker) except Exception as e: st.error(f"Filings failed to load: {e}") - -with tab_news: +elif _active == "news": try: render_news(ticker) except Exception as e: -- cgit v1.3-2-g0d8e