kash-shell 0.3.20__py3-none-any.whl → 0.3.21__py3-none-any.whl

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.
@@ -1,8 +1,13 @@
1
1
  import re
2
2
  from collections.abc import Set
3
3
 
4
- HTML_IN_MD_TAGS = frozenset(["div", "span", "sup", "sub", "br", "details", "summary"])
5
- """These are tags that have reasonable usage in Markdown so typically would be preserved."""
4
+ HTML_IN_MD_TAGS = frozenset(
5
+ ["div", "span", "i", "b", "em", "sup", "sub", "br", "details", "summary"]
6
+ )
7
+ """
8
+ These are tags that have reasonable usage in Markdown so typically would be preserved.
9
+ Note we want `<i>` because it's used for icons like `<i data-feather="list"></i>`.
10
+ """
6
11
 
7
12
  ALLOWED_BARE_PROTOS = frozenset(["http://", "https://", "file://"])
8
13
 
@@ -68,7 +73,7 @@ def escape_html_tags(
68
73
  def test_escape_html_tags():
69
74
  """Tests the escape_html_tags function with various cases."""
70
75
 
71
- # 1. Basic Whitelist Check (Default)
76
+ # Basic Whitelist Check (Default)
72
77
  assert escape_html_tags("<div>Test</div>") == "<div>Test</div>"
73
78
  assert escape_html_tags("<span>Test</span>") == "<span>Test</span>"
74
79
  assert escape_html_tags("<br>") == "<br>"
@@ -77,21 +82,21 @@ def test_escape_html_tags():
77
82
  == "<details><summary>Sum</summary>Det</details>"
78
83
  )
79
84
 
80
- # 2. Basic Escape Check
85
+ # Basic Escape Check
81
86
  assert escape_html_tags("<p>Test</p>") == "&lt;p>Test&lt;/p>"
82
87
  assert escape_html_tags("<script>alert('x');</script>") == "&lt;script>alert('x');&lt;/script>"
83
88
  assert escape_html_tags("<img>") == "&lt;img>"
84
89
 
85
- # 3. Case Insensitivity
90
+ # Case Insensitivity
86
91
  assert escape_html_tags("<DiV>Case</DiV>") == "<DiV>Case</DiV>" # Whitelisted
87
92
  assert escape_html_tags("<P>Test</P>") == "&lt;P>Test&lt;/P>" # Escaped
88
93
 
89
- # 4. Self-closing tags
94
+ # Self-closing tags
90
95
  assert escape_html_tags("<br/>") == "<br/>" # Whitelisted
91
96
  assert escape_html_tags("<br />") == "<br />" # Whitelisted
92
97
  assert escape_html_tags("<img/>") == "&lt;img/>" # Escaped
93
98
 
94
- # 5. Tags with Attributes
99
+ # Tags with Attributes
95
100
  assert (
96
101
  escape_html_tags('<div class="foo">Test</div>') == '<div class="foo">Test</div>'
97
102
  ) # Whitelisted
@@ -102,7 +107,7 @@ def test_escape_html_tags():
102
107
  assert escape_html_tags('<p class="foo">Test</p>') == '&lt;p class="foo">Test&lt;/p>' # Escaped
103
108
  assert escape_html_tags('<img src="a.jpg"/>') == '&lt;img src="a.jpg"/>' # Escaped
104
109
 
105
- # 6. Markdown URL Handling
110
+ # Markdown URL Handling
106
111
  url_md = "Check <https://example.com> and <http://test.org/path>"
107
112
  assert escape_html_tags(url_md, allow_bare_md_urls=True) == url_md
108
113
  assert (
@@ -127,7 +132,7 @@ def test_escape_html_tags():
127
132
  == "&lt;/https://example.com>"
128
133
  ) # Closing URL-like is escaped
129
134
 
130
- # 7. Nested/Malformed '<' and Edge Cases
135
+ # Nested/Malformed '<' and Edge Cases
131
136
  assert escape_html_tags("<<script>>") == "&lt;&lt;script>>" # Escaped non-tag <
132
137
  assert escape_html_tags("<div><p>nested</p></div>") == "<div>&lt;p>nested&lt;/p></div>"
133
138
  assert escape_html_tags("<div<span") == "&lt;div&lt;span" # Incomplete tags are escaped
@@ -140,7 +145,7 @@ def test_escape_html_tags():
140
145
  assert escape_html_tags("< >") == "&lt; >"
141
146
  assert escape_html_tags("< / div >") == "< / div >" # Whitelisted closing tag with spaces
142
147
 
143
- # 8. Mixed Content Combination
148
+ # Mixed Content Combination
144
149
  complex_html = "<DiV class='A'>Hello <Br/> <p>World</p> <https://link.com> </DiV>"
145
150
  expected_complex_allowed = (
146
151
  "<DiV class='A'>Hello <Br/> &lt;p>World&lt;/p> <https://link.com> </DiV>"
@@ -151,6 +156,6 @@ def test_escape_html_tags():
151
156
  assert escape_html_tags(complex_html, allow_bare_md_urls=True) == expected_complex_allowed
152
157
  assert escape_html_tags(complex_html, allow_bare_md_urls=False) == expected_complex_disallowed
153
158
 
154
- # 9. Empty/No Tags
159
+ # Empty/No Tags
155
160
  assert escape_html_tags("") == ""
156
161
  assert escape_html_tags("Just plain text, no tags.") == "Just plain text, no tags."
@@ -33,6 +33,7 @@ MARKO_GFM = marko.Markdown(
33
33
 
34
34
 
35
35
  FOOTNOTE_UP_ARROW = "&nbsp;↑&nbsp;"
36
+ FOOTNOTE_DOWN_ARROW = "&nbsp;↓&nbsp;"
36
37
 
37
38
 
38
39
  def html_postprocess(html: str) -> str:
@@ -5,7 +5,7 @@
5
5
  --font-sans: "Source Sans 3 Variable", sans-serif, "Hack Nerd Font";
6
6
  --font-serif: "PT Serif", serif, "Hack Nerd Font";
7
7
  /* Source Sans 3 Variable better at these weights. */
8
- --font-weight-sans-medium: 565;
8
+ --font-weight-sans-medium: 550;
9
9
  --font-weight-sans-bold: 650;
10
10
  --font-mono: "Hack Nerd Font", "Menlo", "DejaVu Sans Mono", Consolas, "Lucida Console", monospace;
11
11
 
@@ -156,7 +156,13 @@ h4 {
156
156
  margin-bottom: 0.7rem;
157
157
  }
158
158
 
159
- h5, h6 {
159
+ h5 {
160
+ font-size: 1rem;
161
+ margin-top: 0.7rem;
162
+ margin-bottom: 0.5rem;
163
+ }
164
+
165
+ h6 {
160
166
  font-size: 1rem;
161
167
  margin-top: 0.7rem;
162
168
  margin-bottom: 0.5rem;
@@ -368,15 +374,15 @@ hr:before {
368
374
 
369
375
  .long-text h3 {
370
376
  font-family: var(--font-sans);
371
- font-weight: 565;
377
+ font-weight: 550;
372
378
  text-transform: uppercase;
373
379
  letter-spacing: 0.025em;
374
380
  }
375
381
 
376
382
  .long-text h4 {
377
- font-family: var(--font-sans);
378
- font-weight: 650;
379
- letter-spacing: 0.02em;
383
+ font-family: var(--font-serif);
384
+ font-weight: 400;
385
+ font-style: italic;
380
386
  }
381
387
 
382
388
  .long-text h5 {
@@ -435,6 +441,10 @@ hr:before {
435
441
  {% endblock long_text_styles %}
436
442
 
437
443
  {% block table_styles %}
444
+ table, th, td, tbody tr {
445
+ transition: background-color 0.4s ease-in-out, border-color 0.4s ease-in-out;
446
+ }
447
+
438
448
  table {
439
449
  font-family: var(--font-sans);
440
450
  font-size: var(--font-size-small);
@@ -453,6 +463,7 @@ th {
453
463
  letter-spacing: 0.03em;
454
464
  border-bottom: 1px solid var(--color-border-hint);
455
465
  line-height: 1.2;
466
+ background-color: var(--color-bg-alt-solid);
456
467
  }
457
468
 
458
469
  th, td {
@@ -461,10 +472,6 @@ th, td {
461
472
  min-width: 6rem;
462
473
  }
463
474
 
464
- th {
465
- background-color: var(--color-bg-alt-solid);
466
- }
467
-
468
475
  tbody tr:nth-child(even) {
469
476
  background-color: var(--color-bg-alt-solid);
470
477
  }
@@ -480,7 +487,12 @@ tbody tr:nth-child(even) {
480
487
  transform: translateX(-50%);
481
488
  /* Prevent container from expanding beyond its content area */
482
489
  overflow-x: auto;
483
- overflow-y: visible;
490
+ overflow-y: hidden; /* Whole height of table shown, no vertical scrolling. */
491
+ }
492
+
493
+ .table-container table {
494
+ /* Tricky: Need this to prevent bogus extra horizontal scroll while keeping normal sizing */
495
+ contain: content;
484
496
  }
485
497
 
486
498
  /* When TOC is present, simplify table container positioning */
@@ -540,7 +552,6 @@ sup {
540
552
  width: calc(100vw - 6rem);
541
553
  /* Ensure container doesn't expand beyond its width */
542
554
  max-width: calc(100vw - 6rem);
543
- contain: layout inline-size;
544
555
  }
545
556
 
546
557
  /* Apply shadow to long-text containers on larger screens */
@@ -590,8 +601,6 @@ sup {
590
601
 
591
602
  /* Ensure horizontal scroll works properly without expanding container */
592
603
  overflow-x: auto;
593
- overflow-y: visible;
594
- contain: layout inline-size;
595
604
  }
596
605
 
597
606
  .content-with-toc.has-toc table {
@@ -634,20 +643,17 @@ sup {
634
643
 
635
644
  /* Make table containers scrollable without affecting page layout */
636
645
  .table-container {
637
- width: calc(100vw - 3rem); /* Fixed width instead of max-width */
638
- max-width: calc(100vw - 3rem);
639
646
  overflow-x: auto;
640
- overflow-y: visible; /* Ensure vertical overflow is not hidden */
641
647
  transform: none;
642
648
  left: 0;
643
649
  position: relative;
644
650
  margin-left: auto;
645
- margin-right: auto;
651
+ margin-right: 0; /* Extend to right edge */
646
652
  /* Prevent container from expanding beyond its width */
647
653
  box-sizing: border-box;
648
- contain: layout inline-size; /* CSS containment to prevent width expansion */
654
+ width: calc(100vw - 1.5rem); /* Full width minus left margin */
649
655
  }
650
-
656
+
651
657
  table {
652
658
  font-size: var(--font-size-smaller);
653
659
  /* Tables can be wider than container on mobile */
@@ -6,7 +6,7 @@
6
6
 
7
7
  @media (min-width: 1536px) {
8
8
  :root {
9
- --toc-width: min(21vw, 30rem);
9
+ --toc-width: min(21vw, 26rem);
10
10
  }
11
11
  }
12
12
 
@@ -7,8 +7,8 @@ const TOOLTIP_CONFIG = {
7
7
  // Timing delays (in milliseconds)
8
8
  delays: {
9
9
  show: 500, // Delay before showing tooltip
10
- hide: 1500, // Default delay before hiding tooltip
11
- hideWideRight: 2000, // Delay for wide-right tooltips (farther away)
10
+ hide: 2500, // Default delay before hiding tooltip
11
+ hideWideRight: 4000, // Delay for wide-right tooltips (farther away)
12
12
  hideMovingToward: 500, // Shorter delay when mouse moving toward tooltip
13
13
  hideLinkClick: 300, // Delay after clicking a link in tooltip
14
14
  },
@@ -79,6 +79,14 @@ const TooltipUtils = {
79
79
  return this.extractContent(element, true);
80
80
  },
81
81
 
82
+ // Add footnote navigation button to tooltip content
83
+ addFootnoteNavigationButton(content, footnoteId) {
84
+ const navButton = `<span class="footnote-nav-container">
85
+ <a href="#${footnoteId}" class="footnote" data-footnote-id="${footnoteId}" title="Go to footnote">&nbsp;↓&nbsp;</a>
86
+ </span>`;
87
+ return content + navButton;
88
+ },
89
+
82
90
  // Determine optimal tooltip position based on element location and screen size
83
91
  getOptimalPosition(element) {
84
92
  const viewportWidth = window.innerWidth;
@@ -106,6 +114,69 @@ const TooltipUtils = {
106
114
  }
107
115
  };
108
116
 
117
+ /* -----------------------------------------------------------------------------
118
+ Mobile tap-outside-to-close functionality
119
+ ----------------------------------------------------------------------------- */
120
+
121
+ // Mobile interaction manager
122
+ const MobileInteractionManager = {
123
+ isActive: false,
124
+
125
+ // Initialize mobile interaction handling
126
+ init() {
127
+ if (this.isActive) return;
128
+
129
+ // Only activate on mobile screens
130
+ if (window.innerWidth <= TOOLTIP_CONFIG.breakpoints.mobile) {
131
+ this.activate();
132
+ }
133
+
134
+ // Listen for resize events to activate/deactivate
135
+ window.addEventListener('resize', () => {
136
+ if (window.innerWidth <= TOOLTIP_CONFIG.breakpoints.mobile) {
137
+ this.activate();
138
+ } else {
139
+ this.deactivate();
140
+ }
141
+ });
142
+ },
143
+
144
+ // Activate mobile interaction handling
145
+ activate() {
146
+ if (this.isActive) return;
147
+ this.isActive = true;
148
+ document.addEventListener('touchstart', this.handleTouchStart.bind(this), { passive: true });
149
+ console.debug('Mobile interaction manager activated');
150
+ },
151
+
152
+ // Deactivate mobile interaction handling
153
+ deactivate() {
154
+ if (!this.isActive) return;
155
+ this.isActive = false;
156
+ document.removeEventListener('touchstart', this.handleTouchStart.bind(this));
157
+ console.debug('Mobile interaction manager deactivated');
158
+ },
159
+
160
+ // Handle touch start events
161
+ handleTouchStart(event) {
162
+ // Check if any tooltip is visible
163
+ const hasVisibleTooltips = Array.from(TooltipManager.activeTooltips.values())
164
+ .some(state => state.isVisible);
165
+
166
+ if (!hasVisibleTooltips) return;
167
+
168
+ // Check if touch is outside all tooltips
169
+ const touchedElement = event.target;
170
+ const isInsideTooltip = touchedElement.closest('.tooltip-element');
171
+ const isTooltipTrigger = touchedElement.closest('[data-tooltip-trigger]');
172
+
173
+ if (!isInsideTooltip && !isTooltipTrigger) {
174
+ // Close all tooltips
175
+ TooltipManager.closeAllTooltips();
176
+ }
177
+ }
178
+ };
179
+
109
180
  /* -----------------------------------------------------------------------------
110
181
  Generic tooltip creation and management functions
111
182
  ----------------------------------------------------------------------------- */
@@ -115,7 +186,7 @@ const TooltipManager = {
115
186
  activeTooltips: new Map(), // Track active tooltip states
116
187
 
117
188
  // Add tooltip to any element with HTML content
118
- addTooltip(element, htmlContent, position = 'auto') {
189
+ addTooltip(element, htmlContent, position = 'auto', options = {}) {
119
190
  if (!element || !htmlContent) return;
120
191
 
121
192
  // Check if tooltip already exists for this element
@@ -128,7 +199,7 @@ const TooltipManager = {
128
199
  TooltipUtils.getOptimalPosition(element) : position;
129
200
 
130
201
  // Create real DOM tooltip element
131
- const tooltipElement = this.createTooltipElement(htmlContent, actualPosition);
202
+ const tooltipElement = this.createTooltipElement(htmlContent, actualPosition, options);
132
203
 
133
204
  // Mark the trigger element
134
205
  element.setAttribute('data-tooltip-trigger', 'true');
@@ -147,29 +218,62 @@ const TooltipManager = {
147
218
  }
148
219
 
149
220
  // Set up enhanced hover behavior
150
- this.setupAdvancedHover(element, tooltipElement, actualPosition);
221
+ this.setupAdvancedHover(element, tooltipElement, actualPosition, options);
151
222
  },
152
223
 
153
224
  // Create a real DOM tooltip element
154
- createTooltipElement(htmlContent, position) {
225
+ createTooltipElement(htmlContent, position, options = {}) {
155
226
  const tooltip = document.createElement('div');
156
227
  tooltip.className = `tooltip-element tooltip-${position}`;
157
228
 
158
229
  // Add footnote-specific class if content contains footnote or links
159
- if (htmlContent.includes('footnote') || htmlContent.includes('<a')) {
230
+ if (htmlContent.includes('footnote') || htmlContent.includes('<a') || options.isFootnote) {
160
231
  tooltip.classList.add('footnote-element');
161
232
  }
162
233
 
163
234
  tooltip.innerHTML = htmlContent;
235
+
236
+ // Set up footnote navigation button if present
237
+ if (options.isFootnote) {
238
+ this.setupFootnoteNavigation(tooltip, options.footnoteId);
239
+ }
240
+
164
241
  return tooltip;
165
242
  },
166
243
 
244
+ // Set up footnote navigation link functionality
245
+ setupFootnoteNavigation(tooltipElement, footnoteId) {
246
+ const navLink = tooltipElement.querySelector('.footnote-nav-container .footnote');
247
+ if (navLink) {
248
+ navLink.addEventListener('click', (e) => {
249
+ e.preventDefault();
250
+ e.stopPropagation();
251
+
252
+ // Navigate to the footnote
253
+ const footnoteElement = document.getElementById(footnoteId);
254
+ if (footnoteElement) {
255
+ footnoteElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
256
+
257
+ // Close tooltip after navigation
258
+ setTimeout(() => {
259
+ const tooltipState = Array.from(this.activeTooltips.values())
260
+ .find(state => state.tooltipElement === tooltipElement);
261
+ if (tooltipState) {
262
+ this.hideTooltip(tooltipState);
263
+ }
264
+ }, TOOLTIP_CONFIG.delays.hideLinkClick);
265
+ }
266
+ });
267
+ }
268
+ },
269
+
167
270
  // Set up advanced hover behavior with delays and mouse tracking
168
- setupAdvancedHover(triggerElement, tooltipElement, position) {
271
+ setupAdvancedHover(triggerElement, tooltipElement, position, options = {}) {
169
272
  const tooltipState = {
170
273
  triggerElement,
171
274
  tooltipElement,
172
275
  position,
276
+ options,
173
277
  showTimeout: null,
174
278
  hideTimeout: null,
175
279
  isVisible: false
@@ -181,6 +285,7 @@ const TooltipManager = {
181
285
  const handlers = {
182
286
  triggerEnter: (e) => this.handleTriggerEnter(tooltipState, e),
183
287
  triggerLeave: (e) => this.handleTriggerLeave(tooltipState, e),
288
+ triggerTouch: (e) => this.handleTriggerTouch(tooltipState, e),
184
289
  tooltipEnter: () => this.handleTooltipEnter(tooltipState),
185
290
  tooltipLeave: () => this.handleTooltipLeave(tooltipState),
186
291
  tooltipClick: (e) => this.handleTooltipClick(tooltipState, e)
@@ -189,6 +294,7 @@ const TooltipManager = {
189
294
  // Add event listeners
190
295
  triggerElement.addEventListener('mouseenter', handlers.triggerEnter);
191
296
  triggerElement.addEventListener('mouseleave', handlers.triggerLeave);
297
+ triggerElement.addEventListener('touchstart', handlers.triggerTouch, { passive: false });
192
298
  tooltipElement.addEventListener('mouseenter', handlers.tooltipEnter);
193
299
  tooltipElement.addEventListener('mouseleave', handlers.tooltipLeave);
194
300
  tooltipElement.addEventListener('click', handlers.tooltipClick);
@@ -197,6 +303,7 @@ const TooltipManager = {
197
303
  tooltipState.cleanupListeners = () => {
198
304
  triggerElement.removeEventListener('mouseenter', handlers.triggerEnter);
199
305
  triggerElement.removeEventListener('mouseleave', handlers.triggerLeave);
306
+ triggerElement.removeEventListener('touchstart', handlers.triggerTouch);
200
307
  tooltipElement.removeEventListener('mouseenter', handlers.tooltipEnter);
201
308
  tooltipElement.removeEventListener('mouseleave', handlers.tooltipLeave);
202
309
  tooltipElement.removeEventListener('click', handlers.tooltipClick);
@@ -228,6 +335,26 @@ const TooltipManager = {
228
335
  }
229
336
  },
230
337
 
338
+ // Handle touch events on trigger elements (mobile)
339
+ handleTriggerTouch(tooltipState, event) {
340
+ // Prevent default to avoid triggering mouse events and navigation
341
+ event.preventDefault();
342
+ event.stopPropagation();
343
+
344
+ // If tooltip is already visible, hide it
345
+ if (tooltipState.isVisible) {
346
+ this.hideTooltip(tooltipState);
347
+ return;
348
+ }
349
+
350
+ // Cancel any pending timers
351
+ this.clearTimeout(tooltipState, 'hideTimeout');
352
+ this.clearTimeout(tooltipState, 'showTimeout');
353
+
354
+ // Show tooltip immediately on touch (no delay like hover)
355
+ this.showTooltip(tooltipState, event);
356
+ },
357
+
231
358
  // Handle tooltip mouse enter
232
359
  handleTooltipEnter(tooltipState) {
233
360
  this.clearTimeout(tooltipState, 'hideTimeout');
@@ -242,14 +369,17 @@ const TooltipManager = {
242
369
 
243
370
  // Handle clicks within tooltip
244
371
  handleTooltipClick(tooltipState, event) {
245
- // Allow clicks on links within tooltips
246
- if (event.target.tagName === 'A') {
372
+ // Allow clicks on all links and buttons within tooltips
373
+ if (event.target.tagName === 'A' || event.target.tagName === 'BUTTON' ||
374
+ event.target.closest('a') || event.target.closest('button')) {
247
375
  event.stopPropagation();
248
- // Keep tooltip open briefly after clicking a link
249
- this.clearTimeout(tooltipState, 'hideTimeout');
250
- tooltipState.hideTimeout = setTimeout(() => {
251
- this.hideTooltip(tooltipState);
252
- }, TOOLTIP_CONFIG.delays.hideLinkClick);
376
+ // Keep tooltip open briefly after clicking a link (except footnote nav link)
377
+ if (!event.target.closest('.footnote-nav-container')) {
378
+ this.clearTimeout(tooltipState, 'hideTimeout');
379
+ tooltipState.hideTimeout = setTimeout(() => {
380
+ this.hideTooltip(tooltipState);
381
+ }, TOOLTIP_CONFIG.delays.hideLinkClick);
382
+ }
253
383
  }
254
384
  },
255
385
 
@@ -632,8 +762,9 @@ const TooltipManager = {
632
762
 
633
763
  if (shouldBeWideRight !== isWideRight) {
634
764
  const htmlContent = tooltipState.tooltipElement.innerHTML;
765
+ const options = tooltipState.options;
635
766
  this.removeTooltip(element);
636
- this.addTooltip(element, htmlContent, 'auto');
767
+ this.addTooltip(element, htmlContent, 'auto', options);
637
768
  }
638
769
  });
639
770
  }
@@ -667,6 +798,18 @@ function initFootnoteTooltips() {
667
798
  return;
668
799
  }
669
800
 
801
+ // Prevent default action immediately for all footnote reference links
802
+ refLink.addEventListener('click', (e) => {
803
+ e.preventDefault();
804
+ return false;
805
+ });
806
+
807
+ // Also prevent default on touch events for mobile
808
+ refLink.addEventListener('touchend', (e) => {
809
+ e.preventDefault();
810
+ return false;
811
+ });
812
+
670
813
  // Extract and validate content
671
814
  const footnoteHtml = TooltipUtils.extractHtmlContent(footnoteElement);
672
815
  const footnoteText = TooltipUtils.extractTextContent(footnoteElement);
@@ -677,10 +820,16 @@ function initFootnoteTooltips() {
677
820
  }
678
821
 
679
822
  // Truncate if needed
680
- const displayContent = truncateFootnoteContent(footnoteHtml, footnoteText);
823
+ let displayContent = truncateFootnoteContent(footnoteHtml, footnoteText);
681
824
 
682
- // Add tooltip
683
- TooltipManager.addTooltip(refLink, displayContent, 'auto');
825
+ // Add footnote navigation button
826
+ displayContent = TooltipUtils.addFootnoteNavigationButton(displayContent, footnoteId);
827
+
828
+ // Add tooltip with footnote options
829
+ TooltipManager.addTooltip(refLink, displayContent, 'auto', {
830
+ isFootnote: true,
831
+ footnoteId: footnoteId
832
+ });
684
833
  tooltipsAdded++;
685
834
 
686
835
  console.debug(`Added tooltip for footnote ${footnoteId}: "${footnoteText.substring(0, 50)}..."`);
@@ -715,6 +864,9 @@ function initTooltips() {
715
864
  console.debug('Starting tooltip initialization...');
716
865
 
717
866
  try {
867
+ // Initialize mobile interaction manager
868
+ MobileInteractionManager.init();
869
+
718
870
  const footnoteCount = initFootnoteTooltips();
719
871
  console.debug(`Tooltip initialization complete. Total footnote tooltips: ${footnoteCount}`);
720
872
  } catch (error) {
@@ -62,10 +62,10 @@
62
62
  z-index: 1000;
63
63
 
64
64
  /* Tooltip styling - matching TOC active item */
65
- background: var(--color-bg-selected);
65
+ background: var(--color-bg-meta-solid);
66
66
  border: 1px solid var(--color-border-hint);
67
67
  border-left: 2px solid var(--color-primary);
68
- color: var(--color-secondary);
68
+ color: var(--color-text);
69
69
  font-family: var(--font-sans);
70
70
  font-size: var(--font-size-small);
71
71
  line-height: 1.4;
@@ -104,11 +104,6 @@
104
104
  pointer-events: auto;
105
105
  }
106
106
 
107
- /* Tooltip hover state */
108
- .tooltip-element:hover {
109
- background: var(--color-hover-bg);
110
- }
111
-
112
107
  /* -----------------------------------------------------------------------------
113
108
  General Tooltip Positioning - Using margin variables
114
109
  ----------------------------------------------------------------------------- */
@@ -323,7 +318,7 @@
323
318
 
324
319
  /* Footnote reference styling */
325
320
  .footnote-ref a[data-tooltip-trigger] {
326
- transition: color 0.4s ease-in-out;
321
+ transition: color 0.2s ease-in-out;
327
322
  }
328
323
 
329
324
  .footnote-ref a[data-tooltip-trigger]:hover {
@@ -367,6 +362,16 @@
367
362
  display: none;
368
363
  }
369
364
 
365
+ /* But show our navigation footnote link */
366
+ .footnote-element .footnote-nav-container .footnote {
367
+ display: inline;
368
+ text-decoration: none !important;
369
+ }
370
+
371
+ .footnote-element .footnote-nav-container .footnote:hover {
372
+ text-decoration: none !important;
373
+ }
374
+
370
375
  /* Text breaking rules for all tooltips */
371
376
  .tooltip-element .force-break,
372
377
  .tooltip-element code,
@@ -479,4 +484,14 @@
479
484
  .tooltip-element:not(.tooltip-mobile-bottom) {
480
485
  max-width: min(var(--tooltip-max-width), calc(100vw - 2rem)) !important;
481
486
  }
487
+ }
488
+
489
+ /* -----------------------------------------------------------------------------
490
+ Footnote Navigation Button Styles
491
+ ----------------------------------------------------------------------------- */
492
+
493
+ /* Container for footnote navigation link */
494
+ .footnote-nav-container {
495
+ float: right;
496
+ margin-left: 0.5rem;
482
497
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kash-shell
3
- Version: 0.3.20
3
+ Version: 0.3.21
4
4
  Summary: The knowledge agent shell (core)
5
5
  Project-URL: Repository, https://github.com/jlevy/kash-shell
6
6
  Author-email: Joshua Levy <joshua@cal.berkeley.edu>
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Programming Language :: Python :: 3.13
17
17
  Classifier: Typing :: Typed
18
18
  Requires-Python: <4.0,>=3.11
19
+ Requires-Dist: aiolimiter>=1.2.1
19
20
  Requires-Dist: anyio>=4.8.0
20
21
  Requires-Dist: audioop-lts>=0.2.1; python_version >= '3.13'
21
22
  Requires-Dist: cachetools>=5.5.2
@@ -50,6 +51,7 @@ Requires-Dist: pydantic>=2.10.6
50
51
  Requires-Dist: pydub>=0.25.1
51
52
  Requires-Dist: pygments>=2.19.1
52
53
  Requires-Dist: pyperclip>=1.9.0
54
+ Requires-Dist: pyrate-limiter>=3.7.0
53
55
  Requires-Dist: python-dotenv>=1.0.1
54
56
  Requires-Dist: python-magic-bin>=0.4.14; platform_system == 'Windows'
55
57
  Requires-Dist: python-magic>=0.4.27; platform_system == 'Linux' or platform_system == 'Darwin'