jekyll-theme-zer0 1.29.0 → 1.30.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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +715 -2
  3. data/_data/README.md +2 -0
  4. data/_data/ai.yml +5 -3
  5. data/_data/ai_pricing.yml +36 -0
  6. data/_data/backlog.yml +169 -18
  7. data/_data/consumers.yml +48 -5
  8. data/_data/features.yml +146 -20
  9. data/_data/feedback_types.yml +17 -12
  10. data/_data/landing.yml +5 -2
  11. data/_data/navigation/quickstart.yml +4 -0
  12. data/_data/site_builder.yml +874 -0
  13. data/_data/theme-manifest.yml +122 -114
  14. data/_data/ui-text.yml +18 -0
  15. data/_includes/README.md +11 -1
  16. data/_includes/analytics/posthog.html +2 -2
  17. data/_includes/components/admin-links.html +2 -2
  18. data/_includes/components/admin-tabs.html +2 -2
  19. data/_includes/components/ai-chat.html +14 -11
  20. data/_includes/components/analytics-dashboard.html +8 -8
  21. data/_includes/components/author-bio.html +1 -1
  22. data/_includes/components/author-card.html +10 -2
  23. data/_includes/components/author-eeat.html +4 -4
  24. data/_includes/components/background-customizer.html +8 -8
  25. data/_includes/components/background-image.html +114 -0
  26. data/_includes/components/background-settings.html +28 -15
  27. data/_includes/components/collection-manager.html +5 -5
  28. data/_includes/components/component-showcase.html +13 -13
  29. data/_includes/components/config-editor.html +12 -12
  30. data/_includes/components/config-viewer.html +8 -8
  31. data/_includes/components/cookie-consent.html +11 -11
  32. data/_includes/components/cta-button.html +7 -2
  33. data/_includes/components/dev-shortcuts.html +7 -7
  34. data/_includes/components/env-dashboard.html +8 -8
  35. data/_includes/components/env-switcher.html +9 -9
  36. data/_includes/components/feature-card.html +2 -2
  37. data/_includes/components/halfmoon.html +2 -2
  38. data/_includes/components/info-section.html +36 -36
  39. data/_includes/components/js-cdn.html +15 -15
  40. data/_includes/components/language-toggle.html +4 -4
  41. data/_includes/components/mermaid.html +72 -435
  42. data/_includes/components/nanobar.html +5 -5
  43. data/_includes/components/nav-editor.html +2 -2
  44. data/_includes/components/nav-export.html +2 -2
  45. data/_includes/components/nav-overview.html +2 -2
  46. data/_includes/components/page-feedback.html +45 -30
  47. data/_includes/components/page-views-init.html +1 -1
  48. data/_includes/components/post-card.html +22 -22
  49. data/_includes/components/post-type-badge.html +2 -2
  50. data/_includes/components/powered-by.html +2 -2
  51. data/_includes/components/preview-image.html +6 -0
  52. data/_includes/components/quick-index.html +2 -2
  53. data/_includes/components/search-modal.html +27 -2
  54. data/_includes/components/searchbar.html +2 -2
  55. data/_includes/components/svg-background.html +2 -2
  56. data/_includes/components/theme-customizer.html +2 -2
  57. data/_includes/components/theme-info.html +6 -6
  58. data/_includes/components/theme-preview-gallery.html +22 -22
  59. data/_includes/content/giscus.html +2 -2
  60. data/_includes/content/intro.html +8 -8
  61. data/_includes/content/jsonld-faq.html +2 -2
  62. data/_includes/content/jsonld-software.html +2 -2
  63. data/_includes/content/seo.html +4 -4
  64. data/_includes/content/sitemap.html +27 -27
  65. data/_includes/content/toc.html +183 -183
  66. data/_includes/core/branding.html +6 -6
  67. data/_includes/core/console-capture.html +32 -74
  68. data/_includes/core/favicon.html +49 -7
  69. data/_includes/core/footer-fabs.html +17 -3
  70. data/_includes/core/footer.html +31 -18
  71. data/_includes/core/head.html +102 -90
  72. data/_includes/core/header.html +71 -52
  73. data/_includes/docs/bootstrap-docs.html +8 -8
  74. data/_includes/landing/landing-install-cards.html +2 -2
  75. data/_includes/landing/landing-quick-links.html +1 -1
  76. data/_includes/navigation/admin-nav.html +2 -2
  77. data/_includes/navigation/nav-tree.html +8 -8
  78. data/_includes/navigation/navbar.html +12 -12
  79. data/_includes/navigation/section-sidebar.html +16 -16
  80. data/_includes/navigation/sidebar-config.html +36 -2
  81. data/_includes/navigation/sidebar-left.html +15 -15
  82. data/_includes/navigation/sidebar-right.html +6 -6
  83. data/_includes/obsidian/full-graph.html +2 -2
  84. data/_includes/setup/claude-session.html +72 -0
  85. data/_includes/setup/prereq-checklist.html +90 -0
  86. data/_includes/setup/wizard.html +906 -291
  87. data/_includes/stats/stats-categories.html +8 -8
  88. data/_includes/stats/stats-header.html +14 -14
  89. data/_includes/stats/stats-metrics.html +12 -12
  90. data/_includes/stats/stats-no-data.html +12 -12
  91. data/_includes/stats/stats-overview.html +6 -6
  92. data/_includes/stats/stats-tags.html +8 -8
  93. data/_layouts/404.html +38 -24
  94. data/_layouts/admin.html +22 -22
  95. data/_layouts/article.html +39 -34
  96. data/_layouts/author.html +20 -20
  97. data/_layouts/authors.html +2 -2
  98. data/_layouts/book-abc.html +12 -12
  99. data/_layouts/book-story.html +15 -15
  100. data/_layouts/book.html +12 -12
  101. data/_layouts/collection.html +33 -33
  102. data/_layouts/cookbook.html +12 -12
  103. data/_layouts/default.html +27 -24
  104. data/_layouts/home.html +23 -23
  105. data/_layouts/index.html +10 -10
  106. data/_layouts/landing.html +17 -17
  107. data/_layouts/news.html +44 -44
  108. data/_layouts/note.html +38 -38
  109. data/_layouts/notebook.html +34 -34
  110. data/_layouts/recipe.html +24 -24
  111. data/_layouts/root.html +73 -55
  112. data/_layouts/section.html +23 -23
  113. data/_layouts/setup.html +3 -3
  114. data/_layouts/sitemap-collection.html +49 -49
  115. data/_layouts/stats.html +40 -40
  116. data/_layouts/tag.html +12 -12
  117. data/_layouts/welcome.html +21 -21
  118. data/_sass/components/_mermaid.scss +375 -0
  119. data/_sass/components/_setup-wizard.scss +569 -40
  120. data/_sass/core/_navbar.scss +11 -31
  121. data/_sass/layouts/_navbar-extras.scss +14 -4
  122. data/assets/css/main.scss +1 -0
  123. data/assets/js/ai-chat.js +47 -5
  124. data/assets/js/fleet-feedback-capture.js +124 -0
  125. data/assets/js/fleet-feedback.js +853 -0
  126. data/assets/js/mermaid-diagrams.js +1267 -0
  127. data/assets/js/modules/navigation/config.js +9 -6
  128. data/assets/js/modules/navigation/scroll-spy.js +315 -80
  129. data/assets/js/modules/theme/appearance.js +8 -2
  130. data/assets/js/obsidian-wiki-links.js +8 -3
  131. data/assets/js/page-feedback.js +125 -192
  132. data/assets/js/search-modal.js +26 -0
  133. data/assets/js/setup-wizard.js +2112 -361
  134. data/assets/js/site-builder.js +1834 -0
  135. data/assets/js/ui-enhancements.js +11 -3
  136. data/scripts/README.md +15 -0
  137. data/scripts/ai/README.md +38 -0
  138. data/scripts/ai/api_call.rb +124 -0
  139. data/scripts/ai/usage.rb +314 -0
  140. data/scripts/ai/usage_report.rb +225 -0
  141. data/scripts/ci/test_visual_evidence_autogen.py +341 -0
  142. data/scripts/ci/visual_evidence_autogen.py +1060 -0
  143. data/scripts/content-review.rb +20 -1
  144. data/scripts/test/integration/mermaid +22 -8
  145. data/scripts/test/lib/run_tests.sh +1 -0
  146. data/scripts/test/lib/test_visual_evidence_autogen.sh +24 -0
  147. data/scripts/translate.rb +23 -1
  148. metadata +19 -2
