diff --git a/pyproject.toml b/pyproject.toml
index 9b1b87f..e465e86 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "plba"
-version = "0.3.5"
+version = "0.3.6"
description = "Platform runtime for business applications"
readme = "README.md"
requires-python = ">=3.11"
diff --git a/src/app_runtime/control/trace_presenter.py b/src/app_runtime/control/trace_presenter.py
index cbc6dd3..ec0576d 100644
--- a/src/app_runtime/control/trace_presenter.py
+++ b/src/app_runtime/control/trace_presenter.py
@@ -95,7 +95,7 @@ class TraceResponseRenderer:
def _render_html(self, trace_view: TraceLogView, request: TraceQueryRequest) -> HTMLResponse:
title = escape(f"Trace {trace_view.trace_id}")
header_links = self._related_trace_links(trace_view, request)
- rows = self._step_rows(trace_view, request)
+ sections = self._step_sections(trace_view, request)
html = f"""
@@ -166,26 +166,16 @@ class TraceResponseRenderer:
a:hover {{
text-decoration: underline;
}}
- table {{
- width: 100%;
- border-collapse: collapse;
- }}
- th, td {{
- vertical-align: top;
- padding: 14px 12px;
+ .step-section + .step-section {{
+ margin-top: 24px;
+ padding-top: 24px;
border-top: 1px solid var(--line);
}}
- th {{
- text-align: left;
- color: var(--muted);
- font-size: 13px;
- text-transform: uppercase;
- letter-spacing: 0.06em;
- }}
- td.step {{
- width: 240px;
+ .step-title {{
+ margin: 0 0 14px;
+ font-size: 22px;
+ line-height: 1.2;
font-weight: 700;
- white-space: nowrap;
}}
.log-entry + .log-entry {{
margin-top: 14px;
@@ -222,6 +212,21 @@ class TraceResponseRenderer:
.level-DEBUG {{
color: var(--debug);
}}
+ @media (max-width: 640px) {{
+ .page {{
+ padding: 14px;
+ }}
+ .card {{
+ padding: 16px 14px;
+ border-radius: 12px;
+ }}
+ h1 {{
+ font-size: 22px;
+ }}
+ .step-title {{
+ font-size: 20px;
+ }}
+ }}
@@ -237,14 +242,7 @@ class TraceResponseRenderer:
Logs By Step
-
-
- | Step | Log |
-
-
- {rows}
-
-
+ {sections}
@@ -283,11 +281,11 @@ class TraceResponseRenderer:
)
return f"/traces/{trace_id}?{params}"
- def _step_rows(self, trace_view: TraceLogView, request: TraceQueryRequest) -> str:
+ def _step_sections(self, trace_view: TraceLogView, request: TraceQueryRequest) -> str:
groups = self._step_groups(trace_view.records)
if not groups:
- return "| | No log records for selected filters. |
"
- return "".join(self._step_row(group, request.include_attrs_json) for group in groups)
+ return "No step
No log records for selected filters.
"
+ return "".join(self._step_section(group, request.include_attrs_json) for group in groups)
def _step_groups(self, records: tuple[TraceLogRecord, ...]) -> tuple[TraceStepGroup, ...]:
grouped: dict[str, list[TraceLogRecord]] = {}
@@ -300,10 +298,10 @@ class TraceResponseRenderer:
grouped[step].append(record)
return tuple(TraceStepGroup(step=step, records=tuple(grouped[step])) for step in order)
- def _step_row(self, group: TraceStepGroup, include_attrs_json: bool) -> str:
- step_label = escape(group.step) if group.step else " "
+ def _step_section(self, group: TraceStepGroup, include_attrs_json: bool) -> str:
+ step_label = escape(group.step) if group.step else "No step"
entries = "".join(self._log_entry(record, include_attrs_json) for record in group.records)
- return f"| {step_label} | {entries} |
"
+ return f""
def _log_entry(self, record: TraceLogRecord, include_attrs_json: bool) -> str:
level = escape(record.level)
diff --git a/tests/test_trace_endpoint.py b/tests/test_trace_endpoint.py
index e791d6e..5b8fd6d 100644
--- a/tests/test_trace_endpoint.py
+++ b/tests/test_trace_endpoint.py
@@ -201,11 +201,13 @@ def test_trace_endpoint_returns_html_page_with_related_links() -> None:
assert response.status_code == 200
assert response.headers["content-type"].startswith("text/html")
- assert "Step | Log | " in response.text
- assert ">load_stocks<" in response.text
- assert ">filter_stocks<" in response.text
+ assert "Logs By Step
" in response.text
+ assert 'load_stocks
' in response.text
+ assert 'filter_stocks
' in response.text
assert "loaded prices" in response.text
assert "filtered suspicious ticker" in response.text
+ assert "2026-04-28T10:11:12+00:00 | INFO | ok" in response.text
+ assert "2026-04-28T10:11:12+00:00 | WARNING | degraded" in response.text
assert 'href="/traces/trace-1?format=html&levels=ERROR%2CWARNING%2CINFO&attrs_json=true"' in response.text
assert 'href="/traces/parent-1?format=html&levels=ERROR%2CWARNING%2CINFO&attrs_json=true"' in response.text
assert 'href="/traces/child-1?format=html&levels=ERROR%2CWARNING%2CINFO&attrs_json=true"' in response.text