@narumitw/pi-inspect 0.0.0-stage → 0.1.0

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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +107 -2
  3. package/dist/app.css +1 -0
  4. package/dist/app.js +129 -0
  5. package/dist/index.html +1 -0
  6. package/docs/REVIEW.md +189 -0
  7. package/docs/VERIFICATION.md +186 -0
  8. package/docs/viewer.md +63 -0
  9. package/package.json +67 -4
  10. package/src/ancestry.ts +32 -0
  11. package/src/collector.ts +143 -0
  12. package/src/context.ts +115 -0
  13. package/src/correlation.ts +27 -0
  14. package/src/entry-index.ts +17 -0
  15. package/src/extension.ts +259 -0
  16. package/src/feed.ts +145 -0
  17. package/src/identity.ts +112 -0
  18. package/src/index.ts +1 -0
  19. package/src/model.ts +113 -0
  20. package/src/native-projection.ts +28 -0
  21. package/src/privacy.ts +136 -0
  22. package/src/projection.ts +361 -0
  23. package/src/server.ts +202 -0
  24. package/src/system-message.ts +117 -0
  25. package/src/web/api.ts +137 -0
  26. package/src/web/app.tsx +439 -0
  27. package/src/web/components.css +261 -0
  28. package/src/web/components.tsx +289 -0
  29. package/src/web/composition.css +554 -0
  30. package/src/web/context-composition.tsx +324 -0
  31. package/src/web/entry-calls.ts +14 -0
  32. package/src/web/format.ts +92 -0
  33. package/src/web/hierarchy.ts +117 -0
  34. package/src/web/inline-entry.tsx +124 -0
  35. package/src/web/inspector.tsx +337 -0
  36. package/src/web/json-tree.tsx +74 -0
  37. package/src/web/live-drawer.tsx +175 -0
  38. package/src/web/overview.tsx +67 -0
  39. package/src/web/panels.css +267 -0
  40. package/src/web/panes.tsx +123 -0
  41. package/src/web/search.ts +16 -0
  42. package/src/web/style.css +332 -0
  43. package/src/web/styles.d.ts +1 -0
  44. package/src/web/timeline.tsx +33 -0
  45. package/src/web/timing.ts +26 -0
  46. package/src/web/trace.css +410 -0
  47. package/src/web/trace.tsx +273 -0
  48. package/src/web/tree.tsx +281 -0
  49. package/src/web/virtual-context.ts +138 -0