@@ -137,21 +137,16 @@
137
137
  overflow: visible;
138
138
  }
139
139
 
140
+ // No transform on :hover. A translate moves the element out from under a
141
+ // pointer parked on the edge it vacates, hover exits, the element returns —
142
+ // an infinite flicker. Nav items are thin enough for a 1px lift to do it.
140
143
  #bdNavbar .nav-link:hover {
141
144
  background-color: var(--bs-tertiary-bg);
142
- transform: translateY(-1px);
143
145
  }
144
146
 
145
147
  .nav-hover-dropdown .dropdown-toggle-split {
146
148
  padding: var(--zer0-space-1) var(--zer0-space-2);
147
149
 
148
- &::after {
149
- transition: transform 0.2s ease-in-out;
150
- }
151
-
152
- &:hover::after {
153
- transform: translateY(2px);
154
- }
155
150
  }
156
151
  }
157
152
 
@@ -181,12 +176,18 @@
181
176
  &:hover {
182
177
  color: var(--bs-primary);
183
178
  background-color: var(--bs-tertiary-bg);
184
- transform: translateY(-1px);
185
179
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
180
+ // These are `.btn`s, and _ui-enhancements.scss lifts every button by 2px
181
+ // on hover. Dropping the navbar's own translateY(-1px) is not enough —
182
+ // the inherited lift flickers the same way. `transform: none` is the
183
+ // repo's existing corrective pattern (see _post-navigation.scss).
184
+ transform: none;
186
185
  }
