webbee 0.3.28__tar.gz → 0.3.30__tar.gz

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 (118) hide show
  1. {webbee-0.3.28 → webbee-0.3.30}/CHANGELOG.md +12 -0
  2. {webbee-0.3.28 → webbee-0.3.30}/PKG-INFO +1 -1
  3. {webbee-0.3.28 → webbee-0.3.30}/pyproject.toml +1 -1
  4. webbee-0.3.30/src/webbee/__init__.py +1 -0
  5. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/home_view.py +66 -38
  6. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/render.py +13 -0
  7. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/repl.py +5 -0
  8. {webbee-0.3.28 → webbee-0.3.30}/tests/test_home_view.py +26 -4
  9. webbee-0.3.28/src/webbee/__init__.py +0 -1
  10. {webbee-0.3.28 → webbee-0.3.30}/.github/workflows/publish.yml +0 -0
  11. {webbee-0.3.28 → webbee-0.3.30}/.gitignore +0 -0
  12. {webbee-0.3.28 → webbee-0.3.30}/LICENSE +0 -0
  13. {webbee-0.3.28 → webbee-0.3.30}/README.md +0 -0
  14. {webbee-0.3.28 → webbee-0.3.30}/docs/superpowers/plans/2026-07-21-interactive-home-dashboard.md +0 -0
  15. {webbee-0.3.28 → webbee-0.3.30}/install.sh +0 -0
  16. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/account.py +0 -0
  17. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/active_sessions.py +0 -0
  18. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/banner_art.py +0 -0
  19. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/boot.py +0 -0
  20. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/checkpoints.py +0 -0
  21. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/cli.py +0 -0
  22. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/clipboard.py +0 -0
  23. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/coding_context.py +0 -0
  24. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/commands.py +0 -0
  25. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/config.py +0 -0
  26. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/consent.py +0 -0
  27. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/details.py +0 -0
  28. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/events.py +0 -0
  29. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/frames.py +0 -0
  30. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/home.py +0 -0
  31. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/http.py +0 -0
  32. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/instance_lock.py +0 -0
  33. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/intel/__init__.py +0 -0
  34. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/intel/chunker.py +0 -0
  35. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/intel/embed.py +0 -0
  36. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/intel/graph.py +0 -0
  37. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/intel/indexer.py +0 -0
  38. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/intel/models.py +0 -0
  39. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/intel/query.py +0 -0
  40. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/intel/service.py +0 -0
  41. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/intel/store.py +0 -0
  42. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/intel/vectors.py +0 -0
  43. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/intel/watch.py +0 -0
  44. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/mode_store.py +0 -0
  45. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/newtab_mode.py +0 -0
  46. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/output_pane.py +0 -0
  47. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/queue_panel.py +0 -0
  48. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/reflow.py +0 -0
  49. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/remote.py +0 -0
  50. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/repo.py +0 -0
  51. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/selection.py +0 -0
  52. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/session.py +0 -0
  53. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/sessions.py +0 -0
  54. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/sizing.py +0 -0
  55. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/slots.py +0 -0
  56. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/steer.py +0 -0
  57. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/stream.py +0 -0
  58. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/tabs.py +0 -0
  59. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/thread.py +0 -0
  60. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/todo_panel.py +0 -0
  61. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/tokens.py +0 -0
  62. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/tools.py +0 -0
  63. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/tui.py +0 -0
  64. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/update.py +0 -0
  65. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/urlopen.py +0 -0
  66. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/wallet.py +0 -0
  67. {webbee-0.3.28 → webbee-0.3.30}/src/webbee/worktrees.py +0 -0
  68. {webbee-0.3.28 → webbee-0.3.30}/tests/__init__.py +0 -0
  69. {webbee-0.3.28 → webbee-0.3.30}/tests/conftest.py +0 -0
  70. {webbee-0.3.28 → webbee-0.3.30}/tests/test_account.py +0 -0
  71. {webbee-0.3.28 → webbee-0.3.30}/tests/test_active_sessions.py +0 -0
  72. {webbee-0.3.28 → webbee-0.3.30}/tests/test_checkpoints.py +0 -0
  73. {webbee-0.3.28 → webbee-0.3.30}/tests/test_cli.py +0 -0
  74. {webbee-0.3.28 → webbee-0.3.30}/tests/test_clipboard.py +0 -0
  75. {webbee-0.3.28 → webbee-0.3.30}/tests/test_commands.py +0 -0
  76. {webbee-0.3.28 → webbee-0.3.30}/tests/test_config.py +0 -0
  77. {webbee-0.3.28 → webbee-0.3.30}/tests/test_cpc_contract_stable.py +0 -0
  78. {webbee-0.3.28 → webbee-0.3.30}/tests/test_details.py +0 -0
  79. {webbee-0.3.28 → webbee-0.3.30}/tests/test_events.py +0 -0
  80. {webbee-0.3.28 → webbee-0.3.30}/tests/test_freeze_fix.py +0 -0
  81. {webbee-0.3.28 → webbee-0.3.30}/tests/test_home.py +0 -0
  82. {webbee-0.3.28 → webbee-0.3.30}/tests/test_instance_lock.py +0 -0
  83. {webbee-0.3.28 → webbee-0.3.30}/tests/test_intel_chunker.py +0 -0
  84. {webbee-0.3.28 → webbee-0.3.30}/tests/test_intel_embed.py +0 -0
  85. {webbee-0.3.28 → webbee-0.3.30}/tests/test_intel_graph.py +0 -0
  86. {webbee-0.3.28 → webbee-0.3.30}/tests/test_intel_indexer.py +0 -0
  87. {webbee-0.3.28 → webbee-0.3.30}/tests/test_intel_no_numpy.py +0 -0
  88. {webbee-0.3.28 → webbee-0.3.30}/tests/test_intel_query.py +0 -0
  89. {webbee-0.3.28 → webbee-0.3.30}/tests/test_intel_service.py +0 -0
  90. {webbee-0.3.28 → webbee-0.3.30}/tests/test_intel_store.py +0 -0
  91. {webbee-0.3.28 → webbee-0.3.30}/tests/test_intel_vectors.py +0 -0
  92. {webbee-0.3.28 → webbee-0.3.30}/tests/test_intel_watch.py +0 -0
  93. {webbee-0.3.28 → webbee-0.3.30}/tests/test_marathon.py +0 -0
  94. {webbee-0.3.28 → webbee-0.3.30}/tests/test_mode_store.py +0 -0
  95. {webbee-0.3.28 → webbee-0.3.30}/tests/test_newtab_mode.py +0 -0
  96. {webbee-0.3.28 → webbee-0.3.30}/tests/test_packaging.py +0 -0
  97. {webbee-0.3.28 → webbee-0.3.30}/tests/test_reflow.py +0 -0
  98. {webbee-0.3.28 → webbee-0.3.30}/tests/test_render.py +0 -0
  99. {webbee-0.3.28 → webbee-0.3.30}/tests/test_repl.py +0 -0
  100. {webbee-0.3.28 → webbee-0.3.30}/tests/test_repo.py +0 -0
  101. {webbee-0.3.28 → webbee-0.3.30}/tests/test_session.py +0 -0
  102. {webbee-0.3.28 → webbee-0.3.30}/tests/test_sessions.py +0 -0
  103. {webbee-0.3.28 → webbee-0.3.30}/tests/test_sizing.py +0 -0
  104. {webbee-0.3.28 → webbee-0.3.30}/tests/test_slots.py +0 -0
  105. {webbee-0.3.28 → webbee-0.3.30}/tests/test_steer.py +0 -0
  106. {webbee-0.3.28 → webbee-0.3.30}/tests/test_stream.py +0 -0
  107. {webbee-0.3.28 → webbee-0.3.30}/tests/test_tabs.py +0 -0
  108. {webbee-0.3.28 → webbee-0.3.30}/tests/test_thread.py +0 -0
  109. {webbee-0.3.28 → webbee-0.3.30}/tests/test_todo_panel.py +0 -0
  110. {webbee-0.3.28 → webbee-0.3.30}/tests/test_tokens.py +0 -0
  111. {webbee-0.3.28 → webbee-0.3.30}/tests/test_tools.py +0 -0
  112. {webbee-0.3.28 → webbee-0.3.30}/tests/test_tui.py +0 -0
  113. {webbee-0.3.28 → webbee-0.3.30}/tests/test_tui_hardening.py +0 -0
  114. {webbee-0.3.28 → webbee-0.3.30}/tests/test_update.py +0 -0
  115. {webbee-0.3.28 → webbee-0.3.30}/tests/test_urlopen.py +0 -0
  116. {webbee-0.3.28 → webbee-0.3.30}/tests/test_version.py +0 -0
  117. {webbee-0.3.28 → webbee-0.3.30}/tests/test_wallet.py +0 -0
  118. {webbee-0.3.28 → webbee-0.3.30}/tests/test_worktrees.py +0 -0
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.30
4
+
5
+ - Removed the stray "◆ Home" title from the top of the Home screen — Home now
6
+ opens straight into your tiles.
7
+
8
+ ## 0.3.29
9
+
10
+ - The Home screen scrolls for real now — mouse wheel and PageUp/PageDown move
11
+ through the whole page, however tall it gets.
12
+ - The Webbee Code logo now opens every new tab you start, and Home itself is
13
+ kept clean (the logo is no longer on Home).
14
+
3
15
  ## 0.3.28
