Skip to content

fix(tui): per-route vertical reserve + required panel props (#1069 round-2 follow-up) - #1071

Open
saravmajestic wants to merge 4 commits into
mainfrom
fix/AI-1067-welcome-panel-reserve-split
Open

fix(tui): per-route vertical reserve + required panel props (#1069 round-2 follow-up)#1071
saravmajestic wants to merge 4 commits into
mainfrom
fix/AI-1067-welcome-panel-reserve-split

Conversation

@saravmajestic

@saravmajestic saravmajestic commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Follow-up to the merged #1069, addressing @sahrizvi's round-2 review. Also passed a 2-model consensus panel (Claude + Gemini) — both SOUND.

What changed (from the round-2 comments)

MINOR — reserve fit only session, under-reserved home. Split into per-route constants that match each route's actual chrome:

  • HOME_VERTICAL_RESERVE = 10 (top spacer 2 + prompt wrapper 1 + prompt ~4 + footer ~3)
  • SESSION_VERTICAL_RESERVE = 7 (gaps 2 + paddingBottom 1 + prompt ~4; no top spacer/footer in that column)

Thresholds re-expressed in usable terms (MEDIUM_MIN_HEIGHT 13, FULL_MIN_HEIGHT 34) so full still engages at a ~44-row terminal on home, and the classic 80×24 clears the medium floor with margin (the cliff the review flagged now sits at 23, off the common size).

MINOR — optional props silently defaulted to raw terminal. Made availableWidth/availableHeight required and dropped the component's own useTerminalDimensions + fallback. A call site that forgets to scale is now a compile error, not a silent regression — this is the type-level guard for the "no render test" finding.

NITs

  • Medium copy wording clarified (one line on a wide terminal, two at the narrow end).
  • Height now uses the same available-space model as width, so short windows shrink instead of crowding the prompt.
  • Per-route note: full engages ~3 rows sooner on session (≥41) than home (≥44) — intentional (session has less chrome, so more usable height).

From the consensus panel (both models endorsed)

  • Centralized PANEL_HORIZONTAL_PADDING = 4 — was a bare -4 in the home call site + test helper (vertical reserves were already named constants).
  • Added the sidebar-toggle transition test: session(150,50,false) → full vs session(150,50,true) → medium — pins the exact home_logo slot always visible and cannot be dismissed in TUI #1067 case directly.

Effective terminal → variant (verified)

Terminal Route Variant
120×44 home full (the chosen "full at 44")
106×31 / 80×24 home medium
120×22 home compact (short-terminal height fix)
130×50 + sidebar session medium (blocker-1)
130×50 no sidebar session full

Verification

tsgo clean · 12 unit + 11 render/adjacent tests pass · oxlint 0/0 · prettier. Gate-isolation tests still kill mutants (deleting or <<=-flipping any gate fails a test).

Deferred (noted by both reviewers): a session-with-sidebar render test as the true call-site regression guard — happy to add as a follow-up.

🤖 Generated with Claude Code


Summary by cubic

Fixes welcome panel sizing so variants are chosen by available space per route, not raw terminal size. Updates reserves, thresholds, and shared route math to prevent “full” in the session sidebar and on short terminals.

  • Bug Fixes

    • Per-route reserves corrected: HOME_VERTICAL_RESERVE = 15, SESSION_VERTICAL_RESERVE = 9.
    • Thresholds retuned on available size: MEDIUM_MIN_HEIGHT = 8, FULL_MIN_HEIGHT = 30. Results: 80×24 → medium; home full at ≥45 rows; session full at ≥39; 120×22 → compact.
    • Centralized arithmetic: homeAvailable / sessionAvailable, shared PANEL_HORIZONTAL_PADDING = 4 and SIDEBAR_WIDTH = 42; session contentWidth derives from panelAvailable().width.
    • Session sizing follows the content column and narrows with the sidebar (including the overlay state); on 150×50, toggling the sidebar flips full → medium.
    • Added tests for reserves, overlay narrowing, boundaries and degenerate sizes, plus a render test that asserts each variant’s content.
  • Migration

    • WelcomePanel now requires availableWidth and availableHeight. Pass values from homeAvailable(...) / sessionAvailable(...).

Written for commit a5e1b50. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Improvements
    • Improved welcome panel sizing across Home and Session views.
    • Responsive layouts now account for each view’s available space, including surrounding interface elements and sidebars.
    • Updated height thresholds provide more accurate compact, medium, and full panel layouts.
    • Panel dimensions now adjust more reliably as available terminal space changes.
    • Improved panel content and connection prompts across different layout sizes.

…nd-2 review)

Follow-up to the merged #1069, addressing the round-2 review.

- Split the vertical reserve per route: HOME_VERTICAL_RESERVE (10) and
  SESSION_VERTICAL_RESERVE (7) accurately reflect each route's chrome (home has a
  top spacer + footer; session doesn't), instead of one constant that fit only
  session. Thresholds re-expressed in usable terms (MEDIUM_MIN_HEIGHT 13,
  FULL_MIN_HEIGHT 34) so `full` still engages at a ~44-row terminal on home and
  the classic 80x24 clears the medium floor with margin.
- Make WelcomePanel's availableWidth/availableHeight props REQUIRED (drop the
  raw-terminal fallback + the component's own useTerminalDimensions), so a call
  site that forgets to scale is a compile error, not a silent regression.
- Height now gets the same available-space treatment as width (terminal minus
  the route's prompt/footer chrome), so a short window shrinks the panel instead
  of crowding the prompt.
- Centralize PANEL_HORIZONTAL_PADDING (4) — was a bare `-4` in the home call site
  and test helper.

Tests: add the sidebar-toggle transition (session 150x50 full -> medium when the
sidebar opens — the exact #1067 case), the classic 80x24 on both routes, and keep
the isolated-gate / boundary / degenerate coverage. tsgo clean; oxlint 0/0.

Reviewed by a 2-model consensus panel (both SOUND); the PANEL_HORIZONTAL_PADDING
and sidebar-toggle test came out of that review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Welcome panel sizing now uses caller-provided available dimensions. Home and session routes apply separate vertical reserves, shared horizontal padding, and session sidebar width. A new workflow dispatches documentation promotion events after documentation changes reach main.

Changes

Welcome Panel Sizing

Layer / File(s) Summary
Panel sizing constants and component contract
packages/tui/src/component/welcome-panel-utils.ts, packages/tui/src/component/welcome-panel.tsx
The utilities define route-specific reserves, shared padding, sidebar width, and lower height thresholds. WelcomePanel now requires available width and height.
Route dimension calculations
packages/tui/src/routes/home.tsx, packages/tui/src/routes/session/index.tsx
The routes calculate panel dimensions through shared helpers. Session sizing accounts for sidebar visibility.
Route sizing validation
packages/tui/test/component/welcome-panel-utils.test.ts, packages/tui/test/component/welcome-panel.test.tsx
Tests cover route boundaries, sidebar transitions, negative dimensions, and full, medium, and compact rendered variants.

Documentation Sync Notification

Layer / File(s) Summary
Documentation promotion workflow
.github/workflows/notify-help-docs.yml
The workflow processes documentation pushes, excludes sync commits, creates a GitHub App token, and dispatches promotion metadata to the help-docs repository.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Home
  participant SessionRoute
  participant WelcomePanel
  participant welcomePanelVariant
  Home->>WelcomePanel: provide home available width and height
  SessionRoute->>WelcomePanel: provide session available width and height
  WelcomePanel->>welcomePanelVariant: calculate responsive variant
Loading

Possibly related PRs

Poem

A rabbit checks each panel’s space,
With route reserves in their place.
Width and height now guide the view,
While docs hop onward, fresh and new.
Tests guard each boundary too.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the main TUI changes: per-route sizing reserves and required panel props.
Description check ✅ Passed The description explains the changes and verification results in detail, but it omits several template sections such as the issue, type checklist, screenshots, and checklist.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/AI-1067-welcome-panel-reserve-split

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kilo-code-bot

kilo-code-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 Suggestion Found | Recommendation: Merge (suggestion optional)

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/tui/test/component/welcome-panel.test.tsx 78 resetSetupComplete() in withPanel's finally is redundant — renderPanel already resets onboarding at its start (line 27), so the post-test reset has no observable effect on any later test.
Files Reviewed (3 files)
  • packages/tui/src/routes/home.tsx
  • packages/tui/src/routes/session/index.tsx
  • packages/tui/test/component/welcome-panel.test.tsx

Fix these issues in Kilo Cloud

Previous Review Summary (commit 2762efb)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 2762efb)

Status: 1 Suggestion Found | Recommendation: Merge (suggestion optional)

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/tui/test/component/welcome-panel.test.tsx 78 resetSetupComplete() in finally is redundant — renderPanel already resets at its start (line 27), so the post-test reset has no observable effect on any later test.
Files Reviewed (4 files)
  • .github/workflows/notify-help-docs.yml
  • packages/tui/src/routes/home.tsx
  • packages/tui/src/routes/session/index.tsx
  • packages/tui/test/component/welcome-panel.test.tsx - 1 suggestion

Fix these issues in Kilo Cloud


Reviewed by glm-5.2 · Input: 47.7K · Output: 16.5K · Cached: 508.8K

Review guidance: REVIEW.md from base branch main

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/tui/src/component/welcome-panel-utils.ts">

<violation number="1" location="packages/tui/src/component/welcome-panel-utils.ts:37">
P3: The padding centralization is incomplete: PANEL_HORIZONTAL_PADDING is used at the home call site, but the session route's contentWidth still hardcodes the bare `- 4` rather than referencing the constant (the new doc comment even acknowledges session "applies the same 4 as part of its own content-column math"). If this constant ever changes, the session width math won't follow and drifts out of sync; consider having session/index.tsx import PANEL_HORIZONTAL_PADDING so both routes share one source of truth.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/tui/test/component/welcome-panel-utils.test.ts Outdated
// Columns the home slot spends on its own left/right padding (2 + 2); the caller
// subtracts this to get the panel's usable width. (Session's contentWidth applies
// the same 4 as part of its own content-column math.)
export const PANEL_HORIZONTAL_PADDING = 4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The padding centralization is incomplete: PANEL_HORIZONTAL_PADDING is used at the home call site, but the session route's contentWidth still hardcodes the bare - 4 rather than referencing the constant (the new doc comment even acknowledges session "applies the same 4 as part of its own content-column math"). If this constant ever changes, the session width math won't follow and drifts out of sync; consider having session/index.tsx import PANEL_HORIZONTAL_PADDING so both routes share one source of truth.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/tui/src/component/welcome-panel-utils.ts, line 37:

<comment>The padding centralization is incomplete: PANEL_HORIZONTAL_PADDING is used at the home call site, but the session route's contentWidth still hardcodes the bare `- 4` rather than referencing the constant (the new doc comment even acknowledges session "applies the same 4 as part of its own content-column math"). If this constant ever changes, the session width math won't follow and drifts out of sync; consider having session/index.tsx import PANEL_HORIZONTAL_PADDING so both routes share one source of truth.</comment>

<file context>
@@ -7,36 +7,46 @@
+// Columns the home slot spends on its own left/right padding (2 + 2); the caller
+// subtracts this to get the panel's usable width. (Session's contentWidth applies
+// the same 4 as part of its own content-column math.)
+export const PANEL_HORIZONTAL_PADDING = 4
 
-/** Minimum usable size for the medium panel; below either → compact (one line). */
</file context>

@sahrizvi

sahrizvi commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@saravmajestic CI is failing on this PR.

@sahrizvi sahrizvi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — round 3

The refactor is the right shape and structurally answers the round-2 comments: per-route reserves are the correct fix, and making the props required is the right way to close the silent-fallback path. But the central quantitative claim — the reserve arithmetic, and the "80×24 clears the medium floor with margin" conclusion drawn from it — doesn't survive checking against the rendered layout.

Major — both vertical reserves undercount the real chrome

welcome-panel-utils.ts:27-32 budgets prompt (~4). Counting the actual Prompt tree at rest (component/prompt/index.tsx:1479-1611):

  1. <box border={["top"]}> — 1 row (top rule, L1479)
  2. inner paddingTop={1} — 1 row (L1483)
  3. <textarea minHeight={1}> — 1 row (L1500)
  4. <box height={1} border={["top"]} /> — 1 row (separator, L1569)
  5. agent/model meta row — 1 row, always rendered (fallback={<box height={1} />}, L1572)
  6. status/hint row — 0 or 1. The wrapping <box> at L1611 is unconditional, but at idle the <Switch> falls through to <Match when={true}>{props.hint ?? <text />}</Match> (L1752), and neither route passes hint — so whether that empty row occupies a line is renderer-dependent.

The prompt is 5-6 rows at rest, not ~4 — 5 are certain, the 6th needs measuring.

Home is undercounted a second time: the reserve omits the home_bottom slot entirely (routes/home.tsx:128). Its built-in provider always returns a box with paddingTop={3} (feature-plugins/home/tips.tsx:34, outside the <Show when={props.show}>), and flexbox shrinks content, not padding — so those 3 rows are unconditional whether or not tips render.

home:    top spacer 2 + wrapper 1 + prompt 5-6 + home_bottom 3+ + footer 3 = 14-15+   (constant says 10)
session: gaps 2 + paddingBottom 1 + prompt 5-6                            = 8-9      (constant says 7)

Why this matters rather than being a comment nit. The round-2 comment this PR answers was "80×24 sits exactly on the medium floor", and the fix claims the cliff moved to 23. Against MEDIUM_MIN_HEIGHT = 13:

home reserve source home(80,24) available variant
10 as merged 14 medium, margin 1
11 5-row prompt, ignoring home_bottom (most conservative) 13 medium, margin 0 — exactly on the floor
12 6-row prompt, ignoring home_bottom 12 compact
14-15 prompt + home_bottom 9-10 compact

Every corrected reading destroys the claimed margin. The most conservative one puts 80×24 back exactly on the medium floor — verbatim the round-2 complaint — and the rest drop it to compact. The margin exists only at reserve 10, and expect(home(80,24)).toBe("medium") is pinned to that constant rather than to the rendered layout.

To be precise about the consequence: at 80×24 the unconnected medium panel plus a one-line tip fits almost exactly, so this isn't guaranteed overflow — it clips once a tip wraps to a second line. The defect is the absent margin, not certain breakage.

Suggested fix — derive the reserves from the real tree and re-tune the thresholds separately, so the two stop compensating for each other:

// Measure once against a real render and fix the value; 5 is the floor,
// 6 if the idle hint row occupies a line.
const PROMPT_REST_HEIGHT = 6

export const HOME_VERTICAL_RESERVE =
  2 +                    // top spacer (home.tsx:109)
  1 +                    // prompt wrapper paddingTop (home.tsx:123)
  PROMPT_REST_HEIGHT +
  3 +                    // home_bottom paddingTop (tips.tsx:34)
  3                      // footer (feature-plugins/home/footer.tsx:64-82)
// 15

export const SESSION_VERTICAL_RESERVE = 2 + 1 + PROMPT_REST_HEIGHT  // 9

Then choose MEDIUM_MIN_HEIGHT / FULL_MIN_HEIGHT against the panel's actual heights (medium ≈ 6-8 rows, full ≈ 13) plus whatever product breakpoints you want. If 80×24 must stay medium, that becomes an explicit threshold decision instead of a hidden one.

Minor

1. PANEL_HORIZONTAL_PADDING isn't actually centralizedwelcome-panel-utils.ts:34-37, routes/session/index.tsx:275

The constant's comment says session "applies the same 4 as part of its own content-column math", but contentWidth is still dimensions().width - (sidebarVisible() ? 42 : 0) - 4 — literal 4, literal 42, neither imported. Only home.tsx uses the constant. Either import it at session/index.tsx:275 (and add a SIDEBAR_WIDTH alongside), or drop the claim from the comment.

2. Sidebar width 42 is duplicated between production and the test helpertest/component/welcome-panel-utils.test.ts:22, routes/session/index.tsx:275

If the sidebar width changes, production and tests drift silently in the same direction that produced #1067.

3. Test helpers re-implement the call-site arithmetic instead of exercising ittest/component/welcome-panel-utils.test.ts:19-22

home() and session() duplicate exactly what home.tsx:116-117 and session/index.tsx:1194-1195 compute, so the "verified terminal → variant" table proves a property of the helpers, not of the routes — which is precisely why the Major finding above passed CI green. Required props catch a missing prop, never a wrong one, so they don't cover this. Cheap fix short of the deferred render test: export homeAvailable(w,h) / sessionAvailable(w,h,sidebar) from the utils module and call them from both the routes and the tests, so there's one definition of the arithmetic.

4. Worth confirming intent: session sizes against the sidebar even when it's an overlayroutes/session/index.tsx:267, 275, 1194

contentWidth subtracts 42 whenever sidebarVisible(), but the sidebar only consumes column width when wide() (width > 120). Below that it renders position="absolute" (session/index.tsx:1360-1371) and takes no layout width, so on a 100-col terminal with the sidebar opened contentWidth() reports 54 while the panel has ~96 columns of layout space, and the panel drops to compact.

Raising this as a question, not a defect — the overlay does visually cover roughly those columns behind a dimmed backdrop, so sizing to the obscured width may well be deliberate, and it can't produce the opposite (wrongly-full) error since full needs ≥156 raw cols where wide() is always true. Worth a line of comment recording which behaviour is intended, now that panel sizing routes through contentWidth.

Nit

5. The height thresholds read as fit requirements but aren'twelcome-panel-utils.ts:39-44

MEDIUM_MIN_HEIGHT = 13 / FULL_MIN_HEIGHT = 34 are documented as "minimum usable size for the … panel", but the medium panel is ~6-8 rows and the full panel ~13 (welcome-panel.tsx:88-138). They're breathing-room heuristics several times the actual need, and nothing records why 13 and 34 — relevant once the Major finding forces a re-tune.

What's done well

  • Splitting PANEL_VERTICAL_RESERVE per route is the right response to the round-2 finding, and the comments walk the JSX rather than asserting a number. The home footer (3 rows: paddingTop 1 + content 1 + paddingBottom 1) and the <box height={2}> spacer are both counted correctly, as is the session gap count — 3 in-flow children with gap={1} gives exactly 2 gaps, since Toast is position="absolute" and out of flow.
  • Making the props required and deleting the component's internal useTerminalDimensions removes the silent-fallback path that would have reintroduced #1067. Both call sites are in-repo, so there's no external breakage.
  • The added sidebar-toggle test (session(150,50,false) → full vs session(150,50,true) → medium) pins the actual reported bug rather than a proxy — the most valuable test in the file.
  • Gate isolation is real: TALL/WIDE derive from the constants, so each test fails if its own < is deleted or flipped to <=.
  • The reactivity note at welcome-panel.tsx:40-41 is correct — reading props.x inside createMemo tracks, so the variant recomputes on resize and sidebar toggle.

Missing tests

  • A render test of either call site — the acknowledged gap, and the one that would have caught the Major finding and Minor 3. Given this is the second round on the same bug, I'd treat it as the remaining open item rather than a follow-up.
  • A test tying the reserves to the real chrome (HOME_VERTICAL_RESERVE === SPACER + WRAPPER + PROMPT_REST_HEIGHT + HOME_BOTTOM + FOOTER from named constants), which would make the Major finding impossible to reintroduce silently.
  • The overlay-sidebar case from Minor 4: sidebar open on a ≤120-col terminal.
  • Negative available dimensions (an 80×5 terminal yields height -5 on home); only (0,0) and (1,1) are covered today.

One caveat on the numbers above: I read the layout tree rather than running it, so PROMPT_REST_HEIGHT is 5-or-6 pending one measurement against a real render. The conclusion holds either way — even the most conservative count puts 80×24 back on the floor.

saravmajestic and others added 2 commits August 7, 2026 08:22
…sts)

- correct per-route vertical reserves to the real chrome: HOME 10->15,
  SESSION 7->9, derived from PROMPT_REST_HEIGHT=6 plus the counted home_bottom
  slot and footer (the prompt is 5-6 rows at rest, not ~4)
- retune height thresholds honestly: MEDIUM_MIN_HEIGHT 13->8 (the real medium
  panel height, so 80x24 is a genuine medium fit, not a pinned margin),
  FULL_MIN_HEIGHT 34->30 (a documented product breakpoint, not a fit minimum)
- centralize the route arithmetic: export homeAvailable/sessionAvailable and call
  them from both the routes and the tests, so a wrong reserve/threshold fails a
  test instead of being mirrored in a test-only copy
- centralize PANEL_HORIZONTAL_PADDING and add SIDEBAR_WIDTH (drop the duplicated 42)
- keep the panel on session's contentWidth basis (narrows with the sidebar, incl.
  the dimmed overlay) so the panel stays aligned with the messages; documented