187
186
 
187
+ // A pressed cue is safe: :active only holds while the button is held down,
188
+ // so the pointer cannot leave and re-enter in a loop.
188
189
  &:active {
189
- transform: translateY(0);
190
+ transform: translateY(1px);
190
191
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
191
192
  }
192
193
 
@@ -197,11 +198,6 @@
197
198
 
198
199
  i {
199
200
  font-size: 1.15rem;
200
- transition: transform 0.15s ease-in-out;
201
- }
202
-
203
- &:hover i {
204
- transform: scale(1.1);
205
201
  }
206
202
  }
207
203
  }
@@ -446,16 +442,11 @@
446
442
  #bdNavbar .nav-link i {
447
443
  margin-right: 0;
448
444
  font-size: 1rem;
449
- transition: transform 0.15s ease-in-out;
450
445
  }
451
446
 
452
447
  #bdNavbar .nav-link:hover {
453
448
  background-color: var(--bs-tertiary-bg);
454
449
  border-radius: 0.375rem;
455
-
456
- i {
457
- transform: scale(1.1);
458
- }
459
450
  }
460
451
 
461
452
  .nav-hover-dropdown > .nav-link {
@@ -681,7 +672,6 @@
681
672
 
682
673
  &:hover {
683
674
  background-color: var(--bs-tertiary-bg);
684
- transform: rotate(90deg);
685
675
  }
686
676
 
687
677
  &:focus-visible {
@@ -768,20 +758,10 @@
768
758
  i {
769
759
  font-size: 1rem;
770
760
  width: 1.5rem;
771
- transition: transform 0.15s ease-in-out;
772
761
  }
773
762
 
774
763
  &:hover {
775
764
  background-color: var(--bs-tertiary-bg);
776
-
777
- // translateX widens overflow; desktop menus sit inside a clipped nav track
778
- @media (max-width: 991.98px) {
779
- transform: translateX(4px);
780
- }
781
-
782
- i {
783
- transform: scale(1.1);
784
- }
785
765
  }
786
766
 
787
767
  &:focus-visible {
@@ -53,13 +53,18 @@
53
53
  display: block;
54
54
  }
55
55
 
56
+ // No transform on :hover — a lift moves the FAB out from under a pointer
57
+ // parked on its bottom edge, hover exits, it drops back: infinite flicker.
58
+ // These are `.btn`s, so the 2px lift _ui-enhancements.scss puts on every
59
+ // button has to be cancelled explicitly, not merely not-added here.
56
60
  &:hover,
57
61
  &:focus-visible {
58
- transform: translateY(-1px);
62
+ transform: none;
59
63
  }
60
64
 
65
+ // :active is safe, because the button is held down for its whole duration.
61
66
  &:active {
62
- transform: translateY(0);
67
+ transform: translateY(1px);
63
68
  }
64
69
  }
65
70
  }
@@ -112,13 +117,18 @@
112
117
  display: block;
113
118
  }
114
119
 
120
+ // No transform on :hover — a lift moves the FAB out from under a pointer
121
+ // parked on its bottom edge, hover exits, it drops back: infinite flicker.
122
+ // These are `.btn`s, so the 2px lift _ui-enhancements.scss puts on every
123
+ // button has to be cancelled explicitly, not merely not-added here.
115
124
  &:hover,
116
125
  &:focus-visible {
117
- transform: translateY(-1px);
126
+ transform: none;
118
127
  }
119
128
 
129
+ // :active is safe, because the button is held down for its whole duration.
120
130
  &:active {
121
- transform: translateY(0);
131
+ transform: translateY(1px);
122
132
  }
123
133
  }
124
134
  }
data/assets/css/main.scss CHANGED
@@ -44,6 +44,7 @@
44
44
  @import "components/book";
45
45
  @import "components/recipe";
46
46
  @import "components/setup-wizard";
47
+ @import "components/mermaid";
47
48
 
48
49
  // 6. Layout partials
49
50
  @import "layouts/landing";
data/assets/js/ai-chat.js CHANGED
@@ -28,8 +28,10 @@
28
28
  * the tool_result back, and continues the loop.
29
29
  *
30
30
  * Security:
31
- * - Proxy mode (recommended) keeps the Anthropic key and GitHub token
32
- * server-side; the browser only ever talks to the same-origin proxy.
31
+ * - Proxy mode (recommended) keeps the provider key and GitHub token
32
+ * server-side; the browser only ever talks to the same-origin proxy,
33
+ * which answers with Claude or Grok (the reply is always Anthropic
34
+ * Messages SSE — the proxy translates for xAI).
33
35
  * - Direct mode sends x-api-key from the page and requires the
34
36
  * anthropic-dangerous-direct-browser-access header — local dev only.
35
37
  * - GitHub "url" mode never touches a token: it opens pre-filled
@@ -586,14 +588,53 @@
586
588
 
587
589
  // URL mode: open a pre-filled github.com form — the user submits it
588
590
  // with their own account, so no token is ever needed in the browser.
589
- var params = new URLSearchParams();
590
- params.set('title', String(inputData.title).slice(0, 256));
591
+ //
592
+ // The URL is composed by the shared fleet builder (FleetFeedbackCore, in
593
+ // assets/js/fleet-feedback.js) when the page-feedback widget is enabled,
594
+ // which is what removed the THIRD hand-rolled issue-URL builder in this
595
+ // theme. Two things come with it: the same `<!-- fleet-feedback v1 -->`
596
+ // marker the issue pipeline reads, and a real over-budget path — this used
597
+ // to `.slice(0, 6000)` the body, silently dropping whatever Claude had
598
+ // written past that point, with nobody the wiser.
591
599
  var body = String(inputData.body);
592
600
  if (meta.page_url && body.indexOf(meta.page_url) === -1) {
593
601
  body += '\n\n---\nPage: ' + meta.page_url;
594
602
  }
595
- params.set('body', body.slice(0, 6000));
596
603
  var labels = mergedLabels(inputData.labels);