@@ -0,0 +1,267 @@
1
+ .sidebar {
2
+ display: flex;
3
+ flex-direction: column;
4
+ height: 100%;
5
+ min-height: 0;
6
+ gap: 5px;
7
+ }
8
+ .tree-panel {
9
+ display: flex;
10
+ flex-direction: column;
11
+ flex: 1;
12
+ min-height: 0;
13
+ }
14
+ .tree-controls {
15
+ padding: 0 9px 8px;
16
+ }
17
+ .tree-controls .rt-TextFieldRoot {
18
+ background: var(--panel-raised);
19
+ box-shadow: none;
20
+ border: 1px solid var(--panel-border);
21
+ font-size: 11px;
22
+ }
23
+ .filter-pills {
24
+ display: flex;
25
+ gap: 4px;
26
+ margin-top: 7px;
27
+ }
28
+ .filter-pills button {
29
+ flex: 1;
30
+ padding: 4px;
31
+ border: 0;
32
+ border-radius: 4px;
33
+ background: transparent;
34
+ color: var(--secondary);
35
+ font-size: 11px;
36
+ }
37
+ .filter-pills button[aria-pressed="true"] {
38
+ background: var(--selected-bg);
39
+ color: var(--blue-11);
40
+ }
41
+ .tree-scroll {
42
+ flex: 1;
43
+ min-height: 0;
44
+ overflow: auto;
45
+ padding: 2px 5px;
46
+ }
47
+ .tree-row {
48
+ display: flex;
49
+ align-items: center;
50
+ min-width: 0;
51
+ gap: 2px;
52
+ }
53
+ .tree-row .rt-IconButton {
54
+ width: 18px;
55
+ height: 22px;
56
+ flex-shrink: 0;
57
+ color: var(--muted);
58
+ }
59
+ .tree-spacer {
60
+ width: 18px;
61
+ flex-shrink: 0;
62
+ }
63
+ .node {
64
+ flex: 1;
65
+ min-width: 0;
66
+ display: flex;
67
+ gap: 7px;
68
+ align-items: center;
69
+ padding: 5px 4px;
70
+ border: 0;
71
+ border-radius: 4px;
72
+ background: transparent;
73
+ color: var(--gray-12);
74
+ text-align: left;
75
+ }
76
+ .node:hover {
77
+ background: var(--surface-hover);
78
+ }
79
+ .node[aria-current="true"] {
80
+ background: var(--selected-bg);
81
+ box-shadow: inset 2px 0 var(--blue-9);
82
+ }
83
+ .node-copy {
84
+ min-width: 0;
85
+ flex: 1;
86
+ display: block;
87
+ }
88
+ .node-title {
89
+ display: flex;
90
+ align-items: baseline;
91
+ gap: 6px;
92
+ min-width: 0;
93
+ white-space: nowrap;
94
+ }
95
+ .node-title strong {
96
+ font-size: 11px;
97
+ font-weight: 600;
98
+ overflow: hidden;
99
+ text-overflow: ellipsis;
100
+ }
101
+ .node-title small {
102
+ font:
103
+ 10px / 1.4 Consolas,
104
+ monospace;
105
+ color: var(--muted);
106
+ flex-shrink: 0;
107
+ }
108
+ .node-description {
109
+ display: block;
110
+ white-space: nowrap;
111
+ overflow: hidden;
112
+ text-overflow: ellipsis;
113
+ color: var(--secondary);
114
+ font-size: 10px;
115
+ line-height: 15px;
116
+ }
117
+ .tree-count,
118
+ .nav-pagination {
119
+ border-top: 1px solid var(--panel-border);
120
+ padding: 6px;
121
+ color: var(--muted);
122
+ text-align: center;
123
+ font-size: 10px;
124
+ }
125
+ .nav-pagination {
126
+ display: flex;
127
+ justify-content: space-between;
128
+ gap: 4px;
129
+ }
130
+ .nav-pagination button {
131
+ background: none;
132
+ border: 0;
133
+ color: var(--secondary);
134
+ font-size: 10px;
135
+ }
136
+ .filter-panel {
137
+ flex-shrink: 0;
138
+ padding: 0 9px;
139
+ }
140
+ .filters-toggle {
141
+ display: flex;
142
+ align-items: center;
143
+ justify-content: space-between;
144
+ width: 100%;
145
+ gap: 6px;
146
+ background: none;
147
+ border: 0;
148
+ color: var(--secondary);
149
+ padding: 8px 0;
150
+ font-size: 12px;
151
+ }
152
+ .filters-toggle svg {
153
+ width: 14px;
154
+ }
155
+ .filter-check {
156
+ display: flex;
157
+ justify-content: space-between;
158
+ align-items: center;
159
+ margin: 8px 0;
160
+ font-size: 11px;
161
+ color: var(--secondary);
162
+ cursor: pointer;
163
+ }
164
+ .filter-panel .rt-Flex {
165
+ padding-bottom: 9px;
166
+ }
167
+ .filter-panel .rt-SelectTrigger {
168
+ max-width: 135px;
169
+ font-size: 11px;
170
+ }
171
+ .inspector-panel {
172
+ display: flex;
173
+ flex-direction: column;
174
+ height: 100%;
175
+ min-height: 0;
176
+ }
177
+ .inspector-identity {
178
+ padding: 2px 10px 8px;
179
+ }
180
+ .inspector-identity .rt-Heading {
181
+ font-size: 14px;
182
+ line-height: 20px;
183
+ overflow-wrap: anywhere;
184
+ }
185
+ .inspector-identity .rt-Text {
186
+ font-size: 11px;
187
+ line-height: 16px;
188
+ }
189
+ .inspector-tabs {
190
+ flex: 1;
191
+ min-height: 0;
192
+ overflow-y: auto;
193
+ overflow-x: hidden;
194
+ padding: 0 8px 8px;
195
+ }
196
+ .inspector-tabs > .rt-TabsList {
197
+ display: flex;
198
+ flex-wrap: nowrap;
199
+ overflow-x: auto;
200
+ position: sticky;
201
+ top: 0;
202
+ z-index: 2;
203
+ background: var(--panel-bg);
204
+ margin-bottom: 6px;
205
+ flex-shrink: 0;
206
+ }
207
+ .inspector-tabs .rt-TabsTrigger {
208
+ padding: 0 7px;
209
+ height: 31px;
210
+ font-size: 11px;
211
+ white-space: nowrap;
212
+ flex-shrink: 0;
213
+ text-transform: capitalize;
214
+ }
215
+ .inspector-tabs .rt-TabsTriggerInner,
216
+ .inspector-tabs .rt-TabsTriggerInnerHidden {
217
+ padding: 0;
218
+ }
219
+ .inspector-tabs .rt-TabsContent {
220
+ padding-top: 0;
221
+ }
222
+ .format-toolbar {
223
+ display: flex;
224
+ justify-content: end;
225
+ padding: 3px 0 6px;
226
+ }
227
+ .inspector-metadata {
228
+ padding: 0 3px 5px;
229
+ }
230
+ .inspector-metadata .metadata > div {
231
+ border-bottom: 1px solid var(--panel-border);
232
+ }
233
+ .related {
234
+ border-top: 1px solid var(--panel-border);
235
+ padding: 5px 0;
236
+ margin-top: 8px;
237
+ }
238
+ .related > .rt-Button {
239
+ width: 100%;
240
+ justify-content: space-between;
241
+ }
242
+ .related button:not(.rt-Button) {
243
+ display: block;
244
+ padding: 7px 4px;
245
+ width: 100%;
246
+ text-align: left;
247
+ background: none;
248
+ border: 0;
249
+ color: var(--secondary);
250
+ font-size: 11px;
251
+ }
252
+ .inventory-card {
253
+ background: transparent;
254
+ border: 0;
255
+ border-bottom: 1px solid var(--panel-border);
256
+ border-radius: 0;
257
+ box-shadow: none;
258
+ margin: 6px 0;
259
+ padding: 9px 2px;
260
+ }
261
+ .inventory-card .rt-Flex {
262
+ margin: 5px 0;
263
+ }
264
+ .inventory-card .rt-Text {
265
+ overflow-wrap: anywhere;
266
+ font-size: 11px;
267
+ }
@@ -0,0 +1,123 @@
1
+ import { Cross2Icon } from "@radix-ui/react-icons";
2
+ import { Dialog, IconButton } from "@radix-ui/themes";
3
+ import { type ReactNode, type RefObject, useEffect, useState } from "react";
4
+
5
+ export function useNarrow(): boolean {
6
+ const [width, setWidth] = useState(() => window.innerWidth);
7
+ useEffect(() => {
8
+ const changed = () => setWidth(window.innerWidth);
9
+ window.addEventListener("resize", changed);
10
+ return () => window.removeEventListener("resize", changed);
11
+ }, []);
12
+ return width <= 1100;
13
+ }
14
+ export function PaneDrawer({
15
+ name,
16
+ open,
17
+ change,
18
+ children,
19
+ side,
20
+ trigger,
21
+ }: {
22
+ name: string;
23
+ open: boolean;
24
+ change(open: boolean): void;
25
+ children: ReactNode;
26
+ side: string;
27
+ trigger: RefObject<HTMLButtonElement | null>;
28
+ }) {
29
+ return (
30
+ <Dialog.Root open={open} onOpenChange={change}>
31
+ <Dialog.Content
32
+ className={`side-drawer side-drawer-${side}`}
33
+ onCloseAutoFocus={(event) => {
34
+ event.preventDefault();
35
+ if (trigger.current?.isConnected) trigger.current.focus({ preventScroll: true });
36
+ }}
37
+ >
38
+ <div className="drawer-title">
39
+ <Dialog.Title>{name}</Dialog.Title>
40
+ <Dialog.Close>
41
+ <IconButton aria-label={`Close ${name}`} variant="ghost">
42
+ <Cross2Icon />
43
+ </IconButton>
44
+ </Dialog.Close>
45
+ </div>
46
+ <Dialog.Description className="sr-only">Read-only browser panel. Escape closes this drawer.</Dialog.Description>
47
+ {children}
48
+ </Dialog.Content>
49
+ </Dialog.Root>
50
+ );
51
+ }
52
+ export function ResizeHandle({
53
+ name,
54
+ value,
55
+ change,
56
+ min,
57
+ max,
58
+ inverse = false,
59
+ }: {
60
+ name: string;
61
+ value: number;
62
+ change(value: number): void;
63
+ min: number;
64
+ max: number;
65
+ inverse?: boolean;
66
+ }) {
67
+ const [dragging, setDragging] = useState(false);
68
+ const [start, setStart] = useState({ x: 0, value: 0 });
69
+ useEffect(() => {
70
+ if (!dragging) return;
71
+ const controller = new AbortController();
72
+ const move = (event: PointerEvent) =>
73
+ change(Math.max(min, Math.min(max, start.value + (event.clientX - start.x) * (inverse ? -1 : 1))));
74
+ const end = () => setDragging(false);
75
+ window.addEventListener("pointermove", move, { signal: controller.signal });
76
+ window.addEventListener("pointerup", end, { signal: controller.signal });
77
+ window.addEventListener("pointercancel", end, { signal: controller.signal });
78
+ window.addEventListener("blur", end, { signal: controller.signal });
79
+ window.addEventListener(
80
+ "keydown",
81
+ (event) => {
82
+ if (event.key === "Escape") {
83
+ event.preventDefault();
84
+ end();
85
+ }
86
+ },
87
+ { signal: controller.signal },
88
+ );
89
+ return () => controller.abort();
90
+ }, [dragging, start, change, min, max, inverse]);
91
+ return (
92
+ <hr
93
+ aria-label={name}
94
+ aria-orientation="vertical"
95
+ aria-valuenow={Math.round(value)}
96
+ aria-valuemin={min}
97
+ aria-valuemax={max}
98
+ tabIndex={0}
99
+ className="pane-resizer"
100
+ onPointerDown={(event) => {
101
+ if (event.button !== 0) return;
102
+ event.preventDefault();
103
+ setStart({ x: event.clientX, value });
104
+ setDragging(true);
105
+ }}
106
+ onKeyDown={(event) => {
107
+ if (event.key === "Escape") {
108
+ setDragging(false);
109
+ return;
110
+ }
111
+ if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
112
+ event.preventDefault();
113
+ change(
114
+ event.key === "Home"
115
+ ? min
116
+ : event.key === "End"
117
+ ? max
118
+ : Math.max(min, Math.min(max, value + (event.key === "ArrowRight" ? 10 : -10) * (inverse ? -1 : 1))),
119
+ );
120
+ }}
121
+ />
122
+ );
123
+ }
@@ -0,0 +1,16 @@
1
+ // Display-only browser ingestion. Strip complete terminal sequences before bounding the query.
2
+ export function boundedSearch(value: string): string {
3
+ return (
4
+ value
5
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional terminal-control sanitation.
6
+ .replace(/(?:\x1b[\]^_P]|[\x90\x9d\x9e\x9f])[\s\S]*?(?:\x07|\x1b\\|\x9c|$)/g, "")
7
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional CSI and escape sanitation.
8
+ .replace(/(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]|\x1b[ -/]*[@-~]/g, "")
9
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Search contains no terminal controls.
10
+ .replace(/[\x00-\x1f\x7f-\x9f]/g, "")
11
+ .slice(0, 512)
12
+ );
13
+ }
14
+ export function searchNeedle(value: string): string {
15
+ return boundedSearch(value).toLowerCase();
16
+ }
@@ -0,0 +1,332 @@
1
+ @import "./components.css";
2
+ @import "./panels.css";
3
+ @import "./trace.css";
4
+
5
+ :root,
6
+ .radix-themes {
7
+ --app-bg: #0b1220;
8
+ --panel-bg: #101b2b;
9
+ --panel-raised: #152338;
10
+ --surface-hover: #1b2d45;
11
+ --panel-border: #27384d;
12
+ --secondary: #a3b3c8;
13
+ --muted: #71849d;
14
+ --code-bg: #0d1725;
15
+ --selected-bg: #4b91f71f;
16
+ }
17
+ .radix-themes.light {
18
+ --app-bg: #f3f5f9;
19
+ --panel-bg: #fff;
20
+ --panel-raised: #edf2f9;
21
+ --surface-hover: #e5edf8;
22
+ --panel-border: #ced7e5;
23
+ --secondary: #53637a;
24
+ --muted: #687992;
25
+ --code-bg: #f7f9fd;
26
+ --selected-bg: #4b91f71a;
27
+ }
28
+ * {
29
+ box-sizing: border-box;
30
+ }
31
+ body {
32
+ margin: 0;
33
+ background: var(--app-bg);
34
+ }
35
+ button,
36
+ input {
37
+ font: inherit;
38
+ }
39
+ button {
40
+ cursor: pointer;
41
+ }
42
+ button:disabled {
43
+ cursor: default;
44
+ }
45
+ .inspector-app {
46
+ height: 100dvh;
47
+ min-height: 0;
48
+ display: flex;
49
+ flex-direction: column;
50
+ overflow: hidden;
51
+ background: var(--app-bg);
52
+ color: var(--gray-12);
53
+ font:
54
+ 13px / 1.4 "Segoe UI",
55
+ -apple-system,
56
+ BlinkMacSystemFont,
57
+ sans-serif;
58
+ }
59
+ .app-header {
60
+ height: 48px;
61
+ flex-shrink: 0;
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 16px;
65
+ padding: 0 12px;
66
+ border-bottom: 1px solid var(--panel-border);
67
+ }
68
+ .brand {
69
+ display: flex;
70
+ gap: 10px;
71
+ align-items: center;
72
+ flex-shrink: 0;
73
+ }
74
+ .brand-logo {
75
+ width: 28px;
76
+ height: 28px;
77
+ display: grid;
78
+ place-items: center;
79
+ border-radius: 5px;
80
+ background: #2558a7;
81
+ color: white;
82
+ font:
83
+ bold 25px Georgia,
84
+ serif;
85
+ }
86
+ .brand .rt-Heading {
87
+ font-size: 17px;
88
+ }
89
+ .header-context {
90
+ flex: 1;
91
+ min-width: 0;
92
+ display: flex;
93
+ gap: 14px;
94
+ color: var(--secondary);
95
+ font-size: 11px;
96
+ }
97
+ .header-context span {
98
+ white-space: nowrap;
99
+ overflow: hidden;
100
+ text-overflow: ellipsis;
101
+ }
102
+ .header-context strong {
103
+ font:
104
+ 11px / 1.4 Consolas,
105
+ monospace;
106
+ color: var(--gray-12);
107
+ }
108
+ .header-actions {
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 9px;
112
+ margin-left: auto;
113
+ }
114
+ .header-actions .rt-IconButton {
115
+ width: 27px;
116
+ height: 27px;
117
+ color: var(--secondary);
118
+ }
119
+ .app-notices {
120
+ padding: 5px 8px;
121
+ flex-shrink: 0;
122
+ }
123
+ .app-notices .rt-CalloutRoot {
124
+ padding: 6px 10px;
125
+ font-size: 11px;
126
+ }
127
+ .app-grid {
128
+ display: grid;
129
+ flex: 1;
130
+ min-height: 0;
131
+ min-width: 0;
132
+ overflow: hidden;
133
+ padding: 8px;
134
+ gap: 3px;
135
+ }
136
+ .side-slot {
137
+ min-width: 0;
138
+ min-height: 0;
139
+ grid-row: 1;
140
+ height: 100%;
141
+ }
142
+ .nav-slot {
143
+ grid-column: 1;
144
+ }
145
+ .inspector-slot {
146
+ grid-column: 5;
147
+ }
148
+ .side-slot[hidden] {
149
+ display: none;
150
+ }
151
+ .pane-resizer {
152
+ margin: 0;
153
+ border: 0;
154
+ min-width: 0;
155
+ height: 100%;
156
+ cursor: col-resize;
157
+ background: transparent;
158
+ touch-action: none;
159
+ grid-row: 1;
160
+ }
161
+ .pane-resizer[aria-label="Resize navigator"] {
162
+ grid-column: 2;
163
+ }
164
+ .pane-resizer[aria-label="Resize inspector"] {
165
+ grid-column: 4;
166
+ }
167
+ .pane-resizer:hover,
168
+ .pane-resizer:focus-visible {
169
+ background: var(--blue-8);
170
+ }
171
+ .center-column {
172
+ grid-column: 3;
173
+ grid-row: 1;
174
+ display: flex;
175
+ flex-direction: column;
176
+ min-width: 0;
177
+ min-height: 0;
178
+ height: 100%;
179
+ gap: 4px;
180
+ }
181
+ .panel {
182
+ background: var(--panel-bg);
183
+ border: 1px solid var(--panel-border);
184
+ border-radius: 5px;
185
+ min-width: 0;
186
+ min-height: 0;
187
+ overflow: hidden;
188
+ }
189
+ .panel-heading {
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: space-between;
193
+ padding: 9px 10px;
194
+ gap: 6px;
195
+ }
196
+ .panel-heading .rt-Heading {
197
+ display: flex;
198
+ align-items: center;
199
+ gap: 7px;
200
+ font-size: 13px;
201
+ line-height: 18px;
202
+ }
203
+ .panel-heading svg {
204
+ width: 15px;
205
+ height: 15px;
206
+ color: var(--secondary);
207
+ }
208
+ .app-footer {
209
+ height: 24px;
210
+ flex-shrink: 0;
211
+ border-top: 1px solid var(--panel-border);
212
+ display: flex;
213
+ justify-content: space-between;
214
+ align-items: center;
215
+ gap: 10px;
216
+ padding: 0 12px;
217
+ font-size: 10px;
218
+ color: var(--muted);
219
+ }
220
+ button:focus-visible,
221
+ a:focus-visible,
222
+ [role="treeitem"]:focus-visible,
223
+ .pane-resizer:focus-visible,
224
+ summary:focus-visible {
225
+ outline: 2px solid var(--blue-9);
226
+ outline-offset: -2px;
227
+ }
228
+ .tree-scroll,
229
+ .trace-scroll,
230
+ .inspector-tabs,
231
+ .live-drawer-body {
232
+ scrollbar-width: thin;
233
+ scrollbar-color: var(--panel-border) transparent;
234
+ }
235
+ .side-drawer {
236
+ width: min(92vw, 440px);
237
+ max-width: 92vw;
238
+ height: calc(100dvh - 24px);
239
+ max-height: calc(100dvh - 24px);
240
+ margin: 12px;
241
+ padding: 10px;
242
+ display: flex;
243
+ flex-direction: column;
244
+ gap: 8px;
245
+ background: var(--panel-bg);
246
+ }
247
+ .side-drawer-left {
248
+ margin-right: auto;
249
+ }
250
+ .side-drawer-right {
251
+ margin-left: auto;
252
+ width: min(94vw, 540px);
253
+ }
254
+ .side-drawer .inspector-panel > .panel-heading {
255
+ display: none;
256
+ }
257
+ .side-drawer .sidebar,
258
+ .side-drawer .inspector-panel {
259
+ flex: 1;
260
+ min-height: 0;
261
+ }
262
+ .drawer-title {
263
+ display: flex;
264
+ justify-content: space-between;
265
+ align-items: center;
266
+ flex-shrink: 0;
267
+ }
268
+ .drawer-title .rt-DialogTitle {
269
+ margin: 0;
270
+ font-size: 16px;
271
+ }
272
+ .sr-only {
273
+ position: absolute;
274
+ width: 1px;
275
+ height: 1px;
276
+ margin: -1px;
277
+ padding: 0;
278
+ overflow: hidden;
279
+ clip: rect(0, 0, 0, 0);
280
+ white-space: nowrap;
281
+ border: 0;
282
+ }
283
+ .narrow-layout {
284
+ display: flex;
285
+ }
286
+ .narrow-layout .center-column {
287
+ flex: 1;
288
+ width: 100%;
289
+ }
290
+ @media (max-width: 1100px) {
291
+ .header-context {
292
+ display: none;
293
+ }
294
+ }
295
+ @media (max-width: 680px) {
296
+ .app-header {
297
+ gap: 6px;
298
+ padding: 0 8px;
299
+ }
300
+ .brand {
301
+ gap: 5px;
302
+ }
303
+ .brand .rt-Heading {
304
+ font-size: 14px;
305
+ }
306
+ .brand-logo {
307
+ width: 24px;
308
+ height: 24px;
309
+ font-size: 22px;
310
+ }
311
+ .header-actions {
312
+ gap: 4px;
313
+ }
314
+ .header-actions .rt-Badge:last-of-type {
315
+ display: none;
316
+ }
317
+ .app-grid {
318
+ padding: 5px;
319
+ }
320
+ .app-footer span:last-child {
321
+ display: none;
322
+ }
323
+ }
324
+ @media (prefers-reduced-motion: reduce) {
325
+ *,
326
+ *::before,
327
+ *::after {
328
+ animation: none;
329
+ transition: none;
330
+ scroll-behavior: auto;
331
+ }
332
+ }