mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-19 04:52:06 +00:00
feat(kanban-dashboard): native <details> collapse + skip empty metadata
Two follow-up improvements to Tranquil-Flow's metadata-panel restyle.
Both stay within the parent PR's "tone down the panel" scope.
1. Native <details>/<summary> collapse for verbose metadata.
The parent PR consciously deferred this ("adding native expand/collapse
would be the next step but requires UX agreement"). The default they
asked for is straightforward: collapsed when the rendered JSON exceeds
300 chars (the threshold where the max-height: 8.5rem cap actually
starts mattering), expanded otherwise. <details>/<summary> is the right
primitive — zero JS, browser-handled state, accessible by default
(keyboard-navigable, screen-reader announces the disclosure state),
and survives any react-state churn for free.
The OS-default disclosure marker is suppressed (list-style: none +
::-webkit-details-marker hidden) and replaced with a CSS ::before
chevron that rotates 90deg on the [open] attribute, so the look is
consistent across Firefox/WebKit/Blink without the double-marker
that would otherwise appear on the platforms that still render the
default triangle.
2. Skip rendering when metadata is an empty object.
`r.metadata && ...` truthy-checks, but `{}` is truthy in JS — so a
completed task with no actual metadata would render a "Metadata"
labeled disclosure block containing literal `{}`. Adds an
Object.keys(r.metadata).length > 0 guard so empty payloads render
nothing instead of an empty disclosure stub.
Tests: three new static-asset assertions covering the <details> shape,
the empty-object skip, and the suppress-default-marker + animated-chevron
CSS — all in `tests/plugins/test_kanban_dashboard_plugin.py`.
This commit is contained in:
parent
0e0ddaac8f
commit
a91e5a8759
3 changed files with 77 additions and 6 deletions
18
plugins/kanban/dashboard/dist/index.js
vendored
18
plugins/kanban/dashboard/dist/index.js
vendored
|
|
@ -2397,12 +2397,18 @@
|
||||||
r.error
|
r.error
|
||||||
? h("div", { className: "hermes-kanban-run-error" }, r.error)
|
? h("div", { className: "hermes-kanban-run-error" }, r.error)
|
||||||
: null,
|
: null,
|
||||||
r.metadata
|
(r.metadata && Object.keys(r.metadata).length > 0)
|
||||||
? h("div", { className: "hermes-kanban-run-meta-block" },
|
? (function () {
|
||||||
h("div", { className: "hermes-kanban-run-meta-label" }, "Metadata"),
|
var json = JSON.stringify(r.metadata, null, 2);
|
||||||
h("code", { className: "hermes-kanban-run-meta" },
|
var collapsed = json.length > 300;
|
||||||
JSON.stringify(r.metadata, null, 2)),
|
return h("details", {
|
||||||
)
|
className: "hermes-kanban-run-meta-block",
|
||||||
|
open: !collapsed,
|
||||||
|
},
|
||||||
|
h("summary", { className: "hermes-kanban-run-meta-label" }, "Metadata"),
|
||||||
|
h("code", { className: "hermes-kanban-run-meta" }, json),
|
||||||
|
);
|
||||||
|
})()
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
19
plugins/kanban/dashboard/dist/style.css
vendored
19
plugins/kanban/dashboard/dist/style.css
vendored
|
|
@ -867,6 +867,8 @@
|
||||||
* sub-block with a thin left rule, capped height, and muted treatment so
|
* sub-block with a thin left rule, capped height, and muted treatment so
|
||||||
* a verbose JSON blob (e.g. changed_files + URLs from a writer task) does
|
* a verbose JSON blob (e.g. changed_files + URLs from a writer task) does
|
||||||
* not visually swamp the parent run row or get mistaken for a crash dump.
|
* not visually swamp the parent run row or get mistaken for a crash dump.
|
||||||
|
* Uses a native <details>/<summary> pair so collapse is browser-handled
|
||||||
|
* (zero JS); large blobs default collapsed via the open=false attribute.
|
||||||
* See issue #19548. */
|
* See issue #19548. */
|
||||||
.hermes-kanban-run-meta-block {
|
.hermes-kanban-run-meta-block {
|
||||||
margin-top: 0.4rem;
|
margin-top: 0.4rem;
|
||||||
|
|
@ -874,6 +876,23 @@
|
||||||
border-left: 2px solid var(--color-border);
|
border-left: 2px solid var(--color-border);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
.hermes-kanban-run-meta-block > summary.hermes-kanban-run-meta-label {
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.hermes-kanban-run-meta-block > summary.hermes-kanban-run-meta-label::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.hermes-kanban-run-meta-block > summary.hermes-kanban-run-meta-label::before {
|
||||||
|
content: "▶ ";
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
transition: transform 120ms ease;
|
||||||
|
}
|
||||||
|
.hermes-kanban-run-meta-block[open] > summary.hermes-kanban-run-meta-label::before {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
.hermes-kanban-run-meta-label {
|
.hermes-kanban-run-meta-label {
|
||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
|
||||||
|
|
@ -1833,3 +1833,49 @@ def test_run_metadata_secondary_styling():
|
||||||
assert "max-height" in meta_decl
|
assert "max-height" in meta_decl
|
||||||
assert "overflow: auto" in meta_decl
|
assert "overflow: auto" in meta_decl
|
||||||
assert "color: var(--color-muted-foreground)" in meta_decl
|
assert "color: var(--color-muted-foreground)" in meta_decl
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_metadata_uses_native_collapse():
|
||||||
|
"""Metadata panel uses <details>/<summary> for zero-JS collapse.
|
||||||
|
|
||||||
|
Native <details> means the browser handles state — no event handlers,
|
||||||
|
no React-state coupling, accessible by default (keyboard navigable,
|
||||||
|
screen-reader announces the disclosure state). Default-open state is
|
||||||
|
decided per-render based on payload length.
|
||||||
|
"""
|
||||||
|
js = _dashboard_dist_path("index.js").read_text(encoding="utf-8")
|
||||||
|
# Element must be <details> / <summary>, not plain <div>s.
|
||||||
|
assert 'h("details"' in js
|
||||||
|
assert 'h("summary"' in js
|
||||||
|
# The open prop is computed from json length (collapsed when verbose).
|
||||||
|
assert "open: !collapsed" in js or "open:!collapsed" in js
|
||||||
|
assert "json.length > 300" in js
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_metadata_skips_empty_object():
|
||||||
|
"""Empty `{}` metadata renders nothing — no useless labeled block.
|
||||||
|
|
||||||
|
`r.metadata && {} && ...` would render a "Metadata" labeled block
|
||||||
|
containing just `{}`, which is visual noise. The render predicate now
|
||||||
|
also checks Object.keys(r.metadata).length > 0.
|
||||||
|
"""
|
||||||
|
js = _dashboard_dist_path("index.js").read_text(encoding="utf-8")
|
||||||
|
assert "Object.keys(r.metadata).length > 0" in js
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_metadata_disclosure_indicator_styled():
|
||||||
|
"""Native disclosure marker is hidden + replaced with a CSS-only chevron.
|
||||||
|
|
||||||
|
Browsers render an OS-specific arrow next to <summary> by default. For a
|
||||||
|
consistent look across OSes the hermes dashboard hides that marker and
|
||||||
|
renders a CSS ::before chevron that rotates on [open]. Pin it so a
|
||||||
|
future CSS rebuild can't silently lose it (which would put two markers
|
||||||
|
side-by-side on Firefox/WebKit).
|
||||||
|
"""
|
||||||
|
css = _dashboard_dist_path("style.css").read_text(encoding="utf-8")
|
||||||
|
# Default markers suppressed.
|
||||||
|
assert "list-style: none" in css
|
||||||
|
assert "::-webkit-details-marker" in css
|
||||||
|
# CSS-only chevron present + animates on open state.
|
||||||
|
assert ".hermes-kanban-run-meta-block[open]" in css
|
||||||
|
assert "rotate(90deg)" in css
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue