diff --git a/claude_code_log/html/templates/components/message_styles.css b/claude_code_log/html/templates/components/message_styles.css index a4565678..71636c0d 100644 --- a/claude_code_log/html/templates/components/message_styles.css +++ b/claude_code_log/html/templates/components/message_styles.css @@ -84,14 +84,11 @@ font-size: 1.1em; line-height: 1; color: var(--fold-color); - /* Tighten the gap between the two glyphs in ``▼▼`` / ``▶▶`` — - Chrome renders default kerning much wider than Firefox does, - leaving an awkward gap. The pulled-in -0.2em compromise reads - cleanly in Chrome without crowding the glyphs in Firefox - (issue #73 / #153). No visible effect on single-glyph ``▼`` - since letter-spacing only matters when two characters are - adjacent. */ - letter-spacing: -.2em; + /* The doubled glyphs ``⏷⏷`` / ``⏵⏵`` (U+23F7 / U+23F5) sit at a + natural, even spacing in both Chrome and Firefox, so no kerning + trick is needed (the earlier ``▼▼`` / ``▶▶`` needed a -0.2em + letter-spacing pull-in to tame Chrome's wide default — issue + #73 / #153 / #247). */ } .fold-count { @@ -1051,9 +1048,9 @@ pre > code { } /* Fold-valued rows hoist their toggle into the KEY column: the key cell's - ▶/▼ button (wired in transcript.html, state derived from toggle events) + ⏵/⏷ button (wired in transcript.html, state derived from toggle events) replaces the per-fold in-summary collapse chrome, which only remained - useful en masse — every row showing "▼ collapse" drowned the content. + useful en masse — every row showing "⏷ collapse" drowned the content. Scoped to keyed rows: the top-level root fold has no key column and keeps its summary affordances (the pure-CSS ::after "collapse" hint — no interactive child). */ @@ -1077,7 +1074,7 @@ pre > code { /* The shared fold glyph: the BROWSER'S native disclosure marker (the same UA-drawn icon a shows) via display:list-item — geometrically symmetric open/closed, and immune to the italic style of the button - labels (font glyphs like ▶/▼ skew in italics; the marker icon doesn't). + labels (font glyphs like ⏵/⏷ skew in italics; the marker icon doesn't). The empty span doubles as the key-column alignment slot: every key cell renders one (markerless on scalar rows), so all key texts start at the same x. */ @@ -1094,13 +1091,13 @@ pre > code { button > .tool-param-fold-glyph { display: list-item; list-style-position: inside; - list-style-type: "\25B8"; + list-style-type: "\23F5"; list-style-type: disclosure-closed; } .tool-param-key-toggle[aria-expanded='true'] > .tool-param-fold-glyph, [data-state='expanded'] > .tool-param-fold-glyph { - list-style-type: "\25BE"; + list-style-type: "\23F7"; list-style-type: disclosure-open; } @@ -1123,7 +1120,7 @@ button > .tool-param-fold-glyph { display: none; } -/* A keyed fold's open summary carries nothing (the key glyph shows ▼ and +/* A keyed fold's open summary carries nothing (the key glyph shows ⏷ and the rows-toggle sits in the controls strip below) — drop the line it would occupy. This also suppresses the generic ::after "collapse". */ .tool-param-row-fold > .tool-param-value > details[open] > summary { diff --git a/claude_code_log/html/templates/transcript.html b/claude_code_log/html/templates/transcript.html index 2cca16a7..29c1662f 100644 --- a/claude_code_log/html/templates/transcript.html +++ b/claude_code_log/html/templates/transcript.html @@ -135,17 +135,17 @@

🔍 Search & Filter

{% if message.immediate_children_count == message.total_descendants_count %} {# Same count = only one level, show single full-width button #}
- + {{ message.get_immediate_children_label() }}
{% else %} {# Multiple levels, show both buttons #}
- + {{ message.get_immediate_children_label() }}
- ▼▼ + ⏷⏷ {{ message.get_total_descendants_label() }} total
{% endif %} @@ -197,17 +197,17 @@

🔍 Search & Filter

{% if message.immediate_children_count == message.total_descendants_count %} {# Same count = only one level, show single full-width button #}
- + {{ message.get_immediate_children_label() }}
{% else %} {# Multiple levels, show both buttons #}
- + {{ message.get_immediate_children_label() }}
- ▼▼ + ⏷⏷ {{ message.get_total_descendants_label() }} total
{% endif %} @@ -446,7 +446,7 @@

🔍 Search & Filter

const allOpen = rows.length > 0 && Array.from(rows).every(row => row.open); const kind = button.dataset.kind || 'rows'; - // data-state drives the CSS rotation of the constant ▸ glyph; + // data-state drives the CSS rotation of the constant ⏵ glyph; // only the label text changes. button.dataset.state = allOpen ? 'expanded' : 'collapsed'; const label = button.querySelector('.tool-param-fold-label'); @@ -482,7 +482,7 @@

🔍 Search & Filter

syncExpandAll(root); return; } - // Key-column fold toggle (▶/▼): drives the value cell's + // Key-column fold toggle (⏵/⏷): drives the value cell's // details from the key cell, keeping the summary free of // per-fold collapse chrome. Glyph state is derived in the // toggle listener below, so any other opener stays in sync. @@ -530,7 +530,7 @@

🔍 Search & Filter

const keyBtn = row && row.querySelector( ':scope > .tool-param-key > .tool-param-key-toggle'); if (keyBtn) { - // CSS rotates the single ▸ glyph on aria-expanded — + // CSS rotates the single ⏵ glyph on aria-expanded — // symmetric open/closed states by construction. keyBtn.setAttribute('aria-expanded', details.open ? 'true' : 'false'); @@ -867,18 +867,18 @@

🔍 Search & Filter

const allSection = foldBar ? foldBar.querySelector('.fold-all-levels') : null; if (state === 'folded') { if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } else if (state === 'first') { if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'folded')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // 'open' if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'open')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, false, '▼▼'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, false, '⏷⏷'); } } @@ -924,15 +924,15 @@

🔍 Search & Filter

const cc = getChildrenContainer(msg); if ((isSession || isUser) && !hasOnlyTools) { - // First level visible (▼), deeper levels folded (▶▶) + // First level visible (⏷), deeper levels folded (⏵⏵) if (cc) cc.style.display = ''; - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // Fully folded if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } }); } @@ -959,8 +959,8 @@

🔍 Search & Filter

const card = wrapper ? wrapper.querySelector(':scope > .message') : null; const foldBar = card ? card.querySelector(':scope > .fold-bar') : null; if (foldBar) { - setSectionState(foldBar.querySelector('.fold-one-level'), false, '▼'); - setSectionState(foldBar.querySelector('.fold-all-levels'), false, '▼▼'); + setSectionState(foldBar.querySelector('.fold-one-level'), false, '⏷'); + setSectionState(foldBar.querySelector('.fold-all-levels'), false, '⏷⏷'); } } node = node.parentElement; diff --git a/claude_code_log/html/tool_formatters.py b/claude_code_log/html/tool_formatters.py index 6e577053..f0961382 100644 --- a/claude_code_log/html/tool_formatters.py +++ b/claude_code_log/html/tool_formatters.py @@ -1222,7 +1222,7 @@ def _param_value_html(value: Any, depth: int) -> str: def _params_table_html(items: "Iterable[tuple[Any, Any]]", depth: int) -> str: """Build one key/value table; nested levels get a marker class. - A fold-valued row hoists its ▶/▼ toggle into the KEY column (wired in + A fold-valued row hoists its ⏵/⏷ toggle into the KEY column (wired in transcript.html), so the value summary stays free of per-fold collapse chrome — only the previews (closed) and the rows-toggle buttons (open) remain there. The `` PairingIndices: # Indexing them here would let a chained system entry (e.g. a # ``stop_hook_summary`` whose ``parentUuid`` is the hook # attachment) pair the hook as its parent — visible as a - # spurious "▼ 1 system" fold-bar on every hook in dense + # spurious "⏷ 1 system" fold-bar on every hook in dense # transcripts (e.g. ClMail bursts). if ( msg.meta.uuid diff --git a/dev-docs/message-hierarchy.md b/dev-docs/message-hierarchy.md index 8275df69..bab3e6a0 100644 --- a/dev-docs/message-hierarchy.md +++ b/dev-docs/message-hierarchy.md @@ -41,32 +41,32 @@ The fold bar has two buttons with three possible states: | State | Button 1 | Button 2 | Visibility | Description | |-------|----------|----------|------------|-------------| -| **A** | ▶ | ▶▶ | Nothing visible | Fully folded | -| **B** | ▼ | ▶▶ | First level visible | One level unfolded | -| **C** | ▼ | ▼▼ | All levels visible | Fully unfolded | +| **A** | ⏵ | ⏵⏵ | Nothing visible | Fully folded | +| **B** | ⏷ | ⏵⏵ | First level visible | One level unfolded | +| **C** | ⏷ | ⏷⏷ | All levels visible | Fully unfolded | -**Note**: The state "▶ ▼▼" (first level folded, all levels unfolded) is **impossible** and should never occur. +**Note**: The state "⏵ ⏷⏷" (first level folded, all levels unfolded) is **impossible** and should never occur. ## State Transitions ``` ┌────────────────────────────────┐ - ┌────────►│ State A (▶ / ▶▶) │◄────────┐ + ┌────────►│ State A (⏵ / ⏵⏵) │◄────────┐ │ │ Nothing visible │ │ │ └────────────────────────────────┘ │ │ │ │ │ - │ Click ▶ │ │ Click ▶▶ │ + │ Click ⏵ │ │ Click ⏵⏵ │ │ (unfold 1) │ │ (unfold all) │ - │ ▼ ▼ │ + │ ⏷ ⏷ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ State B │ │ State C │ │ - │ │ (▼ / ▶▶) │ │ (▼ / ▼▼) │ │ + │ │ (⏷ / ⏵⏵) │ │ (⏷ / ⏷⏷) │ │ │ │ First │ │ All │ │ │ │ level │ │ levels │ │ │ │ visible │ │ visible │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ - │ Click ▼│ └── ▶▶ ↔ ▼▼ ──┘ │Click ▼ │ + │ Click ⏷│ └── ⏵⏵ ↔ ⏷⏷ ──┘ │Click ⏷ │ │ │ (unfold all / fold 1) │ │ └─────────┘ └──────────┘ (fold all) (fold all) @@ -76,46 +76,46 @@ The fold bar has two buttons with three possible states: | Current State | Click Button 1 | Result | Click Button 2 | Result | |---------------|----------------|--------|----------------|--------| -| **A: ▶ ▶▶** (nothing) | ▶ (unfold 1) | **B: ▼ ▶▶** (first level) | ▶▶ (unfold all) | **C: ▼ ▼▼** (all levels) | -| **B: ▼ ▶▶** (first level) | ▼ (fold 1) | **A: ▶ ▶▶** (nothing) | ▶▶ (unfold all) | **C: ▼ ▼▼** (all levels) | -| **C: ▼ ▼▼** (all levels) | ▼ (fold 1) | **A: ▶ ▶▶** (nothing) | ▼▼ (fold all) | **B: ▼ ▶▶** (first level) | +| **A: ⏵ ⏵⏵** (nothing) | ⏵ (unfold 1) | **B: ⏷ ⏵⏵** (first level) | ⏵⏵ (unfold all) | **C: ⏷ ⏷⏷** (all levels) | +| **B: ⏷ ⏵⏵** (first level) | ⏷ (fold 1) | **A: ⏵ ⏵⏵** (nothing) | ⏵⏵ (unfold all) | **C: ⏷ ⏷⏷** (all levels) | +| **C: ⏷ ⏷⏷** (all levels) | ⏷ (fold 1) | **A: ⏵ ⏵⏵** (nothing) | ⏷⏷ (fold all) | **B: ⏷ ⏵⏵** (first level) | ## Key Insights 1. **Button 1 (fold/unfold one level)**: - - From State A (▶): Unfolds to first level → State B (▼) - - From State B or C (▼): Folds completely → State A (▶) + - From State A (⏵): Unfolds to first level → State B (⏷) + - From State B or C (⏷): Folds completely → State A (⏵) - **Always toggles between "nothing" and "first level"** 2. **Button 2 (fold/unfold all levels)**: - - From State A (▶▶): Unfolds to all levels → State C (▼▼) - - From State B (▶▶): Unfolds to all levels → State C (▼▼) - - From State C (▼▼): Folds to first level (NOT nothing) → State B (▼ ▶▶) - - **When unfolding (▶▶), always shows ALL levels. When folding (▼▼), goes back to first level only.** + - From State A (⏵⏵): Unfolds to all levels → State C (⏷⏷) + - From State B (⏵⏵): Unfolds to all levels → State C (⏷⏷) + - From State C (⏷⏷): Folds to first level (NOT nothing) → State B (⏷ ⏵⏵) + - **When unfolding (⏵⏵), always shows ALL levels. When folding (⏷⏷), goes back to first level only.** 3. **Coordination**: - When button 1 changes, button 2 updates accordingly - When button 2 changes, button 1 updates accordingly - - The impossible state "▶ ▼▼" is prevented by design + - The impossible state "⏵ ⏷⏷" is prevented by design ## Initial State -- **Sessions and User messages**: Start in **State B** (▼ ▶▶) - first level visible -- **Assistant, System, Thinking, Tools**: Start in **State A** (▶ ▶▶) - fully folded +- **Sessions and User messages**: Start in **State B** (⏷ ⏵⏵) - first level visible +- **Assistant, System, Thinking, Tools**: Start in **State A** (⏵ ⏵⏵) - fully folded ## Example Flow **Starting from State A (fully folded):** -1. User sees: `▶ 2 messages ▶▶ 125 total` -2. Clicks ▶▶ (unfold all) → Goes to State C, sees everything -3. Now sees: `▼ fold 2 ▼▼ fold all below` -4. Clicks ▼▼ (fold all) → Goes back to State B, sees only first level -5. Now sees: `▼ fold 2 ▶▶ fold all 125 below` -6. Clicks ▼ (fold one) → Goes to State A, sees nothing -7. Back to: `▶ 2 messages ▶▶ 125 total` -8. Clicks ▶ (unfold one) → Goes to State B, sees first level -9. Now sees: `▼ fold 2 ▶▶ fold all 125 below` +1. User sees: `⏵ 2 messages ⏵⏵ 125 total` +2. Clicks ⏵⏵ (unfold all) → Goes to State C, sees everything +3. Now sees: `⏷ fold 2 ⏷⏷ fold all below` +4. Clicks ⏷⏷ (fold all) → Goes back to State B, sees only first level +5. Now sees: `⏷ fold 2 ⏵⏵ fold all 125 below` +6. Clicks ⏷ (fold one) → Goes to State A, sees nothing +7. Back to: `⏵ 2 messages ⏵⏵ 125 total` +8. Clicks ⏵ (unfold one) → Goes to State B, sees first level +9. Now sees: `⏷ fold 2 ⏵⏵ fold all 125 below` This creates a natural exploration pattern: nothing → all levels → first level → nothing → first level. @@ -125,10 +125,10 @@ Fold buttons display context-aware tooltips showing what will happen on click (n | Button State | Tooltip | |--------------|---------| -| ▶ (fold-one, folded) | "Unfold (1st level)..." | -| ▼ (fold-one, unfolded) | "Fold (all levels)..." | -| ▶▶ (fold-all, folded) | "Unfold (all levels)..." | -| ▼▼ (fold-all, unfolded) | "Fold (to 1st level)..." | +| ⏵ (fold-one, folded) | "Unfold (1st level)..." | +| ⏷ (fold-one, unfolded) | "Fold (all levels)..." | +| ⏵⏵ (fold-all, folded) | "Unfold (all levels)..." | +| ⏷⏷ (fold-all, unfolded) | "Fold (to 1st level)..." | ## Implementation Notes @@ -238,8 +238,8 @@ Calculates descendant counts for fold bar labels. The JavaScript in `templates/components/fold_bar.html` uses these computed values: 1. **Ancestry classes**: Each message has `d-{n}` classes from ancestry for CSS targeting -2. **Child counts**: Displayed in fold bar buttons ("▶ 3 messages") -3. **Descendant counts**: Displayed in fold-all button ("▶▶ 125 total") +2. **Child counts**: Displayed in fold bar buttons ("⏵ 3 messages") +3. **Descendant counts**: Displayed in fold-all button ("⏵⏵ 125 total") 4. **Type counts**: Used for descriptive labels ("2 assistant, 4 tools") **Visibility Control:** diff --git a/test/__snapshots__/test_snapshot_html.ambr b/test/__snapshots__/test_snapshot_html.ambr index 26266287..1dc3b3d0 100644 --- a/test/__snapshots__/test_snapshot_html.ambr +++ b/test/__snapshots__/test_snapshot_html.ambr @@ -453,14 +453,11 @@ font-size: 1.1em; line-height: 1; color: var(--fold-color); - /* Tighten the gap between the two glyphs in ``▼▼`` / ``▶▶`` — - Chrome renders default kerning much wider than Firefox does, - leaving an awkward gap. The pulled-in -0.2em compromise reads - cleanly in Chrome without crowding the glyphs in Firefox - (issue #73 / #153). No visible effect on single-glyph ``▼`` - since letter-spacing only matters when two characters are - adjacent. */ - letter-spacing: -.2em; + /* The doubled glyphs ``⏷⏷`` / ``⏵⏵`` (U+23F7 / U+23F5) sit at a + natural, even spacing in both Chrome and Firefox, so no kerning + trick is needed (the earlier ``▼▼`` / ``▶▶`` needed a -0.2em + letter-spacing pull-in to tame Chrome's wide default — issue + #73 / #153 / #247). */ } .fold-count { @@ -1420,9 +1417,9 @@ } /* Fold-valued rows hoist their toggle into the KEY column: the key cell's - ▶/▼ button (wired in transcript.html, state derived from toggle events) + ⏵/⏷ button (wired in transcript.html, state derived from toggle events) replaces the per-fold in-summary collapse chrome, which only remained - useful en masse — every row showing "▼ collapse" drowned the content. + useful en masse — every row showing "⏷ collapse" drowned the content. Scoped to keyed rows: the top-level root fold has no key column and keeps its summary affordances (the pure-CSS ::after "collapse" hint — no interactive child). */ @@ -1446,7 +1443,7 @@ /* The shared fold glyph: the BROWSER'S native disclosure marker (the same UA-drawn icon a shows) via display:list-item — geometrically symmetric open/closed, and immune to the italic style of the button - labels (font glyphs like ▶/▼ skew in italics; the marker icon doesn't). + labels (font glyphs like ⏵/⏷ skew in italics; the marker icon doesn't). The empty span doubles as the key-column alignment slot: every key cell renders one (markerless on scalar rows), so all key texts start at the same x. */ @@ -1463,13 +1460,13 @@ button > .tool-param-fold-glyph { display: list-item; list-style-position: inside; - list-style-type: "\25B8"; + list-style-type: "\23F5"; list-style-type: disclosure-closed; } .tool-param-key-toggle[aria-expanded='true'] > .tool-param-fold-glyph, [data-state='expanded'] > .tool-param-fold-glyph { - list-style-type: "\25BE"; + list-style-type: "\23F7"; list-style-type: disclosure-open; } @@ -1492,7 +1489,7 @@ display: none; } - /* A keyed fold's open summary carries nothing (the key glyph shows ▼ and + /* A keyed fold's open summary carries nothing (the key glyph shows ⏷ and the rows-toggle sits in the controls strip below) — drop the line it would occupy. This also suppresses the generic ::after "collapse". */ .tool-param-row-fold > .tool-param-value > details[open] > summary { @@ -4605,11 +4602,11 @@
- + 1 user
- ▼▼ + ⏷⏷ 3 assistants, 2 users, 4 more total
@@ -4640,11 +4637,11 @@
- + 2 tools, 1 task_notification, 1 more
- ▼▼ + ⏷⏷ 3 assistants, 2 tools, 4 more total
@@ -4704,7 +4701,7 @@
- + 2 assistants, 1 user
@@ -5159,7 +5156,7 @@ const allOpen = rows.length > 0 && Array.from(rows).every(row => row.open); const kind = button.dataset.kind || 'rows'; - // data-state drives the CSS rotation of the constant ▸ glyph; + // data-state drives the CSS rotation of the constant ⏵ glyph; // only the label text changes. button.dataset.state = allOpen ? 'expanded' : 'collapsed'; const label = button.querySelector('.tool-param-fold-label'); @@ -5195,7 +5192,7 @@ syncExpandAll(root); return; } - // Key-column fold toggle (▶/▼): drives the value cell's + // Key-column fold toggle (⏵/⏷): drives the value cell's // details from the key cell, keeping the summary free of // per-fold collapse chrome. Glyph state is derived in the // toggle listener below, so any other opener stays in sync. @@ -5243,7 +5240,7 @@ const keyBtn = row && row.querySelector( ':scope > .tool-param-key > .tool-param-key-toggle'); if (keyBtn) { - // CSS rotates the single ▸ glyph on aria-expanded — + // CSS rotates the single ⏵ glyph on aria-expanded — // symmetric open/closed states by construction. keyBtn.setAttribute('aria-expanded', details.open ? 'true' : 'false'); @@ -5580,18 +5577,18 @@ const allSection = foldBar ? foldBar.querySelector('.fold-all-levels') : null; if (state === 'folded') { if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } else if (state === 'first') { if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'folded')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // 'open' if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'open')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, false, '▼▼'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, false, '⏷⏷'); } } @@ -5637,15 +5634,15 @@ const cc = getChildrenContainer(msg); if ((isSession || isUser) && !hasOnlyTools) { - // First level visible (▼), deeper levels folded (▶▶) + // First level visible (⏷), deeper levels folded (⏵⏵) if (cc) cc.style.display = ''; - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // Fully folded if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } }); } @@ -5672,8 +5669,8 @@ const card = wrapper ? wrapper.querySelector(':scope > .message') : null; const foldBar = card ? card.querySelector(':scope > .fold-bar') : null; if (foldBar) { - setSectionState(foldBar.querySelector('.fold-one-level'), false, '▼'); - setSectionState(foldBar.querySelector('.fold-all-levels'), false, '▼▼'); + setSectionState(foldBar.querySelector('.fold-one-level'), false, '⏷'); + setSectionState(foldBar.querySelector('.fold-all-levels'), false, '⏷⏷'); } } node = node.parentElement; @@ -6889,14 +6886,11 @@ font-size: 1.1em; line-height: 1; color: var(--fold-color); - /* Tighten the gap between the two glyphs in ``▼▼`` / ``▶▶`` — - Chrome renders default kerning much wider than Firefox does, - leaving an awkward gap. The pulled-in -0.2em compromise reads - cleanly in Chrome without crowding the glyphs in Firefox - (issue #73 / #153). No visible effect on single-glyph ``▼`` - since letter-spacing only matters when two characters are - adjacent. */ - letter-spacing: -.2em; + /* The doubled glyphs ``⏷⏷`` / ``⏵⏵`` (U+23F7 / U+23F5) sit at a + natural, even spacing in both Chrome and Firefox, so no kerning + trick is needed (the earlier ``▼▼`` / ``▶▶`` needed a -0.2em + letter-spacing pull-in to tame Chrome's wide default — issue + #73 / #153 / #247). */ } .fold-count { @@ -7856,9 +7850,9 @@ } /* Fold-valued rows hoist their toggle into the KEY column: the key cell's - ▶/▼ button (wired in transcript.html, state derived from toggle events) + ⏵/⏷ button (wired in transcript.html, state derived from toggle events) replaces the per-fold in-summary collapse chrome, which only remained - useful en masse — every row showing "▼ collapse" drowned the content. + useful en masse — every row showing "⏷ collapse" drowned the content. Scoped to keyed rows: the top-level root fold has no key column and keeps its summary affordances (the pure-CSS ::after "collapse" hint — no interactive child). */ @@ -7882,7 +7876,7 @@ /* The shared fold glyph: the BROWSER'S native disclosure marker (the same UA-drawn icon a shows) via display:list-item — geometrically symmetric open/closed, and immune to the italic style of the button - labels (font glyphs like ▶/▼ skew in italics; the marker icon doesn't). + labels (font glyphs like ⏵/⏷ skew in italics; the marker icon doesn't). The empty span doubles as the key-column alignment slot: every key cell renders one (markerless on scalar rows), so all key texts start at the same x. */ @@ -7899,13 +7893,13 @@ button > .tool-param-fold-glyph { display: list-item; list-style-position: inside; - list-style-type: "\25B8"; + list-style-type: "\23F5"; list-style-type: disclosure-closed; } .tool-param-key-toggle[aria-expanded='true'] > .tool-param-fold-glyph, [data-state='expanded'] > .tool-param-fold-glyph { - list-style-type: "\25BE"; + list-style-type: "\23F7"; list-style-type: disclosure-open; } @@ -7928,7 +7922,7 @@ display: none; } - /* A keyed fold's open summary carries nothing (the key glyph shows ▼ and + /* A keyed fold's open summary carries nothing (the key glyph shows ⏷ and the rows-toggle sits in the controls strip below) — drop the line it would occupy. This also suppresses the generic ::after "collapse". */ .tool-param-row-fold > .tool-param-value > details[open] > summary { @@ -11041,11 +11035,11 @@
- + 1 user
- ▼▼ + ⏷⏷ 1 user, 1 tool, 2 more total
@@ -11076,7 +11070,7 @@
- + 1 tool, 1 task_notification, 1 more
@@ -11494,7 +11488,7 @@ const allOpen = rows.length > 0 && Array.from(rows).every(row => row.open); const kind = button.dataset.kind || 'rows'; - // data-state drives the CSS rotation of the constant ▸ glyph; + // data-state drives the CSS rotation of the constant ⏵ glyph; // only the label text changes. button.dataset.state = allOpen ? 'expanded' : 'collapsed'; const label = button.querySelector('.tool-param-fold-label'); @@ -11530,7 +11524,7 @@ syncExpandAll(root); return; } - // Key-column fold toggle (▶/▼): drives the value cell's + // Key-column fold toggle (⏵/⏷): drives the value cell's // details from the key cell, keeping the summary free of // per-fold collapse chrome. Glyph state is derived in the // toggle listener below, so any other opener stays in sync. @@ -11578,7 +11572,7 @@ const keyBtn = row && row.querySelector( ':scope > .tool-param-key > .tool-param-key-toggle'); if (keyBtn) { - // CSS rotates the single ▸ glyph on aria-expanded — + // CSS rotates the single ⏵ glyph on aria-expanded — // symmetric open/closed states by construction. keyBtn.setAttribute('aria-expanded', details.open ? 'true' : 'false'); @@ -11915,18 +11909,18 @@ const allSection = foldBar ? foldBar.querySelector('.fold-all-levels') : null; if (state === 'folded') { if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } else if (state === 'first') { if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'folded')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // 'open' if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'open')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, false, '▼▼'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, false, '⏷⏷'); } } @@ -11972,15 +11966,15 @@ const cc = getChildrenContainer(msg); if ((isSession || isUser) && !hasOnlyTools) { - // First level visible (▼), deeper levels folded (▶▶) + // First level visible (⏷), deeper levels folded (⏵⏵) if (cc) cc.style.display = ''; - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // Fully folded if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } }); } @@ -12007,8 +12001,8 @@ const card = wrapper ? wrapper.querySelector(':scope > .message') : null; const foldBar = card ? card.querySelector(':scope > .fold-bar') : null; if (foldBar) { - setSectionState(foldBar.querySelector('.fold-one-level'), false, '▼'); - setSectionState(foldBar.querySelector('.fold-all-levels'), false, '▼▼'); + setSectionState(foldBar.querySelector('.fold-one-level'), false, '⏷'); + setSectionState(foldBar.querySelector('.fold-all-levels'), false, '⏷⏷'); } } node = node.parentElement; @@ -15427,14 +15421,11 @@ font-size: 1.1em; line-height: 1; color: var(--fold-color); - /* Tighten the gap between the two glyphs in ``▼▼`` / ``▶▶`` — - Chrome renders default kerning much wider than Firefox does, - leaving an awkward gap. The pulled-in -0.2em compromise reads - cleanly in Chrome without crowding the glyphs in Firefox - (issue #73 / #153). No visible effect on single-glyph ``▼`` - since letter-spacing only matters when two characters are - adjacent. */ - letter-spacing: -.2em; + /* The doubled glyphs ``⏷⏷`` / ``⏵⏵`` (U+23F7 / U+23F5) sit at a + natural, even spacing in both Chrome and Firefox, so no kerning + trick is needed (the earlier ``▼▼`` / ``▶▶`` needed a -0.2em + letter-spacing pull-in to tame Chrome's wide default — issue + #73 / #153 / #247). */ } .fold-count { @@ -16394,9 +16385,9 @@ } /* Fold-valued rows hoist their toggle into the KEY column: the key cell's - ▶/▼ button (wired in transcript.html, state derived from toggle events) + ⏵/⏷ button (wired in transcript.html, state derived from toggle events) replaces the per-fold in-summary collapse chrome, which only remained - useful en masse — every row showing "▼ collapse" drowned the content. + useful en masse — every row showing "⏷ collapse" drowned the content. Scoped to keyed rows: the top-level root fold has no key column and keeps its summary affordances (the pure-CSS ::after "collapse" hint — no interactive child). */ @@ -16420,7 +16411,7 @@ /* The shared fold glyph: the BROWSER'S native disclosure marker (the same UA-drawn icon a shows) via display:list-item — geometrically symmetric open/closed, and immune to the italic style of the button - labels (font glyphs like ▶/▼ skew in italics; the marker icon doesn't). + labels (font glyphs like ⏵/⏷ skew in italics; the marker icon doesn't). The empty span doubles as the key-column alignment slot: every key cell renders one (markerless on scalar rows), so all key texts start at the same x. */ @@ -16437,13 +16428,13 @@ button > .tool-param-fold-glyph { display: list-item; list-style-position: inside; - list-style-type: "\25B8"; + list-style-type: "\23F5"; list-style-type: disclosure-closed; } .tool-param-key-toggle[aria-expanded='true'] > .tool-param-fold-glyph, [data-state='expanded'] > .tool-param-fold-glyph { - list-style-type: "\25BE"; + list-style-type: "\23F7"; list-style-type: disclosure-open; } @@ -16466,7 +16457,7 @@ display: none; } - /* A keyed fold's open summary carries nothing (the key glyph shows ▼ and + /* A keyed fold's open summary carries nothing (the key glyph shows ⏷ and the rows-toggle sits in the controls strip below) — drop the line it would occupy. This also suppresses the generic ::after "collapse". */ .tool-param-row-fold > .tool-param-value > details[open] > summary { @@ -19579,11 +19570,11 @@
- + 4 users
- ▼▼ + ⏷⏷ 4 users, 3 assistants, 2 more total
@@ -19614,7 +19605,7 @@
- + 1 assistant
@@ -19693,7 +19684,7 @@
- + 1 tool, 1 assistant
@@ -19807,7 +19798,7 @@
- + 1 tool, 1 assistant
@@ -20248,7 +20239,7 @@ const allOpen = rows.length > 0 && Array.from(rows).every(row => row.open); const kind = button.dataset.kind || 'rows'; - // data-state drives the CSS rotation of the constant ▸ glyph; + // data-state drives the CSS rotation of the constant ⏵ glyph; // only the label text changes. button.dataset.state = allOpen ? 'expanded' : 'collapsed'; const label = button.querySelector('.tool-param-fold-label'); @@ -20284,7 +20275,7 @@ syncExpandAll(root); return; } - // Key-column fold toggle (▶/▼): drives the value cell's + // Key-column fold toggle (⏵/⏷): drives the value cell's // details from the key cell, keeping the summary free of // per-fold collapse chrome. Glyph state is derived in the // toggle listener below, so any other opener stays in sync. @@ -20332,7 +20323,7 @@ const keyBtn = row && row.querySelector( ':scope > .tool-param-key > .tool-param-key-toggle'); if (keyBtn) { - // CSS rotates the single ▸ glyph on aria-expanded — + // CSS rotates the single ⏵ glyph on aria-expanded — // symmetric open/closed states by construction. keyBtn.setAttribute('aria-expanded', details.open ? 'true' : 'false'); @@ -20669,18 +20660,18 @@ const allSection = foldBar ? foldBar.querySelector('.fold-all-levels') : null; if (state === 'folded') { if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } else if (state === 'first') { if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'folded')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // 'open' if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'open')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, false, '▼▼'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, false, '⏷⏷'); } } @@ -20726,15 +20717,15 @@ const cc = getChildrenContainer(msg); if ((isSession || isUser) && !hasOnlyTools) { - // First level visible (▼), deeper levels folded (▶▶) + // First level visible (⏷), deeper levels folded (⏵⏵) if (cc) cc.style.display = ''; - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // Fully folded if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } }); } @@ -20761,8 +20752,8 @@ const card = wrapper ? wrapper.querySelector(':scope > .message') : null; const foldBar = card ? card.querySelector(':scope > .fold-bar') : null; if (foldBar) { - setSectionState(foldBar.querySelector('.fold-one-level'), false, '▼'); - setSectionState(foldBar.querySelector('.fold-all-levels'), false, '▼▼'); + setSectionState(foldBar.querySelector('.fold-one-level'), false, '⏷'); + setSectionState(foldBar.querySelector('.fold-all-levels'), false, '⏷⏷'); } } node = node.parentElement; @@ -21978,14 +21969,11 @@ font-size: 1.1em; line-height: 1; color: var(--fold-color); - /* Tighten the gap between the two glyphs in ``▼▼`` / ``▶▶`` — - Chrome renders default kerning much wider than Firefox does, - leaving an awkward gap. The pulled-in -0.2em compromise reads - cleanly in Chrome without crowding the glyphs in Firefox - (issue #73 / #153). No visible effect on single-glyph ``▼`` - since letter-spacing only matters when two characters are - adjacent. */ - letter-spacing: -.2em; + /* The doubled glyphs ``⏷⏷`` / ``⏵⏵`` (U+23F7 / U+23F5) sit at a + natural, even spacing in both Chrome and Firefox, so no kerning + trick is needed (the earlier ``▼▼`` / ``▶▶`` needed a -0.2em + letter-spacing pull-in to tame Chrome's wide default — issue + #73 / #153 / #247). */ } .fold-count { @@ -22945,9 +22933,9 @@ } /* Fold-valued rows hoist their toggle into the KEY column: the key cell's - ▶/▼ button (wired in transcript.html, state derived from toggle events) + ⏵/⏷ button (wired in transcript.html, state derived from toggle events) replaces the per-fold in-summary collapse chrome, which only remained - useful en masse — every row showing "▼ collapse" drowned the content. + useful en masse — every row showing "⏷ collapse" drowned the content. Scoped to keyed rows: the top-level root fold has no key column and keeps its summary affordances (the pure-CSS ::after "collapse" hint — no interactive child). */ @@ -22971,7 +22959,7 @@ /* The shared fold glyph: the BROWSER'S native disclosure marker (the same UA-drawn icon a shows) via display:list-item — geometrically symmetric open/closed, and immune to the italic style of the button - labels (font glyphs like ▶/▼ skew in italics; the marker icon doesn't). + labels (font glyphs like ⏵/⏷ skew in italics; the marker icon doesn't). The empty span doubles as the key-column alignment slot: every key cell renders one (markerless on scalar rows), so all key texts start at the same x. */ @@ -22988,13 +22976,13 @@ button > .tool-param-fold-glyph { display: list-item; list-style-position: inside; - list-style-type: "\25B8"; + list-style-type: "\23F5"; list-style-type: disclosure-closed; } .tool-param-key-toggle[aria-expanded='true'] > .tool-param-fold-glyph, [data-state='expanded'] > .tool-param-fold-glyph { - list-style-type: "\25BE"; + list-style-type: "\23F7"; list-style-type: disclosure-open; } @@ -23017,7 +23005,7 @@ display: none; } - /* A keyed fold's open summary carries nothing (the key glyph shows ▼ and + /* A keyed fold's open summary carries nothing (the key glyph shows ⏷ and the rows-toggle sits in the controls strip below) — drop the line it would occupy. This also suppresses the generic ::after "collapse". */ .tool-param-row-fold > .tool-param-value > details[open] > summary { @@ -26130,11 +26118,11 @@
- + 2 teammates, 1 user
- ▼▼ + ⏷⏷ 9 tools, 5 assistants, 8 more total
@@ -26165,11 +26153,11 @@
- + 6 tools
- ▼▼ + ⏷⏷ 6 tools, 4 assistants, 5 more total
@@ -26337,7 +26325,7 @@
- + 2 assistants, 1 user
@@ -26439,7 +26427,7 @@
- + 2 assistants, 1 teammate
@@ -26574,7 +26562,7 @@
- + 3 tools, 1 assistant
@@ -27066,7 +27054,7 @@ const allOpen = rows.length > 0 && Array.from(rows).every(row => row.open); const kind = button.dataset.kind || 'rows'; - // data-state drives the CSS rotation of the constant ▸ glyph; + // data-state drives the CSS rotation of the constant ⏵ glyph; // only the label text changes. button.dataset.state = allOpen ? 'expanded' : 'collapsed'; const label = button.querySelector('.tool-param-fold-label'); @@ -27102,7 +27090,7 @@ syncExpandAll(root); return; } - // Key-column fold toggle (▶/▼): drives the value cell's + // Key-column fold toggle (⏵/⏷): drives the value cell's // details from the key cell, keeping the summary free of // per-fold collapse chrome. Glyph state is derived in the // toggle listener below, so any other opener stays in sync. @@ -27150,7 +27138,7 @@ const keyBtn = row && row.querySelector( ':scope > .tool-param-key > .tool-param-key-toggle'); if (keyBtn) { - // CSS rotates the single ▸ glyph on aria-expanded — + // CSS rotates the single ⏵ glyph on aria-expanded — // symmetric open/closed states by construction. keyBtn.setAttribute('aria-expanded', details.open ? 'true' : 'false'); @@ -27487,18 +27475,18 @@ const allSection = foldBar ? foldBar.querySelector('.fold-all-levels') : null; if (state === 'folded') { if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } else if (state === 'first') { if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'folded')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // 'open' if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'open')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, false, '▼▼'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, false, '⏷⏷'); } } @@ -27544,15 +27532,15 @@ const cc = getChildrenContainer(msg); if ((isSession || isUser) && !hasOnlyTools) { - // First level visible (▼), deeper levels folded (▶▶) + // First level visible (⏷), deeper levels folded (⏵⏵) if (cc) cc.style.display = ''; - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // Fully folded if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } }); } @@ -27579,8 +27567,8 @@ const card = wrapper ? wrapper.querySelector(':scope > .message') : null; const foldBar = card ? card.querySelector(':scope > .fold-bar') : null; if (foldBar) { - setSectionState(foldBar.querySelector('.fold-one-level'), false, '▼'); - setSectionState(foldBar.querySelector('.fold-all-levels'), false, '▼▼'); + setSectionState(foldBar.querySelector('.fold-one-level'), false, '⏷'); + setSectionState(foldBar.querySelector('.fold-all-levels'), false, '⏷⏷'); } } node = node.parentElement; @@ -28796,14 +28784,11 @@ font-size: 1.1em; line-height: 1; color: var(--fold-color); - /* Tighten the gap between the two glyphs in ``▼▼`` / ``▶▶`` — - Chrome renders default kerning much wider than Firefox does, - leaving an awkward gap. The pulled-in -0.2em compromise reads - cleanly in Chrome without crowding the glyphs in Firefox - (issue #73 / #153). No visible effect on single-glyph ``▼`` - since letter-spacing only matters when two characters are - adjacent. */ - letter-spacing: -.2em; + /* The doubled glyphs ``⏷⏷`` / ``⏵⏵`` (U+23F7 / U+23F5) sit at a + natural, even spacing in both Chrome and Firefox, so no kerning + trick is needed (the earlier ``▼▼`` / ``▶▶`` needed a -0.2em + letter-spacing pull-in to tame Chrome's wide default — issue + #73 / #153 / #247). */ } .fold-count { @@ -29763,9 +29748,9 @@ } /* Fold-valued rows hoist their toggle into the KEY column: the key cell's - ▶/▼ button (wired in transcript.html, state derived from toggle events) + ⏵/⏷ button (wired in transcript.html, state derived from toggle events) replaces the per-fold in-summary collapse chrome, which only remained - useful en masse — every row showing "▼ collapse" drowned the content. + useful en masse — every row showing "⏷ collapse" drowned the content. Scoped to keyed rows: the top-level root fold has no key column and keeps its summary affordances (the pure-CSS ::after "collapse" hint — no interactive child). */ @@ -29789,7 +29774,7 @@ /* The shared fold glyph: the BROWSER'S native disclosure marker (the same UA-drawn icon a shows) via display:list-item — geometrically symmetric open/closed, and immune to the italic style of the button - labels (font glyphs like ▶/▼ skew in italics; the marker icon doesn't). + labels (font glyphs like ⏵/⏷ skew in italics; the marker icon doesn't). The empty span doubles as the key-column alignment slot: every key cell renders one (markerless on scalar rows), so all key texts start at the same x. */ @@ -29806,13 +29791,13 @@ button > .tool-param-fold-glyph { display: list-item; list-style-position: inside; - list-style-type: "\25B8"; + list-style-type: "\23F5"; list-style-type: disclosure-closed; } .tool-param-key-toggle[aria-expanded='true'] > .tool-param-fold-glyph, [data-state='expanded'] > .tool-param-fold-glyph { - list-style-type: "\25BE"; + list-style-type: "\23F7"; list-style-type: disclosure-open; } @@ -29835,7 +29820,7 @@ display: none; } - /* A keyed fold's open summary carries nothing (the key glyph shows ▼ and + /* A keyed fold's open summary carries nothing (the key glyph shows ⏷ and the rows-toggle sits in the controls strip below) — drop the line it would occupy. This also suppresses the generic ::after "collapse". */ .tool-param-row-fold > .tool-param-value > details[open] > summary { @@ -32948,11 +32933,11 @@
- + 5 users
- ▼▼ + ⏷⏷ 5 users, 2 assistants, 2 more total
@@ -32983,7 +32968,7 @@
- + 1 assistant
@@ -33081,7 +33066,7 @@
- + 1 tool
@@ -33191,11 +33176,11 @@
- + 1 assistant
- ▼▼ + ⏷⏷ 1 assistant, 1 tool total
@@ -33228,7 +33213,7 @@
- + 1 tool
@@ -33329,7 +33314,7 @@
- + 1 tool
@@ -33847,7 +33832,7 @@ const allOpen = rows.length > 0 && Array.from(rows).every(row => row.open); const kind = button.dataset.kind || 'rows'; - // data-state drives the CSS rotation of the constant ▸ glyph; + // data-state drives the CSS rotation of the constant ⏵ glyph; // only the label text changes. button.dataset.state = allOpen ? 'expanded' : 'collapsed'; const label = button.querySelector('.tool-param-fold-label'); @@ -33883,7 +33868,7 @@ syncExpandAll(root); return; } - // Key-column fold toggle (▶/▼): drives the value cell's + // Key-column fold toggle (⏵/⏷): drives the value cell's // details from the key cell, keeping the summary free of // per-fold collapse chrome. Glyph state is derived in the // toggle listener below, so any other opener stays in sync. @@ -33931,7 +33916,7 @@ const keyBtn = row && row.querySelector( ':scope > .tool-param-key > .tool-param-key-toggle'); if (keyBtn) { - // CSS rotates the single ▸ glyph on aria-expanded — + // CSS rotates the single ⏵ glyph on aria-expanded — // symmetric open/closed states by construction. keyBtn.setAttribute('aria-expanded', details.open ? 'true' : 'false'); @@ -34268,18 +34253,18 @@ const allSection = foldBar ? foldBar.querySelector('.fold-all-levels') : null; if (state === 'folded') { if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } else if (state === 'first') { if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'folded')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // 'open' if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'open')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, false, '▼▼'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, false, '⏷⏷'); } } @@ -34325,15 +34310,15 @@ const cc = getChildrenContainer(msg); if ((isSession || isUser) && !hasOnlyTools) { - // First level visible (▼), deeper levels folded (▶▶) + // First level visible (⏷), deeper levels folded (⏵⏵) if (cc) cc.style.display = ''; - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // Fully folded if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } }); } @@ -34360,8 +34345,8 @@ const card = wrapper ? wrapper.querySelector(':scope > .message') : null; const foldBar = card ? card.querySelector(':scope > .fold-bar') : null; if (foldBar) { - setSectionState(foldBar.querySelector('.fold-one-level'), false, '▼'); - setSectionState(foldBar.querySelector('.fold-all-levels'), false, '▼▼'); + setSectionState(foldBar.querySelector('.fold-one-level'), false, '⏷'); + setSectionState(foldBar.querySelector('.fold-all-levels'), false, '⏷⏷'); } } node = node.parentElement; @@ -35577,14 +35562,11 @@ font-size: 1.1em; line-height: 1; color: var(--fold-color); - /* Tighten the gap between the two glyphs in ``▼▼`` / ``▶▶`` — - Chrome renders default kerning much wider than Firefox does, - leaving an awkward gap. The pulled-in -0.2em compromise reads - cleanly in Chrome without crowding the glyphs in Firefox - (issue #73 / #153). No visible effect on single-glyph ``▼`` - since letter-spacing only matters when two characters are - adjacent. */ - letter-spacing: -.2em; + /* The doubled glyphs ``⏷⏷`` / ``⏵⏵`` (U+23F7 / U+23F5) sit at a + natural, even spacing in both Chrome and Firefox, so no kerning + trick is needed (the earlier ``▼▼`` / ``▶▶`` needed a -0.2em + letter-spacing pull-in to tame Chrome's wide default — issue + #73 / #153 / #247). */ } .fold-count { @@ -36544,9 +36526,9 @@ } /* Fold-valued rows hoist their toggle into the KEY column: the key cell's - ▶/▼ button (wired in transcript.html, state derived from toggle events) + ⏵/⏷ button (wired in transcript.html, state derived from toggle events) replaces the per-fold in-summary collapse chrome, which only remained - useful en masse — every row showing "▼ collapse" drowned the content. + useful en masse — every row showing "⏷ collapse" drowned the content. Scoped to keyed rows: the top-level root fold has no key column and keeps its summary affordances (the pure-CSS ::after "collapse" hint — no interactive child). */ @@ -36570,7 +36552,7 @@ /* The shared fold glyph: the BROWSER'S native disclosure marker (the same UA-drawn icon a shows) via display:list-item — geometrically symmetric open/closed, and immune to the italic style of the button - labels (font glyphs like ▶/▼ skew in italics; the marker icon doesn't). + labels (font glyphs like ⏵/⏷ skew in italics; the marker icon doesn't). The empty span doubles as the key-column alignment slot: every key cell renders one (markerless on scalar rows), so all key texts start at the same x. */ @@ -36587,13 +36569,13 @@ button > .tool-param-fold-glyph { display: list-item; list-style-position: inside; - list-style-type: "\25B8"; + list-style-type: "\23F5"; list-style-type: disclosure-closed; } .tool-param-key-toggle[aria-expanded='true'] > .tool-param-fold-glyph, [data-state='expanded'] > .tool-param-fold-glyph { - list-style-type: "\25BE"; + list-style-type: "\23F7"; list-style-type: disclosure-open; } @@ -36616,7 +36598,7 @@ display: none; } - /* A keyed fold's open summary carries nothing (the key glyph shows ▼ and + /* A keyed fold's open summary carries nothing (the key glyph shows ⏷ and the rows-toggle sits in the controls strip below) — drop the line it would occupy. This also suppresses the generic ::after "collapse". */ .tool-param-row-fold > .tool-param-value > details[open] > summary { @@ -39790,11 +39772,11 @@
- + 2 users
- ▼▼ + ⏷⏷ 2 users, 1 assistant total
@@ -39825,7 +39807,7 @@
- + 1 assistant
@@ -39902,11 +39884,11 @@
- + 4 users
- ▼▼ + ⏷⏷ 4 users, 3 assistants, 2 more total
@@ -39937,7 +39919,7 @@
- + 1 assistant
@@ -40016,7 +39998,7 @@
- + 1 tool, 1 assistant
@@ -40130,7 +40112,7 @@
- + 1 tool, 1 assistant
@@ -40571,7 +40553,7 @@ const allOpen = rows.length > 0 && Array.from(rows).every(row => row.open); const kind = button.dataset.kind || 'rows'; - // data-state drives the CSS rotation of the constant ▸ glyph; + // data-state drives the CSS rotation of the constant ⏵ glyph; // only the label text changes. button.dataset.state = allOpen ? 'expanded' : 'collapsed'; const label = button.querySelector('.tool-param-fold-label'); @@ -40607,7 +40589,7 @@ syncExpandAll(root); return; } - // Key-column fold toggle (▶/▼): drives the value cell's + // Key-column fold toggle (⏵/⏷): drives the value cell's // details from the key cell, keeping the summary free of // per-fold collapse chrome. Glyph state is derived in the // toggle listener below, so any other opener stays in sync. @@ -40655,7 +40637,7 @@ const keyBtn = row && row.querySelector( ':scope > .tool-param-key > .tool-param-key-toggle'); if (keyBtn) { - // CSS rotates the single ▸ glyph on aria-expanded — + // CSS rotates the single ⏵ glyph on aria-expanded — // symmetric open/closed states by construction. keyBtn.setAttribute('aria-expanded', details.open ? 'true' : 'false'); @@ -40992,18 +40974,18 @@ const allSection = foldBar ? foldBar.querySelector('.fold-all-levels') : null; if (state === 'folded') { if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } else if (state === 'first') { if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'folded')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // 'open' if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'open')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, false, '▼▼'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, false, '⏷⏷'); } } @@ -41049,15 +41031,15 @@ const cc = getChildrenContainer(msg); if ((isSession || isUser) && !hasOnlyTools) { - // First level visible (▼), deeper levels folded (▶▶) + // First level visible (⏷), deeper levels folded (⏵⏵) if (cc) cc.style.display = ''; - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // Fully folded if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } }); } @@ -41084,8 +41066,8 @@ const card = wrapper ? wrapper.querySelector(':scope > .message') : null; const foldBar = card ? card.querySelector(':scope > .fold-bar') : null; if (foldBar) { - setSectionState(foldBar.querySelector('.fold-one-level'), false, '▼'); - setSectionState(foldBar.querySelector('.fold-all-levels'), false, '▼▼'); + setSectionState(foldBar.querySelector('.fold-one-level'), false, '⏷'); + setSectionState(foldBar.querySelector('.fold-all-levels'), false, '⏷⏷'); } } node = node.parentElement; @@ -42301,14 +42283,11 @@ font-size: 1.1em; line-height: 1; color: var(--fold-color); - /* Tighten the gap between the two glyphs in ``▼▼`` / ``▶▶`` — - Chrome renders default kerning much wider than Firefox does, - leaving an awkward gap. The pulled-in -0.2em compromise reads - cleanly in Chrome without crowding the glyphs in Firefox - (issue #73 / #153). No visible effect on single-glyph ``▼`` - since letter-spacing only matters when two characters are - adjacent. */ - letter-spacing: -.2em; + /* The doubled glyphs ``⏷⏷`` / ``⏵⏵`` (U+23F7 / U+23F5) sit at a + natural, even spacing in both Chrome and Firefox, so no kerning + trick is needed (the earlier ``▼▼`` / ``▶▶`` needed a -0.2em + letter-spacing pull-in to tame Chrome's wide default — issue + #73 / #153 / #247). */ } .fold-count { @@ -43268,9 +43247,9 @@ } /* Fold-valued rows hoist their toggle into the KEY column: the key cell's - ▶/▼ button (wired in transcript.html, state derived from toggle events) + ⏵/⏷ button (wired in transcript.html, state derived from toggle events) replaces the per-fold in-summary collapse chrome, which only remained - useful en masse — every row showing "▼ collapse" drowned the content. + useful en masse — every row showing "⏷ collapse" drowned the content. Scoped to keyed rows: the top-level root fold has no key column and keeps its summary affordances (the pure-CSS ::after "collapse" hint — no interactive child). */ @@ -43294,7 +43273,7 @@ /* The shared fold glyph: the BROWSER'S native disclosure marker (the same UA-drawn icon a shows) via display:list-item — geometrically symmetric open/closed, and immune to the italic style of the button - labels (font glyphs like ▶/▼ skew in italics; the marker icon doesn't). + labels (font glyphs like ⏵/⏷ skew in italics; the marker icon doesn't). The empty span doubles as the key-column alignment slot: every key cell renders one (markerless on scalar rows), so all key texts start at the same x. */ @@ -43311,13 +43290,13 @@ button > .tool-param-fold-glyph { display: list-item; list-style-position: inside; - list-style-type: "\25B8"; + list-style-type: "\23F5"; list-style-type: disclosure-closed; } .tool-param-key-toggle[aria-expanded='true'] > .tool-param-fold-glyph, [data-state='expanded'] > .tool-param-fold-glyph { - list-style-type: "\25BE"; + list-style-type: "\23F7"; list-style-type: disclosure-open; } @@ -43340,7 +43319,7 @@ display: none; } - /* A keyed fold's open summary carries nothing (the key glyph shows ▼ and + /* A keyed fold's open summary carries nothing (the key glyph shows ⏷ and the rows-toggle sits in the controls strip below) — drop the line it would occupy. This also suppresses the generic ::after "collapse". */ .tool-param-row-fold > .tool-param-value > details[open] > summary { @@ -46453,11 +46432,11 @@
- + 4 users
- ▼▼ + ⏷⏷ 4 users, 3 assistants, 2 more total
@@ -46488,7 +46467,7 @@
- + 1 assistant
@@ -46567,7 +46546,7 @@
- + 1 tool, 1 assistant
@@ -46681,7 +46660,7 @@
- + 1 tool, 1 assistant
@@ -47122,7 +47101,7 @@ const allOpen = rows.length > 0 && Array.from(rows).every(row => row.open); const kind = button.dataset.kind || 'rows'; - // data-state drives the CSS rotation of the constant ▸ glyph; + // data-state drives the CSS rotation of the constant ⏵ glyph; // only the label text changes. button.dataset.state = allOpen ? 'expanded' : 'collapsed'; const label = button.querySelector('.tool-param-fold-label'); @@ -47158,7 +47137,7 @@ syncExpandAll(root); return; } - // Key-column fold toggle (▶/▼): drives the value cell's + // Key-column fold toggle (⏵/⏷): drives the value cell's // details from the key cell, keeping the summary free of // per-fold collapse chrome. Glyph state is derived in the // toggle listener below, so any other opener stays in sync. @@ -47206,7 +47185,7 @@ const keyBtn = row && row.querySelector( ':scope > .tool-param-key > .tool-param-key-toggle'); if (keyBtn) { - // CSS rotates the single ▸ glyph on aria-expanded — + // CSS rotates the single ⏵ glyph on aria-expanded — // symmetric open/closed states by construction. keyBtn.setAttribute('aria-expanded', details.open ? 'true' : 'false'); @@ -47543,18 +47522,18 @@ const allSection = foldBar ? foldBar.querySelector('.fold-all-levels') : null; if (state === 'folded') { if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } else if (state === 'first') { if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'folded')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // 'open' if (cc) cc.style.display = ''; getImmediateChildMessages(messageEl).forEach(child => applyFoldState(child, 'open')); - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, false, '▼▼'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, false, '⏷⏷'); } } @@ -47600,15 +47579,15 @@ const cc = getChildrenContainer(msg); if ((isSession || isUser) && !hasOnlyTools) { - // First level visible (▼), deeper levels folded (▶▶) + // First level visible (⏷), deeper levels folded (⏵⏵) if (cc) cc.style.display = ''; - setSectionState(oneSection, false, '▼'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, false, '⏷'); + setSectionState(allSection, true, '⏵⏵'); } else { // Fully folded if (cc) cc.style.display = 'none'; - setSectionState(oneSection, true, '▶'); - setSectionState(allSection, true, '▶▶'); + setSectionState(oneSection, true, '⏵'); + setSectionState(allSection, true, '⏵⏵'); } }); } @@ -47635,8 +47614,8 @@ const card = wrapper ? wrapper.querySelector(':scope > .message') : null; const foldBar = card ? card.querySelector(':scope > .fold-bar') : null; if (foldBar) { - setSectionState(foldBar.querySelector('.fold-one-level'), false, '▼'); - setSectionState(foldBar.querySelector('.fold-all-levels'), false, '▼▼'); + setSectionState(foldBar.querySelector('.fold-one-level'), false, '⏷'); + setSectionState(foldBar.querySelector('.fold-all-levels'), false, '⏷⏷'); } } node = node.parentElement; diff --git a/test/test_hook_attachment_rendering.py b/test/test_hook_attachment_rendering.py index d9c17697..9a5aaed2 100644 --- a/test/test_hook_attachment_rendering.py +++ b/test/test_hook_attachment_rendering.py @@ -461,7 +461,7 @@ def test_hook_does_not_pair_with_chained_system_entry(self) -> None: ``stop_hook_summary`` whose ``parentUuid`` is a hook attachment used to pair with the hook via ``_try_pair_by_index`` because both share ``type == "system"``. That made every hook render - with a spurious "▼ 1 system" fold-bar in transcripts where + with a spurious "⏷ 1 system" fold-bar in transcripts where plugins fire on every turn (issue #128 follow-up). """ from claude_code_log.renderer import generate_template_messages diff --git a/test/test_params_rows_toggle_browser.py b/test/test_params_rows_toggle_browser.py index 47beb2bd..8c26b1fa 100644 --- a/test/test_params_rows_toggle_browser.py +++ b/test/test_params_rows_toggle_browser.py @@ -1,11 +1,11 @@ """Playwright tests for the params-table fold controls. -An open structured-value fold shows "▶ expand all rows" in a controls +An open structured-value fold shows "⏵ expand all rows" in a controls strip after the summary (never inside it — interactive elements within are an accessibility violation); pressing it opens every -row-level fold of that table and turns into "▼ collapse all rows"; +row-level fold of that table and turns into "⏷ collapse all rows"; closing the outer fold restores the initial state. Fold-valued rows -carry their ▶/▼ toggle in the KEY column, derived from the actual open +carry their ⏵/⏷ toggle in the KEY column, derived from the actual open state. """ @@ -148,7 +148,7 @@ def test_closing_fold_restores_initial_state(self, page: Page) -> None: assert all(outer.evaluate(ROW_DETAILS_JS)) # Close the outer fold via its key-column toggle (the open summary - # is hidden for keyed rows — the ▼ in the key cell is the collapse + # is hidden for keyed rows — the ⏷ in the key cell is the collapse # control), then reopen the same way. key_toggle = page.locator( "tr.tool-param-row-fold > td.tool-param-key > .tool-param-key-toggle"