@ryubyte/dsh-a2a 0.1.5 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,295 @@
1
+ /**
2
+ * dsh-a2a — dashboard stylesheet.
3
+ *
4
+ * The browser half is a compiled bundle (not a dynamic cordis plugin), so it
5
+ * injects its own stylesheet the same way first-party DSH client UI packages
6
+ * do: a single idempotent <style> tag, styled entirely with the product's
7
+ * `--dsw-alias-*` design tokens (so light/dark themes and future token
8
+ * changes apply automatically), plus `@media` queries for narrow windows.
9
+ *
10
+ * Class names are prefixed with `a2a-` to avoid collisions with product or
11
+ * third-party styles.
12
+ */
13
+
14
+ export const DASHBOARD_CSS_ID = '@ryubyte/dsh-a2a/dashboard.css';
15
+
16
+ const css = String.raw`
17
+ /* ── layout ─────────────────────────────────────────────── */
18
+ /*
19
+ * The settings drawer is a fixed-ish narrow column (~560px), unrelated to the
20
+ * browser viewport, so responsive rules key off the container width, not the
21
+ * viewport. .a2a-root is our own wrapper (the host's immediate parent is
22
+ * display:contents and can't be a query container), scoped as an inline-size
23
+ * container so the @container rules below react to the real panel width.
24
+ */
25
+ .a2a-root { container-type: inline-size; container-name: a2a; }
26
+ .a2a-section { display: flex; flex-direction: column; gap: 20px; transition: gap .2s ease; }
27
+ .a2a-stack { display: flex; flex-direction: column; gap: 16px; }
28
+ .a2a-crew { display: flex; flex-direction: column; gap: 10px; }
29
+ .a2a-card-list { display: flex; flex-direction: column; gap: 8px; }
30
+ .a2a-btn-row { display: flex; gap: 8px; margin-top: 2px; }
31
+ .a2a-intro { color: var(--dsw-alias-label-secondary, #61666b); font-size: 13px; line-height: 1.6; margin: 0; }
32
+ .a2a-h3 { margin: 0; font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary, #0f1115); display: flex; align-items: center; gap: 8px; }
33
+ .a2a-count { font-size: 12px; font-weight: 400; color: var(--dsw-alias-label-tertiary, #81858c); background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.1)); border-radius: 10px; padding: 1px 8px; }
34
+ .a2a-empty { color: var(--dsw-alias-label-tertiary, #81858c); font-size: 13px; background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.06)); border-radius: 8px; padding: 12px 14px; }
35
+ .a2a-hint { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, #81858c); line-height: 1.6; }
36
+ .a2a-mono { font-family: var(--ds-font-family-code, "SF Mono", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, Courier, "PingFang SC", "Microsoft YaHei"); }
37
+
38
+ /* status / error / notice */
39
+ .a2a-notice { padding: 8px 12px; border-radius: 8px; font-size: 13px; background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.08)); color: var(--dsw-alias-label-primary, #0f1115); }
40
+ .a2a-error { color: var(--dsw-alias-state-error-primary, #d92d20); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d92d20) 9%, transparent); }
41
+
42
+ /* state dot */
43
+ .a2a-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #81858c); flex: none; }
44
+ .a2a-dot[data-state='connected'] { background: var(--dsw-alias-state-success-primary, #12b76a); }
45
+ .a2a-dot[data-state='reconnecting'] { background: var(--dsw-alias-state-warn-primary, #f79009); }
46
+ .a2a-dot[data-state='disabled'] { background: var(--dsw-alias-state-warn-primary, #f79009); }
47
+
48
+ /* ---- buttons ---- */
49
+ .a2a-btn {
50
+ display: inline-flex; align-items: center; justify-content: center; gap: 4px;
51
+ margin-right: 6px; padding: 4px 12px; font-size: 12px; line-height: 18px;
52
+ border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.3));
53
+ background: transparent; color: var(--dsw-alias-label-primary, #0f1115);
54
+ cursor: pointer; transition: background .15s, border-color .15s, color .15s, opacity .15s;
55
+ }
56
+ .a2a-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.08)); border-color: var(--dsw-alias-border-l3, rgba(127,127,127,.45)); }
57
+ .a2a-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #4a7dff); outline-offset: 1px; }
58
+ .a2a-btn:disabled { opacity: .5; cursor: default; }
59
+ .a2a-btn:last-child { margin-right: 0; }
60
+ .a2a-btn-primary { background: var(--dsw-alias-button-primary-fill, var(--dsw-alias-brand-primary, #4a7dff)); border-color: transparent; color: #fff; }
61
+ .a2a-btn-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover, var(--dsw-alias-brand-primary, #4a7dff)); border-color: transparent; }
62
+
63
+ /* ---- copy button ---- */
64
+ .a2a-copy-btn {
65
+ flex: none; margin-left: 8px; padding: 2px 8px; font-size: 11px; line-height: 16px;
66
+ border-radius: 6px; border: 1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.3));
67
+ background: transparent; color: var(--dsw-alias-label-tertiary, #81858c);
68
+ cursor: pointer; white-space: nowrap;
69
+ transition: color .15s, border-color .15s, background .15s;
70
+ }
71
+ .a2a-copy-btn:hover:not(:disabled) { color: var(--dsw-alias-label-primary, #0f1115); border-color: var(--dsw-alias-border-l3, rgba(127,127,127,.45)); background: var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.08)); }
72
+ .a2a-copy-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #4a7dff); outline-offset: 1px; }
73
+ .a2a-copy-btn[data-copied='true'] { color: var(--dsw-alias-state-success-primary, #12b76a); border-color: var(--dsw-alias-state-success-primary, #12b76a); background: color-mix(in srgb, var(--dsw-alias-state-success-primary, #12b76a) 10%, transparent); }
74
+
75
+ /* ---- meta rows (label + value + copy) ---- */
76
+ .a2a-meta-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
77
+ .a2a-meta-key { flex: none; font-size: 12px; color: var(--dsw-alias-label-secondary, #61666b); }
78
+ .a2a-meta-row .a2a-mono { flex: 1; min-width: 0; word-break: break-all; }
79
+ .a2a-meta-row .a2a-copy-btn { margin-left: auto; flex: none; }
80
+
81
+ /* ---- tabs (underline, matching first-party settings pages) ---- */
82
+ .a2a-tabs { display: flex; align-items: flex-end; gap: 22px; border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.1)); margin-top: 2px; transition: gap .2s ease; }
83
+ .a2a-tab {
84
+ position: relative; padding: 7px 1px 9px; font-size: 13px; line-height: 20px; font-weight: 400;
85
+ color: var(--dsw-alias-label-tertiary, #81858c); background: transparent; border: none;
86
+ cursor: pointer; transition: color .15s;
87
+ }
88
+ .a2a-tab:hover { color: var(--dsw-alias-label-primary, #0f1115); }
89
+ .a2a-tab:focus-visible { outline: none; color: var(--dsw-alias-label-primary, #0f1115); }
90
+ .a2a-tab[data-active='true'] { color: var(--dsw-alias-label-primary, #0f1115); }
91
+ .a2a-tab[data-active='true']::after { content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; border-radius: 1px; background: var(--dsw-alias-label-primary, #0f1115); }
92
+ .a2a-tab-count { margin-left: 6px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #81858c); }
93
+
94
+ /* ---- inputs ---- */
95
+ .a2a-field-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
96
+ .a2a-field-row .a2a-input { flex: 1; min-width: 260px; }
97
+ .a2a-input {
98
+ padding: 5px 10px; font-size: 13px; line-height: 20px; border-radius: 8px; min-width: 0;
99
+ border: 1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.3));
100
+ background: var(--dsw-alias-bg-layer-1, rgba(127,127,127,.06)); color: var(--dsw-alias-label-primary, #0f1115);
101
+ transition: border-color .15s, box-shadow .15s, background .15s;
102
+ }
103
+ .a2a-input::placeholder { color: var(--dsw-alias-label-tertiary, #81858c); }
104
+ .a2a-input:hover { border-color: var(--dsw-alias-border-l3, rgba(127,127,127,.45)); }
105
+ .a2a-input:focus-visible { outline: none; border-color: var(--dsw-alias-brand-primary, #4a7dff); box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary, #4a7dff) 20%, transparent); }
106
+
107
+ /* ---- edit forms (serve identity + per-agent advanced) ---- */
108
+ .a2a-form-row { display: flex; align-items: center; gap: 10px; }
109
+ .a2a-form-row .a2a-input { flex: 1; min-width: 0; }
110
+ .a2a-form-label { flex: none; font-size: 12.5px; font-weight: 600; color: var(--dsw-alias-label-secondary, #61666b); min-width: 76px; }
111
+ .a2a-check-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--dsw-alias-label-primary, #0f1115); cursor: pointer; }
112
+ .a2a-check-row input { flex: none; }
113
+ .a2a-adv { display: flex; flex-direction: column; gap: 10px; margin-top: 2px; padding: 12px; border-radius: 8px; background: var(--dsw-alias-bg-layer-1, rgba(127,127,127,.06)); border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12)); }
114
+
115
+ /* skills editor */
116
+ .a2a-skill-edit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 2px; }
117
+ .a2a-skill-edit-list { display: flex; flex-direction: column; gap: 10px; }
118
+ .a2a-skill-edit-row { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; border-radius: 8px; background: var(--dsw-alias-bg-layer-1, rgba(127,127,127,.06)); border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12)); }
119
+ .a2a-skill-edit-grid { display: flex; gap: 6px; }
120
+ .a2a-skill-edit-grid .a2a-input { flex: 1; min-width: 0; }
121
+ .a2a-skill-edit-foot { display: flex; align-items: center; gap: 6px; }
122
+ .a2a-skill-edit-foot .a2a-input { flex: 1; min-width: 0; }
123
+
124
+ /* ---- panels & cards ---- */
125
+ .a2a-panel {
126
+ display: flex; flex-direction: column; gap: 10px;
127
+ background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.05));
128
+ border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12));
129
+ border-radius: 12px; padding: 14px 16px;
130
+ }
131
+ .a2a-panel-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #0f1115); }
132
+
133
+ .a2a-card {
134
+ display: flex; flex-direction: column; gap: 8px;
135
+ background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.05));
136
+ border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12));
137
+ border-radius: 12px; padding: 12px 14px;
138
+ transition: border-color .15s, background .15s;
139
+ }
140
+ .a2a-card:hover { border-color: var(--dsw-alias-border-l2, rgba(127,127,127,.3)); }
141
+ .a2a-card-disabled { opacity: .72; }
142
+ .a2a-card-header { display: flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; flex-wrap: wrap; transition: gap .2s ease; }
143
+ .a2a-card-chevron { font-size: 11px; color: var(--dsw-alias-label-tertiary, #81858c); width: 14px; display: inline-block; transition: transform .15s; flex: none; }
144
+ .a2a-card-chevron[data-open='true'] { transform: rotate(90deg); }
145
+ .a2a-card-title { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary, #0f1115); }
146
+ .a2a-card-sub { font-size: 12px; color: var(--dsw-alias-label-tertiary, #81858c); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
147
+ .a2a-card-url { font-size: 12px; color: var(--dsw-alias-label-tertiary, #81858c); word-break: break-all; }
148
+ .a2a-card-ops { display: flex; align-items: center; gap: 4px; flex-shrink: 0; margin-left: auto; padding-left: 10px; transition: padding-left .2s ease, margin-left .2s ease; }
149
+
150
+ .a2a-skills { display: flex; flex-direction: column; gap: 6px; background: var(--dsw-alias-bg-layer-1, rgba(0,0,0,.04)); border-radius: 8px; padding: 10px 12px; margin-top: 2px; }
151
+ .a2a-skill-row { display: flex; flex-direction: column; gap: 2px; }
152
+ .a2a-skill-name { display: flex; align-items: baseline; gap: 6px; font-size: 12.5px; font-weight: 600; font-family: var(--ds-font-family-code, "SF Mono", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, Courier, "PingFang SC", "Microsoft YaHei"); color: var(--dsw-alias-label-primary, #0f1115); }
153
+ .a2a-skill-tags { font-size: 11px; color: var(--dsw-alias-label-tertiary, #81858c); }
154
+ .a2a-skill-desc { font-size: 12px; color: var(--dsw-alias-label-secondary, #61666b); }
155
+ .a2a-skill-none { font-size: 12px; color: var(--dsw-alias-label-tertiary, #81858c); }
156
+
157
+ /* ---- badges ---- */
158
+ .a2a-badge { display: inline-block; padding: 1px 6px; border-radius: 4px; font-size: 11.5px; line-height: 16px; }
159
+ .a2a-badge-config { color: var(--dsw-alias-label-secondary, #61666b); background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.12)); }
160
+ .a2a-badge-runtime { color: var(--dsw-alias-brand-primary, #4a7dff); background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4a7dff) 10%, transparent); }
161
+ .a2a-badge-disabled { color: var(--dsw-alias-state-warn-primary, #b26a00); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #b26a00) 10%, transparent); }
162
+
163
+ /* ---- add-agent preview ---- */
164
+ .a2a-preview { display: flex; flex-direction: column; gap: 8px; background: var(--dsw-alias-bg-layer-1, rgba(127,127,127,.06)); border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12)); border-radius: 8px; padding: 12px 14px; }
165
+ .a2a-preview-head { display: flex; align-items: center; gap: 8px; }
166
+ .a2a-preview-name { font-size: 15px; font-weight: 700; color: var(--dsw-alias-label-primary, #0f1115); }
167
+ .a2a-preview-version { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, #81858c); }
168
+ .a2a-preview-desc { font-size: 12.5px; color: var(--dsw-alias-label-secondary, #61666b); }
169
+ .a2a-preview-meta { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, #81858c); }
170
+
171
+ /* ---- serve tab ---- */
172
+ .a2a-serve-head { display: flex; align-items: center; gap: 8px; }
173
+ .a2a-serve-status { font-size: 12.5px; }
174
+ .a2a-serve-status[data-on='true'] { color: var(--dsw-alias-state-success-primary, #12b76a); }
175
+ .a2a-serve-status[data-on='false'] { color: var(--dsw-alias-label-tertiary, #81858c); }
176
+ .a2a-serve-ops { margin-left: auto; }
177
+ .a2a-serve-meta { display: flex; flex-direction: column; gap: 6px; }
178
+ .a2a-serve-name { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #0f1115); }
179
+ .a2a-serve-version { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, #81858c); margin-left: 8px; }
180
+ .a2a-serve-desc { font-size: 12.5px; color: var(--dsw-alias-label-secondary, #61666b); }
181
+ .a2a-serve-endpoint { font-size: 12px; color: var(--dsw-alias-label-tertiary, #81858c); }
182
+ .a2a-skill-list { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; color: var(--dsw-alias-label-primary, #0f1115); }
183
+ .a2a-skill-list-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-secondary, #61666b); }
184
+
185
+ .a2a-note { font-size: 12px; color: var(--dsw-alias-label-secondary, #61666b); background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4a7dff) 8%, transparent); border-radius: 6px; padding: 6px 10px; }
186
+ .a2a-note-warn { color: var(--dsw-alias-state-warn-primary, #b26a00); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #b26a00) 8%, transparent); }
187
+
188
+ .a2a-auth-row { display: flex; align-items: center; gap: 8px; }
189
+ .a2a-auth { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
190
+ .a2a-auth-label { font-size: 12.5px; font-weight: 600; color: var(--dsw-alias-label-primary, #0f1115); }
191
+ .a2a-auth-state { font-size: 12px; }
192
+ .a2a-auth-state[data-on='true'] { color: var(--dsw-alias-state-success-primary, #12b76a); }
193
+ .a2a-auth-state[data-on='false'] { color: var(--dsw-alias-label-tertiary, #81858c); }
194
+ .a2a-auth-input-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
195
+ .a2a-auth-input-row .a2a-input { flex: 1; min-width: 220px; }
196
+
197
+ /* ---- inbound table ---- */
198
+ .a2a-table-wrap { overflow-x: auto; border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12)); border-radius: 10px; }
199
+ .a2a-table { border-collapse: collapse; width: 100%; font-size: 13px; }
200
+ .a2a-table th { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.18)); color: var(--dsw-alias-label-secondary, #61666b); font-weight: 600; white-space: nowrap; }
201
+ .a2a-table td { padding: 8px 10px; border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.1)); color: var(--dsw-alias-label-primary, #0f1115); vertical-align: top; font-size: 12.5px; }
202
+ .a2a-table tr:last-child td { border-bottom: none; }
203
+ .a2a-table tbody tr:hover td { background: var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.06)); }
204
+ .a2a-cell-mono { font-family: var(--ds-font-family-code, "SF Mono", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, Courier, "PingFang SC", "Microsoft YaHei"); font-size: 12px; }
205
+
206
+ /* ---- footer ---- */
207
+ .a2a-footer { color: var(--dsw-alias-label-tertiary, #81858c); font-size: 12px; }
208
+
209
+ /* ── responsive: keyed off the panel's own width via @container ──────────
210
+ * The settings drawer width is independent of the viewport, so these use
211
+ * container queries against .a2a-root (container-name: a2a). Transitions on
212
+ * the affected properties keep the reflow smooth as the panel is resized.
213
+ */
214
+ /* The settings drawer sits around ~560px, so this (max-width:760px) band is
215
+ * effectively the *default* layout here — keep it to genuinely helpful reflow
216
+ * (tighter gaps, card header/ops that would otherwise overflow). Rules that
217
+ * force inputs/values onto their own line belong in the much-narrower band
218
+ * below, or they needlessly break "input + button" / "value + copy" onto two
219
+ * lines at a width that comfortably fits them on one. */
220
+ @container a2a (max-width: 760px) {
221
+ .a2a-section { gap: 16px; }
222
+ /* Header stacks: title row on top, meta + ops flow below, left-aligned —
223
+ avoids the "orphan buttons pinned to the far right" look when wrapping. */
224
+ .a2a-card-header { flex-wrap: wrap; }
225
+ .a2a-card-sub { white-space: normal; }
226
+ .a2a-card-ops { margin-left: 0; width: 100%; justify-content: flex-start; padding-left: 0; }
227
+ .a2a-serve-ops { margin-left: auto; }
228
+ }
229
+ /* Only when the panel is genuinely narrow (< 440px) do inputs/values take a
230
+ * full line — at the normal ~560px width they stay inline with their button. */
231
+ @container a2a (max-width: 440px) {
232
+ .a2a-field-row .a2a-input { min-width: 100%; }
233
+ .a2a-auth-input-row .a2a-input { min-width: 100%; }
234
+ .a2a-meta-row { flex-wrap: wrap; }
235
+ .a2a-meta-row .a2a-mono { flex-basis: 100%; }
236
+ .a2a-meta-row .a2a-copy-btn { margin-left: auto; }
237
+ .a2a-form-row { flex-wrap: wrap; gap: 4px; }
238
+ .a2a-form-row .a2a-input { min-width: 100%; }
239
+ .a2a-skill-edit-grid { flex-direction: column; }
240
+ }
241
+ /* inbound table → stacked cards below 640px (instead of horizontal squeeze) */
242
+ @container a2a (max-width: 640px) {
243
+ .a2a-table-wrap { border: none; border-radius: 0; background: transparent; overflow-x: visible; }
244
+ .a2a-table, .a2a-table thead, .a2a-table tbody, .a2a-table tr, .a2a-table td { display: block; width: auto; }
245
+ .a2a-table thead { display: none; }
246
+ .a2a-table tbody { display: flex; flex-direction: column; gap: 8px; }
247
+ .a2a-table tr { border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12)); border-radius: 10px; padding: 8px 12px; margin-bottom: 0; }
248
+ .a2a-table tr:hover td { background: transparent; }
249
+ .a2a-table td { border: none; padding: 3px 0; display: flex; justify-content: space-between; align-items: center; gap: 12px; font-size: 12.5px; }
250
+ .a2a-table td::before { content: attr(data-label); flex: none; color: var(--dsw-alias-label-secondary, #61666b); font-weight: 600; font-size: 12px; }
251
+ .a2a-table td:last-child { justify-content: flex-end; }
252
+ }
253
+ @container a2a (max-width: 560px) {
254
+ .a2a-tabs { gap: 14px; }
255
+ .a2a-card-header { gap: 8px; }
256
+ }
257
+
258
+ /* Graceful fallback: if @container isn't supported, key off the viewport so
259
+ the narrow layout still appears in a genuinely small window. */
260
+ @supports not (container-type: inline-size) {
261
+ @media (max-width: 760px) {
262
+ .a2a-section { gap: 16px; }
263
+ .a2a-card-header { flex-wrap: wrap; }
264
+ .a2a-card-ops { margin-left: 0; width: 100%; justify-content: flex-start; padding-left: 0; }
265
+ .a2a-meta-row { flex-wrap: wrap; }
266
+ .a2a-meta-row .a2a-mono { flex-basis: 100%; }
267
+ }
268
+ }
269
+ `;
270
+
271
+ let injected = false;
272
+
273
+ /**
274
+ * Idempotently inject the dashboard stylesheet into <head>. Returns a
275
+ * disposer that removes the tag; safe to call repeatedly (a second call is a
276
+ * no-op and returns a no-op disposer).
277
+ */
278
+ export function injectDashboardStyles(): () => void {
279
+ if (typeof document === 'undefined') return () => {};
280
+ if (injected) return () => {};
281
+ injected = true;
282
+ const existing = document.querySelector(
283
+ `style[data-plugin-css=${JSON.stringify(DASHBOARD_CSS_ID)}]`,
284
+ );
285
+ if (existing) return () => {};
286
+ const tag = document.createElement('style');
287
+ tag.dataset.plugin = '@ryubyte/dsh-a2a';
288
+ tag.dataset.pluginCss = DASHBOARD_CSS_ID;
289
+ tag.textContent = css;
290
+ document.head.appendChild(tag);
291
+ return () => {
292
+ tag.remove();
293
+ injected = false;
294
+ };
295
+ }