4
16
 
5
17
  The Home screen scrolls now. When Home has more than fills the window — a tall
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webbee
3
- Version: 0.3.28
3
+ Version: 0.3.30
4
4
  Summary: Webbee 🐝 — the Imperal Cloud coding agent in your terminal
5
5
  Project-URL: Homepage, https://imperal.io
6
6
  Project-URL: Documentation, https://docs.imperal.io
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "webbee"
3
- version = "0.3.28"
3
+ version = "0.3.30"
4
4
  description = "Webbee 🐝 — the Imperal Cloud coding agent in your terminal"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1 @@
1
+ __version__ = "0.3.30"
@@ -259,6 +259,16 @@ def _line_len(frags) -> int:
259
259
  return sum(len(f[1]) for f in frags)
260
260
 
261
261
 
262
+ def _flatten_lines(lines):
263
+ """Flatten a list of fragment-lines into one fragment list with a newline
264
+ fragment terminating each line (what a FormattedTextControl consumes)."""
265
+ out = []
266
+ for ln in lines:
267
+ out.extend(ln)
268
+ out.append(("", "\n"))
269
+ return out
270
+
271
+
262
272
  def _pad_line(frags, width: int):
263
273
  n = _line_len(frags)
264
274
  return list(frags) + ([("", " " * (width - n))] if n < width else [])