604
+ var title = String(inputData.title).slice(0, 256);
605
+ var FF = window.FleetFeedbackCore;
606
+
607
+ if (FF) {
608
+ var issue = {
609
+ title: title,
610
+ // The chat writes prose, not a capture, so it fills the description
611
+ // slot whole; only the footer is contributed by the contract.
612
+ sections: {
613
+ description: body,
614
+ footer: '---\n_Filed from ' + (meta.page_url || location.href) +
615
+ ' via the AI chat assistant (fleet-feedback v' + FF.VERSION + ')._\n' +
616
+ '<!-- fleet-feedback v1 type=chat -->'
617
+ },
618
+ labels: labels,
619
+ assignees: []
620
+ };
621
+ var built = FF.buildUrl(issue, { repo: CONFIG.github.repository });
622
+ var win = window.open(built.url, '_blank', 'noopener');
623
+ if (!win) {
624
+ return toolResult(block.id, 'The browser blocked the pop-up, so no issue form opened. Ask the user to allow pop-ups for this site and try again.', true);
625
+ }
626
+ if (built.overBudget && navigator.clipboard && navigator.clipboard.writeText) {
627
+ navigator.clipboard.writeText(built.fullBody).catch(function () {});
628
+ return toolResult(block.id, 'A pre-filled GitHub issue form was opened in a new browser tab. The report was too long for the URL, so the full text was copied to the user clipboard — tell them to paste it over the pre-filled body before submitting.');
629
+ }
630
+ return toolResult(block.id, 'A pre-filled GitHub issue form was opened in a new browser tab. The user reviews and submits it there (a GitHub account is required).');
631
+ }
632
+
633
+ // No page-feedback widget on this site, so the shared builder is not
634
+ // loaded. Compose the URL directly; the body is capped rather than trimmed.
635
+ var params = new URLSearchParams();
636
+ params.set('title', title);
637
+ params.set('body', body.slice(0, 6000));
597
638
  if (labels.length) params.set('labels', labels.join(','));
598
639
  window.open('https://github.com/' + CONFIG.github.repository + '/issues/new?' + params.toString(), '_blank', 'noopener');
599
640
  return toolResult(block.id, 'A pre-filled GitHub issue form was opened in a new browser tab. The user reviews and submits it there (a GitHub account is required).');
@@ -735,6 +776,7 @@
735
776
  var accumulated = '';
