Files
brain-of-reese/.agents/phases/todo/158_thinking_timeline/01_schema_segments.md
T
ducoterra 966c4b0540
Build and Push Containers / build-and-push-app (push) Successful in 2m45s
Build and Push Containers / build-and-push-db (push) Successful in 19s
phase: 153_document_viewer_context
That last `--co` (collect-only) run was my probe mistake — it shows 34% because no tests execute under collect-only. The valid coverage run was the real gate command executed earlier: `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99%** (exit 0, all 3544 tests executed). All gates are verified.

## Phase 153 — final verification pass report

**Verified (all tasks 01–04 complete in `complete/`):**
- Modal `fullPageUrl(source, path, back = null)` + `openDocumentModal(…, back = null)` with module-level `currentBack`; 5 chat call sites pass `"/"`, sources.js call site unchanged (byte-identical Sources path); `documentUrl` comment now true
- Copy-path control (shared `renderDocument` core, icon+text, text = accessible name) on both surfaces; per-page clipboard helpers with the fallback-field + status-line contract; `#doc-copy-announcer` live region; styles + 44px touch floor
- Unit pins: `&back=%2F` / no-`back` byte-identity, `currentBack` single-writer guard, copy strings byte-pinned, phase-13 regression pins untouched

**Test / lint / coverage results:**
- `uv run pytest` → **3544 passed**, 0 failed (full unit + integration)
- `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99%** (>90% gate)
- `uv run pytest tests/e2e/test_document_viewer_context.py -v --no-cov` → **8 passed** in 47.33s, in isolation (DB up, mock LLM)
- `uv run ruff check . && uv run pyright` → clean (0 errors/warnings)

**Completion criteria:** all met — (1) chat→modal→Full page: `href="/"` + "Chat"; Sources→modal→Full page: `href="/sources.html"` + "Sources" with `back=` asserted absent; (2) no-`back` URLs byte-identical to pre-phase; (3) Copy path: "Path copied." on success (clipboard readback), prefilled select-on-focus fallback field + "Path ready — copy it from the field." on rejected clipboard, both surfaces parameterized; (4) copied string exactly `docs/homelab/kubernetes.md`; (5) full suite green, E2E isolated green, lint clean, coverage 99%; (6) commit by harness.

**Documentation:** README.md reviewed in full — phase-153 feature already named in the Chat section; routes, env reference, workflows, deployment all consistent — no changes needed. AGENTS.md reviewed in full — rules, gates, and quick-reference commands verified against the repo (incl. `test_smoke.py`, `.env.example`, gitignore entries) — consistent, no changes needed.

**Notable:** pytest 9.1.1 quirk — a doubled `-q` (pyproject `addopts` + explicit `-q`) swallows the final "N passed" summary line while still exiting 0; the plain `uv run pytest` gate command prints it. No defects found in prior work.

**Next pending phase:** `154_sync_status_freshness`.
2026-10-04 09:23:54 -04:00

3.8 KiB

Task 01 — the validated ChatMessage.segments field (server)

Phase: 158_thinking_timeline · Story: n/a

Objective

Saved-chat writes accept the new optional segments array (a discriminated thinking/tool union with phase-83-style caps), omit it when absent so pre-phase records round-trip byte-identically, and reject malformed values at the extra="forbid" boundary.

Work

  1. app/schemas.py:
    • ChatThinkingSegment (a BaseModel, beside ToolCall): kind: Literal["thinking"], text: str = Field(min_length=1, max_length=32_000) (HistoryTurn's cap, phase 83 — docstring cross-reference).
    • ChatToolSegment (a BaseModel): kind: Literal["tool"] + the EXACT ToolCall field set (name / argument / offset / limit / path / truncated / chars_shown / chars_total with their existing types and caps — mirror, do not weaken; docstring cross-references ToolCall as the shape source).
    • ChatSegment = Annotated[Union[ChatThinkingSegment, ChatToolSegment], Field(discriminator="kind")].
    • ChatMessage.segments: list[ChatSegment] | None = Field(default=None, max_length=50) — with the phase-83 voice comment (the cap sizing: far past any realistic turn — the round cap is 10 → ≤ 20 entries; 50 matches the tools cap).
    • The EXISTING wrap serializer (_drop_image_when_absent) gains the omission rule: after the two image pops, if self.segments is None: data.pop("segments", None) — ABSENT, never null (the phase-50 round-trip contract; the phase-122 SourceRef.image_url omission precedent, the phase-113 lesson applied up front: the field EXISTS so the boundary does NOT 422 the done-time auto-save carrying it).
    • The ChatMessage docstring gains the phase-158 paragraph (the shape, the omission rule, the extra="forbid" boundary) in the established voice; HistoryTurn and everything in app/rag/ is UNTOUCHED (the history wire stays {who, text, thinking}).
  2. NO migration, NO router change (app/api/chats.py validates through ChatMessage — m.model_dump() + ChatMessage.model_validate(m) already carry the new field end to end, including the public shared read SharedChatOut.messages).

Testing & Quality

  • Integration (extend tests/integration/test_chats_api.py — the file already pins the brain-record contract, phase 83/113/120/123/124):
    • a record WITH segments (one thinking entry + two tool entries carrying every additive field: offset/limit, path, truncated/chars_shown/chars_total) is accepted by POST /api/chats and round-trips through GET (the shape preserved, entry order preserved);
    • segments with 51 entries → 422; a thinking-segment text of 32 001 chars → 422; a segment with kind: "bogus" → 422; a record with a stray top-level key (e.g. "foo": 1) still → 422 (the extra="forbid" boundary intact);
    • a record WITHOUT segments (a pre-phase brain record, the full phase-113 shape) round-trips with the key ABSENT from the served JSON (never null — the byte-identity pin, the phase-50 contract);
    • the shared read path (the public read endpoint's messages) carries the segments of a saved record with them (the public shape, the phase-51 contract).
  • Coverage: >90% on this task's new/modified code (the schema code is exercised by the integration tests).

Completion Criteria

  • the three models + the field + the serializer rule land exactly as designed (a source check)
  • the new integration tests are green; the EXISTING test_chats_api.py tests stay green unchanged
  • uv run pytest tests/integration -q green; uv run ruff check . && uv run pyright clean
  • no change in app/rag/, no migration, no router change (a git diff --stat scoped check)