- tests: reserve-pinned-to-chrome, overlay narrowing, negative dimensions

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mounts the real WelcomePanel through the app context stack at fixed available
sizes and asserts each variant renders its distinct content (full: "What is
Altimate Code"; medium: the condensed line, no what-is section; compact: the
single connect-CTA line). Covers the .tsx Switch that the pure
welcomePanelVariant unit test can't. Deliberately not a full Home/Session route
render — that drags in Prompt's ~28-context dependency graph; the route wiring
is now a shared one-liner through homeAvailable/sessionAvailable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@saravmajestic

Copy link
Copy Markdown
Contributor Author

Thanks for the round-3 pass — the reserve-arithmetic catch was exactly right. All items addressed in 9d4c36cd79 (fixes) + 2762efb97c (render test).

Major — reserves undercount the real chrome

Fixed. Reserves are now derived from the counted tree, not estimated:

  • PROMPT_REST_HEIGHT = 6 (top rule 1 + paddingTop 1 + textarea 1 + separator 1 + meta row 1 + idle hint row 1).
  • HOME_VERTICAL_RESERVE = 2 + 1 + 6 + 3 + 3 = 15 (spacer + wrapper + prompt + home_bottom + footer).
  • SESSION_VERTICAL_RESERVE = 2 + 1 + 6 = 9.

And the thresholds are re-tuned separately so they stop compensating for each other:

  • MEDIUM_MIN_HEIGHT: 13 → 8 — the real medium-panel height, so 80×24 is a genuine medium fit (home available 24−15 = 9 ≥ 8), not a pinned margin.
  • FULL_MIN_HEIGHT: 34 → 30 — kept explicitly as a product breakpoint (the full box is ~13 rows; we require far more so the wordmark never dominates), documented as such rather than a fit minimum.

Minor

  1. PANEL_HORIZONTAL_PADDING not centralized / sidebar 42 duplicated (also cubic): PANEL_HORIZONTAL_PADDING and a new SIDEBAR_WIDTH = 42 are exported and used by session/index.tsx's contentWidth and by sessionAvailable; the test no longer references 42 at all.
  2. Test helpers re-implement the route math: exported homeAvailable(w,h) / sessionAvailable(w,h,sidebarVisible)both the routes and the tests now call these, so a wrong reserve/threshold fails a test instead of being mirrored in a private copy. Added a test pinning the reserves to the counted chrome.
  3. Session sizes against the overlay sidebar: documented as deliberate. The panel shares contentWidth's basis (narrows whenever the sidebar is open, including the dimmed full-area overlay) so the panel and the messages stay aligned and both restore to full width when it closes — sizing to the narrowed column, not the transient obscured width. Test + comment record this.

Nit

The height thresholds now read for what they are: MEDIUM_MIN_* a real fit requirement, FULL_MIN_* a product breakpoint (comments explain the ~13-row panel vs the larger required space).

Missing tests

  • Reserve-tied-to-chrome, overlay narrowing, negative dimensions — all added.
  • Render test — added, component-level (test/component/welcome-panel.test.tsx): mounts the real WelcomePanel through the app context stack and asserts each variant renders its distinct content (full "What is Altimate Code"; medium condensed line; compact CTA), covering the .tsx Switch the pure function can't.
  • Full Home/Session route render test — deferred, honestly. Home renders <Prompt>, whose dependency graph is ~28 contexts; mounting it is ~2× the heaviest existing test and brittle, with no precedent in the suite. Since the arithmetic is now shared between the routes and the tests (Minor 2), the remaining route surface is a one-line <WelcomePanel availableWidth={panelAvailable().width} …/>. Happy to build the full-route harness if you'd still like the end-to-end guard.

CI

The red "TypeScript" job is a pre-existing unrelated flake — the only failing test was config HttpApi > serves config with active provider model status (httpapi-config.test.ts, untouched by this PR); it passes locally (2/0). The fresh push should re-run it.

These changes were also cross-checked with a two-model consensus review (both APPROVE).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/tui/test/component/welcome-panel.test.tsx`:
- Around line 26-74: Update the tests using renderPanel so each test owns its
returned app and wraps execution in a try/finally block that destroys
app.renderer and resets the onboarding state. Remove the module-level current
variable and its afterEach cleanup, ensuring cleanup still runs when assertions
or rendering fail and no renderer can be overwritten across concurrent tests.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0367c3cc-e91a-4591-8794-553241bae06a

📥 Commits

Reviewing files that changed from the base of the PR and between 2eac798 and 2762efb.

📒 Files selected for processing (5)
  • packages/tui/src/component/welcome-panel-utils.ts
  • packages/tui/src/routes/home.tsx
  • packages/tui/src/routes/session/index.tsx
  • packages/tui/test/component/welcome-panel-utils.test.ts
  • packages/tui/test/component/welcome-panel.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/tui/test/component/welcome-panel-utils.test.ts

Comment thread packages/tui/test/component/welcome-panel.test.tsx Outdated
// terminals) so the panel stays aligned with the messages and both restore
// together when it closes. Height reserves the prompt row. Shared arithmetic in
// welcome-panel-utils so the tests exercise this exact math.
const panelAvailable = createMemo(() => sessionAvailable(dimensions().width, dimensions().height, sidebarVisible()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: panelAvailable().width duplicates the contentWidth() formula

The note above states panelAvailable shares contentWidth's content-column basis, and sessionAvailable(...).width computes the exact same value as the contentWidth memo (line 275): width - (sidebarVisible ? SIDEBAR_WIDTH : 0) - PANEL_HORIZONTAL_PADDING. Since sessionAvailable is already the unit-tested single source, consider deriving contentWidth from it (declare panelAvailable first, then const contentWidth = createMemo(() => panelAvailable().width)) so the width math has one definition and can't structurally drift.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/tui/test/component/welcome-panel.test.tsx">

<violation number="1" location="packages/tui/test/component/welcome-panel.test.tsx:70">
P3: The renderer created inside `renderPanel()` is only registered for cleanup after the function returns (via `current = app` in each caller). If rendering or capture throws before that assignment, the leaked app's renderer/event listeners survive — `afterEach` still points at the previous test's already-destroyed app. Make cleanup robust by assigning `current` inside `renderPanel()` (or using try/finally around `renderOnce`) so a failing render still destroys its resources.</violation>
</file>

<file name="packages/tui/src/component/welcome-panel-utils.ts">

<violation number="1" location="packages/tui/src/component/welcome-panel-utils.ts:97">
P2: On a narrow session with the sidebar toggled open, the welcome panel is sized as though 42 columns were removed even though the overlay leaves its layout box full width, so medium content can be laid out across the obscured area and appear truncated. The available-width calculation should distinguish the in-flow (`wide()`) sidebar from the absolute overlay, or the panel wrapper should be constrained to the narrowed width.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

sidebarVisible: boolean,
): { width: number; height: number } {
return {
width: terminalWidth - (sidebarVisible ? SIDEBAR_WIDTH : 0) - PANEL_HORIZONTAL_PADDING,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: On a narrow session with the sidebar toggled open, the welcome panel is sized as though 42 columns were removed even though the overlay leaves its layout box full width, so medium content can be laid out across the obscured area and appear truncated. The available-width calculation should distinguish the in-flow (wide()) sidebar from the absolute overlay, or the panel wrapper should be constrained to the narrowed width.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/tui/src/component/welcome-panel-utils.ts, line 97:

<comment>On a narrow session with the sidebar toggled open, the welcome panel is sized as though 42 columns were removed even though the overlay leaves its layout box full width, so medium content can be laid out across the obscured area and appear truncated. The available-width calculation should distinguish the in-flow (`wide()`) sidebar from the absolute overlay, or the panel wrapper should be constrained to the narrowed width.</comment>

<file context>
@@ -54,3 +69,32 @@ export function welcomePanelVariant(width: number, height: number): WelcomePanel
+  sidebarVisible: boolean,
+): { width: number; height: number } {
+  return {
+    width: terminalWidth - (sidebarVisible ? SIDEBAR_WIDTH : 0) - PANEL_HORIZONTAL_PADDING,
+    height: terminalHeight - SESSION_VERTICAL_RESERVE,
+  }
</file context>

return { app, frame }
}

let current: Awaited<ReturnType<typeof testRender>> | undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The renderer created inside renderPanel() is only registered for cleanup after the function returns (via current = app in each caller). If rendering or capture throws before that assignment, the leaked app's renderer/event listeners survive — afterEach still points at the previous test's already-destroyed app. Make cleanup robust by assigning current inside renderPanel() (or using try/finally around renderOnce) so a failing render still destroys its resources.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/tui/test/component/welcome-panel.test.tsx, line 70:

<comment>The renderer created inside `renderPanel()` is only registered for cleanup after the function returns (via `current = app` in each caller). If rendering or capture throws before that assignment, the leaked app's renderer/event listeners survive — `afterEach` still points at the previous test's already-destroyed app. Make cleanup robust by assigning `current` inside `renderPanel()` (or using try/finally around `renderOnce`) so a failing render still destroys its resources.</comment>

<file context>
@@ -0,0 +1,97 @@
+  return { app, frame }
+}
+
+let current: Awaited<ReturnType<typeof testRender>> | undefined
+afterEach(() => {
+  current?.renderer.destroy()
</file context>

Comment thread packages/tui/src/routes/session/index.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/notify-help-docs.yml:
- Line 25: Update the runs-on value used by the notify-help-docs workflow so
actionlint recognizes it: either register arc-runner-gke under runner-label in
actionlint.yaml if it is the intended self-hosted label, or replace it with an
existing configured runner label. Preserve the workflow’s intended runner
behavior.
- Around line 54-60: Update the create-github-app-token step identified by id
tok to request only repository contents write access, using the action’s
permissions configuration while preserving the existing app ID, private key,
owner, and repository settings.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7406cdbc-a2f9-42ea-960a-502786f6f7bb

📥 Commits

Reviewing files that changed from the base of the PR and between 2762efb and 0757b82.

📒 Files selected for processing (4)
  • .github/workflows/notify-help-docs.yml
  • packages/tui/src/routes/home.tsx
  • packages/tui/src/routes/session/index.tsx
  • packages/tui/test/component/welcome-panel.test.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/tui/test/component/welcome-panel.test.tsx
  • packages/tui/src/routes/home.tsx
  • packages/tui/src/routes/session/index.tsx

Comment thread .github/workflows/notify-help-docs.yml Outdated
jobs:
notify:
if: ${{ !contains(github.event.head_commit.message, '[docs-sync]') }}
runs-on: arc-runner-gke

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -HI -t f 'actionlint\.ya?ml' . -x sh -c '
  echo "=== $1 ==="
  cat "$1"
' sh

actionlint .github/workflows/notify-help-docs.yml

Repository: AltimateAI/altimate-code

Length of output: 1056


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== workflow runs-on lines ==="
rg -n "runs-on:" .github/workflows || true

echo "=== actionlint config files ==="
fd -HI -t f 'actionlint\.ya?ml' . || true

echo "=== actionlint versions/config references ==="
rg -n "runner-label|arc-runner-gke|actionlint" -S .github actions . || true

Repository: AltimateAI/altimate-code

Length of output: 1848


Register arc-runner-gke with actionlint.

arc-runner-gke is unknown to actionlint and causes notify-help-docs.yml to fail linting. Add it to actionlint.yaml under runner-label if this is a self-hosted runner label, or switch this workflow to an already configured runner label.

🧰 Tools
🪛 actionlint (1.7.12)

[error] 25-25: label "arc-runner-gke" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2025-vs2026", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xlarge", "macos-latest-large", "macos-26-intel", "macos-26-xlarge", "macos-26-large", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xlarge", "macos-14-large", "macos-14", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file

(runner-label)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/notify-help-docs.yml at line 25, Update the runs-on value
used by the notify-help-docs workflow so actionlint recognizes it: either
register arc-runner-gke under runner-label in actionlint.yaml if it is the
intended self-hosted label, or replace it with an existing configured runner
label. Preserve the workflow’s intended runner behavior.

Source: Linters/SAST tools

Comment thread .github/workflows/notify-help-docs.yml Outdated
Comment on lines +54 to +60
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
id: tok
with:
app-id: ${{ vars.DOCS_SYNC_APP_ID }}
private-key: ${{ secrets.DOCS_SYNC_APP_PRIVATE_KEY }}
owner: AltimateAI
repositories: help-docs

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '1,140p' .github/workflows/notify-help-docs.yml

echo
echo "== action metadata/usage references =="
for f in .github/workflows/notify-help-docs.yml; do
  echo "-- $f"
  rg -n "create-github-app-token|permission-contents|contents:|write|permissions:" "$f" || true
done

echo
echo "== git diff stat/name-status =="
git diff --stat || true
git status --short || true

Repository: AltimateAI/altimate-code

Length of output: 2944


🌐 Web query:

actions/create-github-app-token permission-contents default behavior repository permissions v1

💡 Result:

In the actions/create-github-app-token GitHub Action, the permission-contents input allows you to specify the access level for repository contents (such as commits and branches) granted to the generated installation access token [1][2][3]. The default behavior for the permission-contents input (and all other permission-* inputs) when left unspecified is that the generated token inherits all permissions granted to the GitHub App installation [1][4][5]. Key points regarding this behavior include: 1. Inherited Scope: If you do not explicitly define a permission-contents value, the resulting token will possess all permissions that were configured for the app installation on the repository or account [1][6][7]. 2. Best Practices: It is highly recommended to explicitly define the permissions required for your specific use case (e.g., setting permission-contents: write) to follow the principle of least privilege [1][8][3]. 3. Constraints: You cannot grant the token permissions that the GitHub App installation itself does not already possess [9][8][6]. Attempting to request a permission via the action that the installation lacks will result in an error [8][6]. 4. Functional Scope: While earlier versions of the action had reports of the permission-* inputs not correctly restricting the token in some scenarios [10][9], modern versions correctly use these inputs to restrict the token scope when interacting with the GitHub API's installation access token endpoint [9]. If you are using version 1 of the action, be aware that while the fundamental logic of requesting a token remains consistent, newer versions (v2+) include improved handling and enforcement of these granular permission inputs [10].

Citations:


🌐 Web query:

actions/create-github-app-token permission-contents default unspecified action.yml

💡 Result:

In the actions/create-github-app-token action, the permission-contents input (and other similar permission-* inputs) does not have a default value specified in the action.yml file [1][2]. When these permission-* inputs are left unspecified by the user in their workflow YAML: 1. The action does not apply any specific restrictions or overrides to the generated token's scopes [3][4]. 2. By default, the generated GitHub App installation token inherits all permissions currently granted to the GitHub App's installation [5][6][7]. The permission-* inputs are provided as optional parameters to allow users to implement the principle of least privilege by explicitly restricting the token's permissions to only those necessary for a specific job [5][3]. If you choose to use these inputs, you must specify them (e.g., permission-contents: read or permission-contents: write) [1][3]. If no such inputs are provided, the action defaults to the full set of permissions defined on the app installation itself [6][4]. Note that if you explicitly set any permission-* input, you are defining the exact scope for the generated token; if the specified permission is not granted to the app installation, the request will fail [5][6][4].

Citations:


🌐 Web query:

"actions/create-github-app-token" "In this case" "permission" "default to" "installation"

💡 Result:

When using the actions/create-github-app-token action, the token's permissions are determined by how you configure the action's inputs [1][2]. By default, if you do not specify any permission-related inputs, the generated token inherits all of the permissions granted to the GitHub App's installation [1][3][4]. However, if you explicitly provide any permission-related inputs (e.g., permission-contents: read), the token's permissions are restricted to only those explicitly defined [4]. In this scenario, the token does not inherit the full set of installation permissions, but is instead scoped specifically to the inputs provided [4]. Key points regarding permissions: - Default Behavior: Inherits all installation permissions [1][3]. - Explicit Configuration: Providing any permission-specific input (prefixed with permission-) overrides the default, and the token is scoped only to those provided permissions [4]. - Requirement: The permissions you request (either by default or explicitly) must be granted to the GitHub App installation itself; otherwise, the action will fail with an error [1][3]. - Minimum Requirement: The GitHub App generally requires at least "Repository Metadata: read" permissions to successfully locate the installation and mint the token [5].

Citations:


Restrict the GitHub App token permissions.

This token inherits every permission granted to the App installation on AltimateAI/help-docs. The dispatch needs only repository contents write access. Mint the app token with only that permission to reduce the impact of token exposure.

Proposed change
       - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
         id: tok
         with:
           app-id: ${{ vars.DOCS_SYNC_APP_ID }}
           private-key: ${{ secrets.DOCS_SYNC_APP_PRIVATE_KEY }}
           owner: AltimateAI
           repositories: help-docs
+          permission-contents: write
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
id: tok
with:
app-id: ${{ vars.DOCS_SYNC_APP_ID }}
private-key: ${{ secrets.DOCS_SYNC_APP_PRIVATE_KEY }}
owner: AltimateAI
repositories: help-docs
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
id: tok
with:
app-id: ${{ vars.DOCS_SYNC_APP_ID }}
private-key: ${{ secrets.DOCS_SYNC_APP_PRIVATE_KEY }}
owner: AltimateAI
repositories: help-docs
permission-contents: write
🧰 Tools
🪛 zizmor (1.29.0)

[error] 54-54: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions

(github-app)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/notify-help-docs.yml around lines 54 - 60, Update the
create-github-app-token step identified by id tok to request only repository
contents write access, using the action’s permissions configuration while
preserving the existing app ID, private key, owner, and repository settings.

Source: Linters/SAST tools

…t renderer isolation; derive contentWidth from panelAvailable

- Marker Guard: home.tsx panelAvailable and session contentWidth/panelAvailable
  are edits in upstream-shared files, now wrapped in altimate_change markers
- derive contentWidth from panelAvailable().width — one definition, can't drift
- render test: own the renderer per test (destroy + reset in finally) instead of
  a shared module-level var, safe under parallel bun test

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
check(frame)
} finally {
app.renderer.destroy()
resetSetupComplete()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: resetSetupComplete() in finally is redundant

renderPanel already calls resetSetupComplete() at its start (line 27) before every render, so each test begins with onboarding state deterministically reset regardless of what the previous test left behind. The finally reset here has no observable effect on any later test — app.renderer.destroy() is the only load-bearing cleanup in this block.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@saravmajestic
saravmajestic force-pushed the fix/AI-1067-welcome-panel-reserve-split branch from 0757b82 to a5e1b50 Compare August 7, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants