Files
ratatoskr/pyproject.toml
T
vh 9918c10acf fix(tui): coalesce thinking deltas on \n (v0.7.1)
Operator: "thinking tokens seem to be split by token — each on a
newline, is that correct? We don't want that."

Root cause: v0.6.5 wrote each Thinking SSE delta as its own
`thinking_log.write(event.content)` call. Worldtree emits Thinking
events at token granularity (per-token or per-few-tokens), so EACH
token became its own RichLog line — visually choppy, one short
fragment per visual row. Wrong UX.

## Fix: coalesce-on-newline

Thinking deltas accumulate in `TuiPresenterState.thinking_chunk_buffer`
(new str field). On each Thinking event:

  1. Append delta content to buffer.
  2. Flush every COMPLETE line (chars before each `\n`) as one
     thinking_log.write(line) call.
  3. Leave the post-final-`\n` tail in the buffer for the next delta.

On any non-thinking event (run close):
  1. Flush remaining buffer tail (if any) as one final line.
  2. Write Rule(end).

Empty lines (blank paragraph separators in the model's `\n\n` flow)
are skipped — they'd render as no-content RichLog entries which
just add vertical noise. Natural paragraph breaks become single
visible lines; multi-paragraph thinking renders top-to-bottom.

## Verified live (tier-3 smoke against personal Worldtree)

Defined a `thinky-smoke` agent via `python -m ratatoskr.tier3 define`,
asked "What is 12 times 13?". Thinking pane rendered with natural
paragraph chunks:

  ── turn N · thinking #1 start ──
  Thinking Process:
  1.  **Analyze the Request:** The user wants to know the result of $12 \times 13$.
  2.  **Calculate:**
      *   Method 1: Standard multiplication.
          $$12 \times 10 = 120$$
          $$12 \times 3 = 36$$
          $$120 + 36 = 156$$
      *   Method 2: $(10 + 2)(10 + 3) = 100 + 30 + 20 + 6 = 156$.
  ── turn N · thinking #1 end ──

Each line = one natural paragraph or list item. No per-token fragments.

## Edge cases noted

- Long-running thinking with NO `\n` at all stays buffered until run
  close → operator sees nothing until close. Possible follow-up: add
  a length-threshold flush (e.g., > 500 chars → flush at the last
  space). For now this is acceptable; thinking content typically has
  `\n` breaks every few sentences.
- Empty deltas (`""`) are ignored implicitly — no buffer growth, no
  flush.
- `\n` at the very start of a delta flushes whatever was buffered
  before, then leaves the empty post-`\n` tail (empty string) in the
  buffer, which doesn't show up as an empty line because of the
  `if line:` guard.

## Contract amendment

docs/contracts/issues/13.contract.md INV-022 amended for v0.7.1
coalesce semantics. Drift-check clean.

## Tests

265/265 GREEN; ruff clean. Two updated tests:

- `test_thinking_streams_into_thinking_log` → renamed
  `test_thinking_coalesces_until_newline`: 3 token-shaped deltas
  with no `\n` → only Rule(start) writes, buffer holds accumulated.
- NEW `test_thinking_flushes_on_newline`: delta carrying `\n` →
  Rule(start) + accumulated line + clear buffer.
- `test_thinking_closes_to_thinking_log`: 2 deltas "a", "b" +
  close → Rule(start) + tail-flush "ab" + Rule(end) = 3 writes
  (was 4 with per-delta).

Patch bump (v0.7.0 → v0.7.1) — internal presenter routing change;
no public-API or layout change.
2026-05-24 20:39:55 -07:00

66 lines
1.8 KiB
TOML

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "ratatoskr"
version = "0.7.1"
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
readme = "README.md"
requires-python = ">=3.12"
license = { file = "LICENSE" }
authors = [{ name = "Vuong Hoang" }]
keywords = ["worldtree", "tui", "debug", "sse", "textual"]
# Network + SSE consumer + TUI framework.
# See docs/design-brief.md §1 (Textual), §3 (httpx-sse).
dependencies = [
"httpx>=0.27",
"httpx-sse>=0.4",
"textual>=0.85",
]
[project.optional-dependencies]
dev = [
"pytest>=8",
"pytest-asyncio>=0.24",
"respx>=0.21", # httpx mocking for SSE-recorded snapshot tests
"ruff>=0.6",
"mypy>=1.11",
"textual-dev>=1.5", # textual console + live reload during dev
"pyyaml>=6", # used by docs/contracts/contract_parser.py and scripts/contract_drift_check.py
]
[project.scripts]
ratatoskr = "ratatoskr.cli:main"
[project.urls]
Repository = "https://gitea.phasefinal.com/vh/ratatoskr"
"Design Brief" = "https://gitea.phasefinal.com/vh/brokkr-smithy/src/branch/main/docs/ratatoskr-design-brief.md"
# Worldtree spec pin — see docs/SPEC-PIN.md for the full bump procedure.
# Ratatoskr is built against Worldtree at this commit; the vendored
# spec snapshot in docs/ reflects that SHA.
[tool.ratatoskr.spec-pin]
worldtree-spec-rev = "55101e909abcd2219833266b6f905c5bc956e0f0"
worldtree-version = "v0.19.0"
pinned-on = "2026-05-20"
[tool.hatch.build.targets.wheel]
packages = ["src/ratatoskr"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP", "RUF"]
[tool.mypy]
python_version = "3.12"
strict = true