@@ -295,6 +305,7 @@ class HomeView:
295
305
  data: "HomeData | None" = None, width: int = 100, out_pane=None):
296
306
  from prompt_toolkit.layout.containers import Window
297
307
  from prompt_toolkit.layout.controls import FormattedTextControl
308
+ from prompt_toolkit.mouse_events import MouseEventType
298
309
  self.slots = slots
299
310
  self.actions = actions
300
311
  self.data = data if data is not None else HomeData()
@@ -319,7 +330,29 @@ class HomeView:
319
330
  self._focus_id: "str | None" = None
320
331
  self._hover_id: "str | None" = None
321
332
  self._model: "HomeModel | None" = None
322
- self.control = FormattedTextControl(self._fragments, focusable=True, show_cursor=False)
333
+ # Virtualized scroll (mirrors OutputPane): the control is fed ONLY the
334
+ # visible slice of lines, so it always fits the viewport and prompt_
335
+ # toolkit never fights an (absent) cursor to reset the scroll — the
336
+ # reason a plain FormattedTextControl window won't scroll. `_scroll` is
337
+ # the top visible line; `_view_h_val` is the last render's viewport
338
+ # height. Wheel adjusts `_scroll` here; PageUp/PageDown route via
339
+ # `scroll()`; `notify()` jumps to the bottom to reveal new output.
340
+ self._scroll = 0
341
+ self._view_h_val = 20
342
+ view = self
343
+
344
+ class _HomeControl(FormattedTextControl):
345
+ def mouse_handler(self, ev):
346
+ et = ev.event_type
347
+ if et == MouseEventType.SCROLL_DOWN:
348
+ view.scroll(3)
349
+ return None
350
+ if et == MouseEventType.SCROLL_UP:
351
+ view.scroll(-3)
352
+ return None
353
+ return super().mouse_handler(ev)
354
+
355
+ self.control = _HomeControl(self._visible_fragments, focusable=True, show_cursor=False)
323
356
  self.window = Window(content=self.control, wrap_lines=False, always_hide_cursor=True)
324
357
 
325
358
  # ---- OutputPane-compatible surface (ticker/layout duck-type) ----------
@@ -336,21 +369,16 @@ class HomeView:
336
369
  return self._out.flash()
337
370
 
338
371
  def scroll(self, delta: int) -> None:
339
- # pageup/pagedown (tui routes here). Move the Window's own vertical
340
- # scroll, clamped to the scrollable range; render_info is present once
341
- # the window has been drawn at least one frame.
342
- win = self.window
343
- info = getattr(win, "render_info", None)
344
- if info is None:
345
- return
346
- max_scroll = max(0, info.content_height - info.window_height)
347
- win.vertical_scroll = min(max_scroll, max(0, win.vertical_scroll + delta))
372
+ # Wheel (via the control) and PageUp/PageDown (tui routes here) move the
373
+ # top visible line; clamped so you can't scroll past the ends.
374
+ lines = self._all_lines()
375
+ max_off = max(0, len(lines) - max(1, self._view_h_val))
376
+ self._scroll = max(0, min(self._scroll + delta, max_off))
348
377
  self._invalidate()
349
378
 
350
379
  @property
351
380
  def _view_h(self) -> int:
352
- info = getattr(self.window, "render_info", None)
353
- return info.window_height if info is not None else 20
381
+ return self._view_h_val
354
382
 
355
383
  def forward_mouse(self, ev, clamp: str = "bottom") -> bool:
356
384
  return False
@@ -360,12 +388,9 @@ class HomeView:
360
388
 
361
389
  def notify(self) -> None:
362
390
  # New captured output landed (a `_say` note / command reply). Reveal
363
- # it: jump the scroll to the bottom (PT clamps on the next render) so
364
- # the answer is visible, then repaint.
365
- try:
366
- self.window.vertical_scroll = 10 ** 7
367
- except Exception:
368
- pass
391
+ # it: jump to the bottom (clamped on the next render) so the answer is
392
+ # visible, then repaint.
393
+ self._scroll = 10 ** 7
369
394
  self._invalidate()
370
395
 
371
396
  def _invalidate(self) -> None:
@@ -434,7 +459,7 @@ class HomeView:
434
459
  return _h
435
460
 
436
461
  # ---- render -----------------------------------------------------------
437
- def _fragments(self):
462
+ def _all_lines(self):
438
463
  from webbee.home import _mask_email
439
464
  from webbee.render import _fmt_tokens
440
465
  m = self._build_model()
@@ -571,21 +596,9 @@ class HomeView:
571
596
  "· Ctrl+T new tab · Alt+N switch")])
572
597
  return L
573
598
 
