Skip to content

Reduce permission-boundary spacing fix to a render-layer derivation#197

Draft
gnguralnick wants to merge 10 commits into
mainfrom
mngr/simplify-permission-boundary
Draft

Reduce permission-boundary spacing fix to a render-layer derivation#197
gnguralnick wants to merge 10 commits into
mainfrom
mngr/simplify-permission-boundary

Conversation

@gnguralnick

Copy link
Copy Markdown
Contributor

Summary

Fixes the empty ~46px vertical gap at a permission grant/deny boundary in the chat progress view (apps/system_interface), in its reduced, render-layer-derived form — no new SectionView flag threaded through turn-grouping.

When the user grants/denies a permission, the app injects a hidden notification whose verdict folds onto the permission card and which opens a fresh progress block carrying the open step over (intended turn-boundary carryover). Because that boundary has no user bubble, two progress blocks' turn-margins stacked into an empty void. This collapses it to a single clean ~18px turn break.

Approach

  • turn-grouping.ts: unchanged from base. No opened_by_permission_resolution flag, none of the five threading points the earlier heavy form added.
  • conversation-rows.ts: one shared helper, sectionRendersUserBubble(section), drives both the bubble-rendering decision and the flush decision, so they can't drift. When a section emits a progress block and the next section renders no user bubble, it passes flushBottomMargin to the ProgressBlock.
  • ProgressBlock.ts: flushBottomMargin?: boolean -> adds progress-block--flush-bottom.
  • style.css: .progress-block--flush-bottom { margin-bottom: 0; }. No :has(...) rule.

Verification

  • Real Playwright build: boundary gap ~46px before -> exactly 18px after; the card-ending block gains the flush class; carried-over step preserved in both blocks; card and prose unchanged.
  • e2e regression test (test_permission_resolution_boundary_has_no_empty_void) reproduces the real chat shape (request card + separate trailing prose + hidden grant + carryover) and asserts the rendered DOM and the ~18px boundary; confirmed fail-before / pass-after.
  • Unit tests for the shared helper and the flush decision (incl. the genuinely-final unresolved permission keeping its margin).

Tests

  • Frontend: lint clean, 366 unit tests pass, clean build.
  • Backend: cd apps/system_interface && uv run pytest -> 479 passed, 10 skipped, coverage 87.26%. One pre-existing, unrelated failure (agent_manager_test.py::test_start_observe_spawns_long_lived_subprocess, a sandbox tmux resource-guard misfire in a file this branch does not touch; fails identically on the clean base).

The branch incorporates origin/main's uvicorn->Flask server migration (merged in); the e2e harness was adapted to make_threaded_server accordingly.

Generated with Claude Code

runtime-backup and others added 10 commits June 22, 2026 15:16
Apply only the CSS fix to the live (FastAPI) workspace, scoped to just the
spacing change the user approved. The regression test on
mngr/fix-permission-spacing targets the new Flask test harness on origin/main
and rides along in the upstream PR rather than the live build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g scenario

A UI fix delegated to a worker validated against a fixture it invented rather
than the real conversation that motivated the change, producing a CSS selector
that never matched the real DOM plus a passing-but-meaningless test. Three
process gaps closed:

- Delegate: the lead must capture the real motivating DOM/measurements and pass
  them in the brief as the worker's reproduction fixture and acceptance target.
- Preview: the preview opens on the worker's empty agent, not the motivating
  conversation; the lead must open the real agent, re-measure, and confirm the
  number actually moved before asking the user.
- Worker sub-skill: the fixture must reproduce the lead-provided DOM shape (not
  a guess), assert that shape, and the regression test must fail-before/pass-after.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The teardown section ran `unpreview` (which kills the preview servers and
deregisters their services) but never closed the `si-preview` dockview tab
opened earlier via `layout.py open`. After teardown the user was left with a
stale tab pointing at a now-deregistered service. Add an explicit
`layout.py close si-preview` step, framed as a separate concern (layout panel
vs. service lifecycle), to run on every teardown path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit ff7d110)
…proxy

A hidden permission grant/deny opens a fresh progress block carrying the
open step over (the intended turn-boundary carryover). Because that
boundary has no user bubble, the card-ending block's turn-bottom margin
and the resumption block's top margin stacked into a ~46px empty void in
the flex-column message list.

Previously this was patched with a CSS `:has(.pv-permission:last-child)`
selector, a fragile proxy: it fires for a permission that is genuinely the
last thing in an unresolved turn, and misses a resolution boundary if work
followed the card before the grant.

Mark the boundary structurally instead: turn-grouping sets a new
`SectionView.opened_by_permission_resolution` flag on exactly the section a
resolution opens. conversation-rows reads the next section's flag and tells
the preceding ProgressBlock to drop its turn-bottom margin
(`progress-block--flush-bottom`), so the seam is just the resumption
block's normal ~18px top margin. The card, prose, and carried-over step are
unchanged.

Removes the old CSS rules and adds tests: a unit test that the resolution
section is flagged (and normal boundaries are not), and an e2e test that
reproduces the real shape (request card + separate trailing prose + hidden
grant + carryover) and asserts the boundary gap is a single ~18px break
(fails at 46px before this change).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 906fb54)
The boundary spacing collapse no longer threads a new
SectionView.opened_by_permission_resolution flag through five places in
turn-grouping. That boolean is fully derivable at the render layer: a
section renders a user bubble iff it has a non-hidden user_event, and the
only bubble-less *boundary* section in practice is the one a permission
grant/deny opens (user_event: null).

conversation-rows now factors that decision into a single shared helper,
sectionRendersUserBubble, used BOTH for the existing bubble-rendering
decision AND to decide the flush, so the two can never drift. When a
section emits a progress block and the NEXT section renders no user
bubble, it passes flushBottomMargin to the ProgressBlock, which adds
progress-block--flush-bottom (margin-bottom: 0) -- exactly as before at
the CSS/DOM layer. turn-grouping is reverted to its base form (no flag).

Tests: unit tests for the shared helper and the flush decision (a section
followed by a bubble-less section flushes; followed by a normal
user-bubble section, or as the last section, does not). The e2e test that
reproduces the real shape (request card + separate trailing prose +
hidden grant + carryover) is unchanged and still asserts the rendered DOM
(flush class present) and the ~18px boundary, failing at the ~46px void
before the fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the threaded SectionView.opened_by_permission_resolution flag (removed
from turn-grouping and its 5 threading points) with a derivation in
conversation-rows: a shared sectionRendersUserBubble() helper drives both the
bubble-rendering decision and the flush decision, so the preceding block's
turn-bottom margin is dropped whenever the next section renders no user bubble.
Identical 18px boundary, no new data-model state, generalizes to any bubble-less
boundary. (Live branch drops the Python e2e test; it lives in the upstream PR.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion-boundary

# Conflicts:
#	apps/system_interface/imbue/system_interface/test_e2e.py
# Conflicts:
#	apps/system_interface/imbue/system_interface/test_e2e.py
…ul path

Clarify in conversation-rows that the oversized void is specifically a
progress-block-to-progress-block gap (two stacked turn-margins), so the
flush is only applied on the stepful branch. A no-steps section's tail
renders as inline .message-style rows that carry their own smaller
spacing, so no oversized void arises there. Comment-only; no behavior
change. Addresses a point raised in architecture review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant