Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 41 additions & 15 deletions sl/SL_Menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@
except (ImportError, ModuleNotFoundError):
UPDATE_MODULE_AVAILABLE = False

try:
# Internal (not officially public) API, but it's the only way to tell a
# fragment's own run_every tick apart from an ordinary rerun - see the
# usage in _auto_refresh_on_external_changes() for why that distinction
# matters. Guarded so a future Streamlit release that moves/removes this
# degrades to the older, less precise timing-based heuristic instead of
# crashing the app outright.
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
except ImportError:
get_script_run_ctx = None

# --- Configuration & Setup ---
CONFIG_FILE = 'config.json'
SL_DIR = os.path.dirname(os.path.abspath(__file__))
Expand Down Expand Up @@ -245,14 +256,6 @@ def navigate_to(menu_name):
:param menu_name: The key of the menu to navigate to (must exist in menu_map).
"""
st.session_state.menu = menu_name
# Counts as "just refreshed" so the auto-refresh fragment (see
# _auto_refresh_on_external_changes below) doesn't fire its own
# competing st.rerun() while this navigation's rerun is still being
# processed - otherwise that second rerun can win the race and abort
# this one before the newly selected menu ever gets rendered, making the
# click look like it did nothing (or, if the user then clicks again out
# of impatience, like the view jumped to the wrong place).
st.session_state["_last_auto_refresh"] = time.monotonic()
st.rerun()

def set_feedback(message, type='success'):
Expand Down Expand Up @@ -305,13 +308,36 @@ def _auto_refresh_on_external_changes():
since an MCP client can be spawning that process independently of what
this app's own config says, so there's no reliable way to know it isn't.

This function is invoked twice per cycle: once inline as a normal part
of every full script run (including the one it itself triggers below),
and once on its own every `run_every` seconds via the fragment's timer.
The timestamp check tells those two cases apart - without it, calling
st.rerun() unconditionally here would re-trigger itself instantly on
every single run and the app would never finish rendering.
"""
This function is invoked two different ways: inline, as a normal part of
every full script run (a click anywhere in the app, navigate_to(), ...),
and separately, on its own, every `run_every` seconds via the fragment's
own timer. Only the second case should trigger a rerun here - Streamlit
runs a timer-triggered tick as a fragment-scoped rerun that calls only
this function's body and nothing else in the script (see
streamlit/runtime/scriptrunner/script_runner.py: a rerun with a non-empty
fragment_id_queue calls just the stored fragment closure instead of
exec()-ing the rest of the script), so escalating to a full st.rerun()
is genuinely needed there for the data reload and current view to
actually refresh. An inline call, by contrast, is already running as
part of a full script execution - calling st.rerun() there would abort
that very run before it reaches whatever code (a button's own handler,
say) triggered it in the first place, silently discarding that
interaction. That was a real, user-reported bug: clicks made after a few
seconds of idling (long enough to make this look like a timer tick by
elapsed time alone, which is what an earlier version of this function
compared against) would occasionally do nothing on the first try.
ctx.fragment_ids_this_run tells the two cases apart directly instead of
guessing from elapsed time.
"""
ctx = get_script_run_ctx() if get_script_run_ctx else None
if ctx is not None:
if ctx.fragment_ids_this_run:
st.rerun()
return

# Fallback if a future Streamlit release moves/removes the internal hook
# above: less precise (can still occasionally preempt a click that lands
# in the same ~4s window), but keeps the periodic refresh itself working.
now = time.monotonic()
last_refresh = st.session_state.get("_last_auto_refresh")
st.session_state["_last_auto_refresh"] = now
Expand Down
292 changes: 292 additions & 0 deletions sl/style_blue.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
/* ==========================================================================
TimeControl - Blue Style
==========================================================================
Clean, professional palette inspired by Dlubal's own software (RFEM /
Dlubal CALC): a near-white page background, soft blue-gray card borders,
and a corporate blue accent for buttons, links and selection states.

Custom properties live on :root (not .stApp) because several Streamlit
components (popover panels, select/date dropdowns) render into a portal
appended directly to <body>, outside .stApp's own subtree - a variable
defined only on .stApp would never reach them.
========================================================================== */

:root {
--tc-bg: #F7F9FC;
--tc-bg-elevated: #FFFFFF;
--tc-bg-input: #FFFFFF;
--tc-bg-hover: #EAF1FC;
--tc-text: #1E2A3A;
--tc-text-muted: #6B7686;
--tc-border: #E1E7F0;
--tc-accent: #2E6FE3;
--tc-accent-hover: #1E56C4;
--tc-icon-color: #55627A;
--tc-code-text: #2E6FE3;
}

/* Global Font & Background */
.stApp {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: var(--tc-bg);
color: var(--tc-text);
}

/* Native Streamlit chrome: the fixed header bar (Deploy/hamburger menu) and
the toolbar/decoration strip sit outside .stApp's own background, so they
otherwise stay on Streamlit's default white/gray regardless of the rest
of the page. */
header[data-testid="stHeader"],
[data-testid="stToolbar"],
[data-testid="stDecoration"] {
background-color: var(--tc-bg) !important;
color: var(--tc-text);
}

header[data-testid="stHeader"] svg {
fill: var(--tc-icon-color) !important;
}

/* Headings */
h1, h2, h3 {
font-weight: 700;
color: var(--tc-text);
letter-spacing: -0.02em;
}

h1 {
font-size: 2.0rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--tc-border);
margin-bottom: 1.5rem;
padding-top: 0.1rem;
}

h1::before {
content: "⏱️";
margin-right: 0.5rem;
}

h2 {
font-size: 1.6rem;
}

h3 {
font-size: 1.35rem;
}

h4 {
font-size: 1.15rem;
}

h5, h6 {
font-size: 1.0rem;
}

/* Widget labels, captions and help tooltips */
[data-testid="stWidgetLabel"] p,
[data-testid="stCaptionContainer"],
[data-testid="stCaptionContainer"] p,
[data-testid="stMarkdownContainer"] small {
color: var(--tc-text-muted) !important;
}

[data-testid="stTooltipIcon"] {
color: var(--tc-text-muted) !important;
}

/* Buttons - regular, form-submit and popover triggers all need the same
treatment so none of them fall back to Streamlit's own default styling. */
.stButton button,
[data-testid="stFormSubmitButton"] button,
[data-testid="stPopoverButton"] {
border-radius: 10px;
font-weight: 600;
border: 1px solid var(--tc-border);
background-color: var(--tc-bg-elevated);
color: var(--tc-text);
transition: all 0.2s ease-in-out;
box-shadow: 0 1px 2px rgba(30, 42, 58, 0.06);
}

.stButton button:hover,
[data-testid="stFormSubmitButton"] button:hover,
[data-testid="stPopoverButton"]:hover {
border-color: var(--tc-accent);
color: var(--tc-accent-hover);
background-color: var(--tc-bg-hover);
transform: translateY(-1px);
box-shadow: 0 4px 10px rgba(30, 42, 58, 0.10);
}

.stButton button:active,
[data-testid="stFormSubmitButton"] button:active {
transform: translateY(0);
box-shadow: none;
}

.stButton button:disabled,
.stButton button:disabled:hover {
opacity: 0.5;
color: var(--tc-text-muted) !important;
border-color: var(--tc-border) !important;
background-color: var(--tc-bg-elevated) !important;
transform: none;
box-shadow: none;
}

/* Primary-type buttons (e.g. "Add Task") get a solid accent fill, matching
Dlubal CALC's own "New chat" / "Sign up" call-to-action buttons. */
[data-testid="stBaseButton-primary"] {
background-color: var(--tc-accent) !important;
border-color: var(--tc-accent) !important;
color: #FFFFFF !important;
}

[data-testid="stBaseButton-primary"]:hover {
background-color: var(--tc-accent-hover) !important;
border-color: var(--tc-accent-hover) !important;
color: #FFFFFF !important;
}

/* The dropdown card a popover opens (toolbar's New/Management/Reporting
menus) - rendered in a body-level portal, stayed on Streamlit's default
white/gray otherwise. */
[data-testid="stPopoverBody"],
[data-testid="stPopoverBody"] > div {
background-color: var(--tc-bg-elevated) !important;
border: 1px solid var(--tc-border) !important;
color: var(--tc-text);
}

/* Input Fields */
.stTextInput input,
.stNumberInput input,
.stTextArea textarea,
.stDateInput input,
.stSelectbox div[data-baseweb="select"] > div {
border-radius: 8px;
border: 1px solid var(--tc-border);
background-color: var(--tc-bg-input);
color: var(--tc-text);
}

.stTextInput input:focus,
.stNumberInput input:focus,
.stTextArea textarea:focus {
border-color: var(--tc-accent) !important;
box-shadow: 0 0 0 1px var(--tc-accent) !important;
}

.stTextInput input::placeholder,
.stTextArea textarea::placeholder {
color: var(--tc-text-muted);
opacity: 1;
}

[data-testid="stNumberInputStepDown"],
[data-testid="stNumberInputStepUp"] {
background-color: var(--tc-bg-hover) !important;
border-color: var(--tc-border) !important;
color: var(--tc-text) !important;
}

/* Select / date-input dropdown popups also render in a body-level portal
via BaseWeb, so they need the same background rendered explicitly. The
plain [data-baseweb="popover"] wrapper (and the unlabeled div directly
inside it) is what shows a card behind the calendar/menu content - the
inner listbox/menu/calendar elements need their own override too. */
[data-baseweb="popover"],
[data-baseweb="popover"] > div,
[data-baseweb="popover"] [role="listbox"],
[data-baseweb="menu"],
[data-baseweb="calendar"] {
background-color: var(--tc-bg-elevated) !important;
color: var(--tc-text) !important;
}

[data-baseweb="popover"] li[role="option"],
[data-baseweb="menu"] li {
color: var(--tc-text) !important;
}

[data-baseweb="popover"] li[role="option"]:hover,
[data-baseweb="menu"] li:hover {
background-color: var(--tc-bg-hover) !important;
}

/* The date-input calendar's month/year header bar and weekday-initials row
keep Streamlit's default secondary-background grey regardless of theme;
[role="presentation"] reaches the weekday row, .st-c7 is the shared class
between it and the header bar just above it. Out-of-month filler day
cells render their fill via a ::after pseudo-element rather than their
own background, so they need a separate override. */
[data-baseweb="calendar"] [role="presentation"],
[data-baseweb="calendar"] .st-c7 {
background-color: transparent !important;
}

[data-baseweb="calendar"] [role="gridcell"]::after {
background-color: transparent !important;
}

/* The selected day's own indicator (a circular ::after ring BaseWeb pairs
with white text) shares that same pseudo-element with the out-of-month
filler cells above, so it lost its fill too - leaving white-on-transparent
text that's invisible against this theme's light background. Restore a
solid, on-brand circle behind just the selected day. */
[data-baseweb="calendar"] .st-cj::after {
background-color: var(--tc-accent) !important;
}

/* Checkbox */
[data-testid="stCheckbox"] label p {
color: var(--tc-text) !important;
}

/* Expander */
[data-testid="stExpander"] {
border: 1px solid var(--tc-border);
border-radius: 10px;
background-color: var(--tc-bg-elevated);
overflow: hidden;
}

[data-testid="stExpander"] summary {
color: var(--tc-text) !important;
}

[data-testid="stExpander"] summary:hover {
background-color: var(--tc-bg-hover);
}

/* Divider */
hr {
border-color: var(--tc-border) !important;
}

/* Alerts (st.info/success/warning/error) */
[data-testid="stAlert"] {
background-color: var(--tc-bg-elevated);
border-radius: 8px;
border: 1px solid var(--tc-border);
}

/* Inline code and code blocks */
code {
background-color: var(--tc-bg-hover) !important;
color: var(--tc-code-text) !important;
}

/* Report Box */
.report-box {
background-color: #FFFFFF;
padding: 20px;
border-radius: 12px;
font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: 0.9em;
white-space: pre-wrap;
border: 1px solid var(--tc-border);
box-shadow: inset 0 1px 3px rgba(30, 42, 58, 0.06);
color: var(--tc-text);
}
9 changes: 9 additions & 0 deletions sl/style_claude.css
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,15 @@ h5, h6 {
background-color: transparent !important;
}

/* The selected day's own indicator (a circular ::after ring BaseWeb pairs
with white text) shares that same pseudo-element with the out-of-month
filler cells above, so it lost its fill too - leaving white-on-transparent
text that's invisible against this theme's light background. Restore a
solid, on-brand circle behind just the selected day. */
[data-baseweb="calendar"] .st-cj::after {
background-color: var(--tc-accent) !important;
}

/* Checkbox */
[data-testid="stCheckbox"] label p {
color: var(--tc-text) !important;
Expand Down
Loading
Loading