574
- # Header: the Webbee Code ASCII logo (bee-yellow), then a centered
575
- # "◆ Home" restored (it was on the pre-dashboard Home and Valentin
576
- # wants it kept). The whole art block is centered by ONE offset (max
577
- # line width) so its shape is preserved; degrades to left-aligned when
578
- # the terminal is narrower than the art.
579
- from webbee.banner_art import WEBBEE_CODE
580
- art = WEBBEE_CODE.rstrip("\n").split("\n")
581
- art_w = max((len(ln) for ln in art), default=0)
582
- art_pad = max(0, (width - art_w) // 2)
599
+ # No center title — the tab bar's Home chip already names the tab,
600
+ # and the tiles below are Home's content. Just a leading blank line.
583
601
  lines = [[("", "")]]
584
- for ln in art:
585
- lines.append([("", " " * art_pad), ("class:home.header", ln)] if ln else [("", "")])
586
- title_pad = max(0, (width - len("◆ Home")) // 2)
587
- lines.append([("", " " * title_pad), ("class:home.header", "◆ Home")])
588
- lines.append([("", "")])
589
602
  if two_column(width):
590
603
  colw = max(20, (width // 2) - 3)
591
604
  lines += _side_by_side(you_lines(), wallet_lines(), colw)
@@ -609,9 +622,24 @@ class HomeView:
609
622
  lines.append([("", "")])
610
623
  lines.append([("class:home.header", " Output ")])
611
624
  lines.append(list(to_formatted_text(ANSI(dump.rstrip("\n")))))
625
+ return lines
612
626
 
613
- out = []
614
- for ln in lines:
615
- out.extend(ln)
616
- out.append(("", "\n"))
617
- return out
627
+ def _fragments(self):
628
+ """The FULL dashboard flattened (every line). The build helpers and
629
+ tests use this; the on-screen control uses `_visible_fragments`."""
630
+ return _flatten_lines(self._all_lines())
631
+
632
+ def _visible_fragments(self):
633
+ """Only the visible slice `[_scroll : _scroll+view_h]`, flattened — what
634
+ the control renders. Captures the live viewport height from the last
635
+ render (`render_info.window_height`) and clamps `_scroll` into range, so
636
+ the slice always fits and the window never needs (and never fights) a
637
+ cursor to scroll."""
638
+ ri = getattr(self.window, "render_info", None)
639
+ if ri is not None and getattr(ri, "window_height", 0):
640
+ self._view_h_val = ri.window_height
641
+ lines = self._all_lines()
642
+ h = max(1, self._view_h_val)
643
+ off = max(0, min(self._scroll, max(0, len(lines) - h)))
644
+ self._scroll = off
645
+ return _flatten_lines(lines[off:off + h])
@@ -214,6 +214,19 @@ class RichSink:
214
214
  self.console.print()
215
215
  self._nudge()
216
216
 
217
+ def banner(self) -> None:
218
+ """The Webbee Code brand logo, printed at the top of every NEW session
219
+ tab (0.3.29) so each tab opens with the logo the launch splash has
220
+ always carried — just the brand block, no identity/privacy (those stay
221
+ on the one-time boot `welcome`). Align.center = reflow-safe."""
222
+ self.console.print()
223
+ self.console.print(Align.center(Text(WEBBEE_CODE, style=f"bold {_BEE}")))
224
+ self.console.print(Align.center(Text("🐝", style=f"bold {_BEE}")))
225
+ self.console.print(Align.center(Text("ICNLI AI Cloud OS · Agent", style=f"bold {_ACCENT}")))
226
+ self.console.print(Align.center(Text("· i m p e r a l . i o ·", style="dim")))
227
+ self.console.print()
228
+ self._nudge()
229
+
217
230
  # ---- turn lifecycle -------------------------------------------------
218
231
  def begin_turn(self) -> None:
219
232
  self._tools = 0
@@ -419,6 +419,11 @@ async def _finish_slot(cfg, token_provider, workspace, mode, *, resources: Works
419
419
  # one kernel-owned row. Reference share — never a copy.
420
420
  sink.local_pending = slot.pending
421
421
  replayed = 0
422
+ if not first:
423
+ # Every NEW tab opens with the Webbee Code logo (0.3.29) — the brand
424
+ # block the launch splash carries — since the boot `welcome` (below)
425
+ # only runs for the first tab.
426
+ sink.banner()
422
427
  if first:
423
428
  if account is not None:
424
429
  sink.welcome(account, workspace, "terminal")
@@ -297,11 +297,31 @@ def test_you_tile_shows_expires_when_not_active():
297
297
  assert "expires Aug 3" in text
298
298
 
299
299
 
300
- def test_home_renders_webbee_code_logo_and_title():
300
+ def test_home_has_no_logo_and_no_center_title():
301
+ # The Webbee Code logo moved to each NEW session tab; the out-of-place
302
+ # center "◆ Home" title is gone too. Home's content starts with the tiles.
301
303
  hv, _ = _view()
302
304
  text = "".join(f[1] for f in hv._fragments())
303
- assert "_____" in text # a slice of the WEBBEE_CODE ascii banner
304
- assert "◆ Home" in text
305
+ assert "_____" not in text # no ascii logo on Home
306
+ assert "◆ Home" not in text # no center title (the tab-bar chip still has it)
307
+ assert "You" in text # the tiles are the top of Home
308
+
309
+
310
+ def test_home_virtualized_scroll_slices_and_clamps():
311
+ # Home virtualizes like OutputPane: the control renders only a slice ≤ the
312
+ # viewport height, and scroll() moves/clamps the top line.
313
+ hv, _ = _view()
314
+ hv._view_h_val = 5
315
+ total = len(hv._all_lines())
316
+ assert total > 5 # taller than a 5-row viewport
317
+ visible_newlines = sum(1 for f in hv._visible_fragments() if f[1] == "\n")
318
+ assert visible_newlines <= 5
319
+ hv.scroll(3)
320
+ assert hv._scroll == 3
321
+ hv.scroll(10 ** 6)
322
+ assert hv._scroll == total - 5 # clamped to the bottom
323
+ hv.scroll(-10 ** 6)
324
+ assert hv._scroll == 0 # clamped to the top
305
325
 
306
326
 
307
327
  def test_captured_output_renders_inline_and_notify_reveals_it():
@@ -314,4 +334,6 @@ def test_captured_output_renders_inline_and_notify_reveals_it():
314
334
  assert "hello-inline-output" in text
315
335
  assert "Output" in text
316
336
  hv.notify()
317
- assert hv.window.vertical_scroll >= 10 ** 6
337
+ hv._visible_fragments() # clamps _scroll into range
338
+ total = len(hv._all_lines())
339
+ assert hv._scroll == max(0, total - hv._view_h_val) # notify jumped to the bottom
@@ -1 +0,0 @@
1
- __version__ = "0.3.28"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes