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
14 changes: 11 additions & 3 deletions claude_code_log/html/minimal_theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,11 @@ def call_title(
Returns ``prefix`` (HTML the template keeps in a hidden ``.mn-tn`` span,
so the title's text content — which search and the timeline read — is
unchanged), ``rest`` (HTML shown), ``generic`` (nothing left to show: the
title line is hidden) and ``tooltip`` (the whole title as plain text, plus
the classic ``title_hint`` such as ``ID: toolu_…``).
title line is hidden), ``tooltip`` (the whole title as plain text, plus
the classic ``title_hint`` such as ``ID: toolu_…``) and ``role_tooltip``
(the same text when the hidden title said more than a bare role: the
gutter's role label is then the only place the name shows, ellipsized
when long, so it carries the tooltip instead; empty otherwise).
"""
full = str(full_title or "")
cut = full.find("<")
Expand All @@ -188,11 +191,16 @@ def call_title(
rest = html.escape(rest_text, quote=False) + tail
plain = _plain(full)
tooltip = f"{plain} · {title_hint}" if plain and title_hint else plain or ""
bare_role = is_generic_title(full)
generic = not _plain(rest) or bare_role
return {
"prefix": html.escape(prefix_text, quote=False),
"rest": rest,
"generic": not _plain(rest) or is_generic_title(full),
"generic": generic,
"tooltip": tooltip or (title_hint or ""),
"role_tooltip": (tooltip or title_hint or "")
if generic and not bare_role
else "",
}


Expand Down
27 changes: 25 additions & 2 deletions claude_code_log/html/templates/components/minimal/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ body.theme-minimal {
border-bottom: 1px solid var(--muted);
}

/* Any image a message carries (one from rendered Markdown too) stays in the
message's frame, so in its column. Zero specificity: a rule that sizes an
image on purpose (the inline favicon's 1em) still wins. */
:where(.theme-minimal #transcript .message) img {
max-width: 100%;
height: auto;
}

.theme-minimal #transcript img.tool-result-image,
.theme-minimal #transcript img.uploaded-image {
margin: 4px 0;
Expand Down Expand Up @@ -671,8 +679,23 @@ body.theme-minimal {
font-style: normal;
}

.theme-minimal #transcript .tool-params-expand-all {
font: 0.72em var(--mono);
/* The two "expand all" buttons look alike: the table's own and a fold's
("expand all rows"). Italic marks them as controls, not data. A fold's
button sits at any font size (a result's top level, or inside a 0.8em
table), so the size is one length resolved on #transcript: a registered
<length> computes where it is declared and inherits as that value. */
@property --mn-ctl {
syntax: '<length>';
inherits: true;
initial-value: 10px;
}

.theme-minimal #transcript {
--mn-ctl: 0.72em;
}

.theme-minimal #transcript :is(.tool-params-expand-all, .tool-param-rows-toggle) {
font: italic var(--mn-ctl) var(--mono);
}

/* -------------------------------------------------------------- todo */
Expand Down
5 changes: 5 additions & 0 deletions claude_code_log/html/templates/components/minimal/layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,13 @@
display: none;
}

/* z-index: a pair's first card can be shorter than its role line, which
then overflows into the next card; that card, later in the DOM, would sit
on top and swallow the label's hover (its title tooltip). Above the cards,
below every sticky bar. */
.theme-minimal #transcript .mn-role {
position: relative;
z-index: 1;
padding-right: calc(var(--ic) + var(--ic-gap));
color: var(--rc);
font-weight: 500;
Expand Down
10 changes: 10 additions & 0 deletions claude_code_log/html/templates/components/minimal/minimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@
if (window.claudeLogOnRehydrate) window.claudeLogOnRehydrate(decorateSessionSummaries);
window.addEventListener('resize', function () { decorateSessionSummaries(document); });

// ---- truncated role labels: the full label on hover ------------
// Only the rendered width tells whether a gutter label is
// ellipsized, so its tooltip is set here, on first hover. A label
// whose hidden title already gave it one (role_tooltip) keeps it.
document.addEventListener('mouseover', function (event) {
const role = event.target.closest && event.target.closest('#transcript .mn-role');
if (!role || role.hasAttribute('title')) return;
if (role.scrollWidth > role.clientWidth) role.title = role.textContent.trim();
});

// ---- sticky offsets ------------------------------------------
// The search & filter panel and the timeline are sticky too; they
// sit under the toolbar, whose height changes when it wraps.
Expand Down
2 changes: 1 addition & 1 deletion claude_code_log/html/templates/transcript.html
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ <h3>🔍 Search & Filter</h3>
{% set msg_emoji = get_message_emoji(message) -%}
{# The icon before a header title, shared by both themes. #}{% set title_emoji %}{% if message_title == 'Memory' %}💭 {% elif msg_emoji and (msg_emoji == '🚨' or message.type not in ('tool_use', 'tool_result') or not starts_with_emoji(message_title)) %}{{ msg_emoji }} {% endif %}{% endset -%}
{% if minimal %}{% set mn_full %}{% if message_title %}{{ title_emoji }}{{ message_title | safe }}{% endif %}{% endset %}{% set mn_t = mn_call_title(mn_full, message, msg_css_class, message.title_hint) %}<span class='mn-title{% if mn_t.generic %} mn-generic{% endif %}'{% if mn_t.tooltip %} title="{{ mn_t.tooltip }}"{% endif %}>{% if mn_t.prefix %}<span class='mn-tn'>{{ mn_t.prefix | safe }}</span>{% endif %}{{ mn_t.rest | safe }}</span>{% else %}<span{% if message.title_hint %} title="{{ message.title_hint }}"{% endif %}>{% if message_title %}{{ title_emoji }}{{ message_title | safe }}{% endif %}</span>{% endif %}
<div class='header-info'>{% if minimal %}<span class='mn-role'>{{ mn_role_icon(message, msg_css_class) }}{{ mn_role_label(message, msg_css_class) }}</span>{% endif %}
<div class='header-info'>{% if minimal %}<span class='mn-role'{% if mn_t.role_tooltip %} title="{{ mn_t.role_tooltip }}"{% endif %}>{{ mn_role_icon(message, msg_css_class) }}{{ mn_role_label(message, msg_css_class) }}</span>{% endif %}
<div class='timestamp-row'>
<span class='timestamp'{% if message.meta and message.meta.timestamp %} data-timestamp='{{ message.meta.timestamp }}'{% if message.pair_duration %} data-duration='{{ message.pair_duration }}'{% endif %}{% endif %}>{{ formatted_timestamp }}</span>{% if minimal %}<span class='mn-time'>{{ mn_gutter_time(formatted_timestamp) }}</span>{% endif %}
</div>
Expand Down
4 changes: 2 additions & 2 deletions dev-docs/css-classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,10 @@ output):
| `.mn-seg.mn-depth`, `button[data-mn-depth=prompts\|steps\|all]` | toolbar, before `.mn-sp` | fold depth (dev-docs/message-hierarchy.md "Fold depth"); `.on` + `aria-pressed` mark the current choice, none after a manual override. Stored in `localStorage` `claude-code-log:fold-depth` |
| `summary[data-more]` | the `<summary>` of `details.collapsible-code`, `.collapsible-details`, `.tool-param-collapsible` | the closed block's label (`+ N lines`, `+ N items` for a params table, `+ more`), written by `minimal.js` and drawn by `summary::after { content: attr(data-more) }` |
| `button.mn-less` | last child of a long (≥ 12 lines/items) collapsible | trailing `− less` (label in CSS): closes the block and scrolls its top back under the toolbar |
| `.mn-title`, `.mn-generic` | the card title span | the row's call line (column 3, mono). `minimal_theme.call_title` moves the title's leading pictograph and a leading repeat of the gutter label (tool name, role label, or an alias such as `Todo List`, `Async result`, `Task`) into a hidden `.mn-tn` span, so the line shows only the path/arguments (`.tool-summary` in `--fg`, details such as `.tool-subagent`/`.message-model` dimmed); the whole title is the span's `title` (plus the classic `title_hint`). `.mn-tn` stays in the DOM, so search and the timeline, which read the title's `textContent`, see the same text as in classic. `.mn-generic` (nothing left to show, or a role-only title) hides the line |
| `.mn-title`, `.mn-generic` | the card title span | the row's call line (column 3, mono). `minimal_theme.call_title` moves the title's leading pictograph and a leading repeat of the gutter label (tool name, role label, or an alias such as `Todo List`, `Async result`, `Task`) into a hidden `.mn-tn` span, so the line shows only the path/arguments (`.tool-summary` in `--fg`, details such as `.tool-subagent`/`.message-model` dimmed); the whole title is the span's `title` (plus the classic `title_hint`). `.mn-tn` stays in the DOM, so search and the timeline, which read the title's `textContent`, see the same text as in classic. `.mn-generic` (nothing left to show, or a role-only title) hides the line; when the hidden title said more than a bare role (a name-only tool title, `🚨 Error`), its tooltip moves to `.mn-role` (`call_title`'s `role_tooltip`), since the ellipsized gutter label is then the only place the name shows; any other ellipsized `.mn-role` gets its full text as `title` on first hover (`minimal.js`: only the rendered width says it is truncated) |
| `.mn-tn` | inside `.mn-title` | hidden pictograph + repeated name (mockup `.tn`) |
| `.mn-sh-sum`, `.mn-sh-id`, `.mn-sh-more` | non-branch session header's `.header` | compact header (`minimal_theme.session_header`): the summary alone, clamped to two lines (`-webkit-line-clamp`; full text as `title`; `.mn-open` unclamps), then the short session id, team badge and model as dim mono metadata. `minimal.js` adds the `+ more` / `− less` button (`.mn-sh-more`) only when the clamp hides text, on load, rehydrate and resize |
| `.mn-role`, `.mn-time`, `.mn-tok` | inside `.header-info` | gutter: role label (`minimal_theme.role_label`), short time (server UTC, localised by `minimal.js` from the sibling `.timestamp[data-timestamp]`), compact tokens (`in · out`, full string as `title`) |
| `.mn-role`, `.mn-time`, `.mn-tok` | inside `.header-info` | gutter: role label (`minimal_theme.role_label`; `z-index: 1`, so a pair's next card can't cover the label where it overflows a shorter first card and swallow its hover), short time (server UTC, localised by `minimal.js` from the sibling `.timestamp[data-timestamp]`), compact tokens (`in · out`, full string as `title`) |
| `svg.mn-sprite` > `symbol#mi-<glyph>` | right after `<body>`'s opening tag, outside `#transcript` | the page's one icon sprite (`minimal_icons.icon_sprite`): every glyph, so a live update that brings a new kind finds its symbol; zero-size, `aria-hidden` |
| `svg.mn-ic[aria-hidden] > use[href="#mi-<glyph>"]` | first child of `.mn-role`; a branch header's `.header` (in place of `↳`) | the role icon (`minimal_icons.role_icon`): `fill: none`, stroke `currentColor` (`--rc`; `--lF` on a branch header), 1.5, round caps and joins, `--ic` square. Absolutely placed in the label's padding: at its right end on a wide row (a column of icons beside the rail; the label truncates before it), leading it on a phone and in a column (`dag-col`), inside the `error` pill. No text, so search, the filter and the timeline read only the label — [minimal-theme.md § 9](minimal-theme.md#9-compact-spawn-rows-and-other-polish-p7c-p8) |
| `.message::after` | every row but a session header | the hairline: 1px of `--hair` on the row's top edge, content column only (grid column 3; 2 on a phone) — none on a tool pair's result half (unless `dag-split`), a turn-rule row, a session header's first row, or a column's first card (`dag-cfirst`, written by the engine). The index's `.mn-srow + .mn-srow` and the search page's `.mn-hit + .mn-hit > a` draw the same line |
Expand Down
Loading
Loading