736
777
  var payload = {
737
778
  model: CONFIG.model,
779
+ provider: CONFIG.provider || 'anthropic', // honoured by the proxy only when that provider has a key
738
780
  max_tokens: CONFIG.maxTokens,
739
781
  system: buildSystemPrompt(meta),
740
782
  messages: history.slice(),
@@ -0,0 +1,124 @@
1
+ /*!
2
+ * fleet-feedback capture v0.2.0 — the early console/error ring buffer.
3
+ * Source: bamr87/bamr87 templates/feedback/ (spec: specs/FEEDBACK.md, UPS-FB-04).
4
+ *
5
+ * Load this FIRST — before any other script, as high in <head> as you can get
6
+ * it — so the errors that happen during page load are in the buffer by the time
7
+ * a reader opens the feedback dialog. fleet-feedback.js installs the same
8
+ * buffer if this file is absent, but by then the interesting failures have
9
+ * already happened silently.
10
+ *
11
+ * <script src="/assets/js/fleet-feedback-capture.js"></script> <!-- Jekyll/Django -->
12
+ * <Script src="/fleet-feedback-capture.js" strategy="beforeInteractive" /> <!-- Next.js -->
13
+ *
14
+ * Small enough to inline verbatim into a <script> tag when a extra request
15
+ * costs more than the bytes (the Jekyll adapter does exactly that).
16
+ *
17
+ * Contract — window.__fleetFeedback:
18
+ * logs [{ t: ISO-8601, level: 'warn'|'error'|'net', msg: string }]
19
+ * limit ring size (default 40)
20
+ * installed true once the hooks are in place; a second load is a no-op
21
+ * version kit version that installed it
22
+ * breadcrumb(level, message) push a structured entry (fetch wrappers use this)
23
+ * snapshot() a copy of the buffer, newest last
24
+ * clear() empty it
25
+ *
26
+ * Privacy: in-memory only. Never persisted, never transmitted, never read until
27
+ * the reader opens the dialog, previews the lines, and submits. Every entry is
28
+ * redacted (bearer tokens, API keys, JWTs, email addresses) and truncated.
29
+ */
30
+ /* fleet-feedback:capture:begin — byte-identical in fleet-feedback.js (tests/contract.test.mjs asserts it) */
31
+ (function (root) {
32
+ 'use strict';
33
+
34
+ var VERSION = '0.2.0';
35
+ var LIMIT = 40;
36
+ var MAX_ENTRY = 600;
37
+
38
+ var g = (root.__fleetFeedback = root.__fleetFeedback || {
39
+ logs: [],
40
+ limit: LIMIT,
41
+ installed: false,
42
+ version: VERSION
43
+ });
44
+ if (g.installed) { return; }
45
+
46
+ // Secrets end up in console output far more often than anyone expects — an
47
+ // Authorization header logged by a fetch wrapper, a signed URL in a 403, a
48
+ // user's address in a validation message. Redact BEFORE the line enters the
49
+ // buffer, so it can never be previewed, copied, or filed.
50
+ var SCRUB = [
51
+ // The credential can carry a scheme word of its own ("Authorization: Bearer
52
+ // <token>"), and a rule that stops at the scheme leaves the secret in the
53
+ // buffer — which is exactly what the first version of this did.
54
+ [/\b(authorization|proxy-authorization|bearer|token|api[-_]?key|access[-_]?token|refresh[-_]?token|client[-_]?secret|secret|password|passwd)(\s*[:=]\s*|\s+)((?:bearer|basic|digest|token)\s+)?([^\s,;"'&]{6,})/gi, '$1$2$3***'],
55
+ [/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]+/g, '***jwt***'],
56
+ [/\bgh[pousr]_[A-Za-z0-9]{16,}/g, '***github-token***'],
57
+ [/\bsk-[A-Za-z0-9_-]{16,}/g, '***api-key***'],
58
+ [/\b[\w.+-]+@[\w-]+\.[\w.-]{2,}\b/g, '***@***']
59
+ ];
60
+
61
+ function redact(text) {
62
+ var out = String(text == null ? '' : text);
63
+ for (var i = 0; i < SCRUB.length; i++) {
64
+ out = out.replace(SCRUB[i][0], SCRUB[i][1]);
65
+ }
66
+ return out;
67
+ }
68
+
69
+ function stringify(value) {
70
+ if (typeof value === 'string') { return value; }
71
+ if (value instanceof Error) {
72
+ return (value.name || 'Error') + ': ' + (value.message || '') +
73
+ (value.stack ? '\n' + String(value.stack).split('\n').slice(1, 4).join('\n') : '');
74
+ }
75
+ try { return JSON.stringify(value); } catch (e) { return String(value); }
76
+ }
77
+
78
+ function push(level, parts) {
79
+ try {
80
+ var line = redact(parts.map(stringify).join(' '));
81
+ if (line.length > MAX_ENTRY) { line = line.slice(0, MAX_ENTRY) + '…'; }
82
+ g.logs.push({ t: new Date().toISOString(), level: level, msg: line });
83
+ while (g.logs.length > g.limit) { g.logs.shift(); }
84
+ } catch (e) { /* capture must never break the page it is watching */ }
85
+ }
86
+
87
+ // Only warn/error are hooked. console.log is noise at this budget — 40 lines
88
+ // of debug chatter push the actual failure out of a ring this size.
89
+ if (root.console) {
90
+ ['warn', 'error'].forEach(function (level) {
91
+ var original = root.console[level];
92
+ root.console[level] = function () {
93
+ push(level, Array.prototype.slice.call(arguments));
94
+ if (original) { return original.apply(root.console, arguments); }
95
+ };
96
+ });
97
+ }
98
+
99
+ if (root.addEventListener) {
100
+ root.addEventListener('error', function (e) {
101
+ if (!e) { return; }
102
+ var where = e.filename ? ' (' + e.filename + ':' + (e.lineno || 0) + ':' + (e.colno || 0) + ')' : '';
103
+ push('error', [(e.message || 'Uncaught error') + where]);
104
+ });
105
+ root.addEventListener('unhandledrejection', function (e) {
106
+ var reason = e && e.reason;
107
+ push('error', ['Unhandled promise rejection:', reason && reason.message ? reason.message : reason]);
108
+ });
109
+ }
110
+
111
+ g.version = VERSION;
112
+ g.installed = true;
113
+ g.redact = redact;
114
+ /**
115
+ * Push a structured entry. An app's fetch wrapper calls this on a failed
116
+ * request so the report carries the API call that broke, not just the
117
+ * exception it surfaced as:
118
+ * FF.breadcrumb('net', 'GET /api/analysis → 502 (1243ms) req=abc123')
119
+ */
120
+ g.breadcrumb = function (level, message) { push(level || 'net', [message]); };
121
+ g.snapshot = function () { return g.logs.slice(); };
122
+ g.clear = function () { g.logs.length = 0; };
123
+ })(typeof window !== 'undefined' ? window : globalThis);
124
+ /* fleet-feedback:capture:end */