Repository navigation
feat(ui): add BrowserPanel chrome visual-smoke fixture + source contract (#819) - #838
Merged
Merged
Conversation
…act (#819) BrowserPanel renderer chrome had no deterministic coverage, so layout/ a11y regressions (e.g. the #818 narrow-layout defect) could only be caught by manual real-window smoke. Add two deterministic layers: - VisualSmokeState.liveBrowserSessionIds + applyVisualSmokeFixture wiring + a `browser-empty` scenario that seeds the active turn session as a live browser session so BrowserPanel mounts. With no native WebContentsView in visual-smoke mode, browser.getState resolves null → EMPTY_STATE → the empty-state chrome (toolbar all-nav-disabled + <Empty> strip), exactly the #818 defect surface. Reuses the always-seeded turn session; no browser-specific on-disk seed. - browser-panel-chrome source contract (symmetric to artifact-pane-layout.test.ts) locking the 5 declarative chrome invariants that don't need a screenshot: back/forward :disabled track canGoBack/canGoForward; reload/stop icon + aria-label + onClick swap with loading; address bar snaps to live URL on blur when not editing; empty state present when !hasPage. The wiring IS the rendered DOM for these declarative bindings, so a source regex is a reliable proxy without a render harness (the repo has no jsdom/testing-library; BrowserPanel is IPC-driven). Loaded/loading/nav chrome states are intentionally not screenshot- fixture'd: their layout is identical to empty (#818 is a layout defect, not a state defect), loading/nav are transient, and their state invariants are locked by the source contract. Seeding BrowserState for a loaded screenshot would need a production test- seam (main-side visual-smoke browser:state hook or BrowserPanel visual-smoke read) for no layout-regression value. 8 baseline PNGs (1280/990 × light/dark × motion/reduced) generated in apps/desktop/tests/screenshots/browser-empty/ (gitignored generated captures) as a stable-subset candidate, pending visual review + promotion to screenshots-baseline/ via a separate baseline commit (the established pattern — baselines are committed separately from fixture code).
Astro-Han
force-pushed
the
feat/819-browser-chrome-fixture
branch
from
July 12, 2026 16:25
84d7e78 to
1192a04
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
browser-emptyvisual-smoke fixture scenario that mountsBrowserPanel(via a newVisualSmokeState.liveBrowserSessionIdsfield wired intoapplyVisualSmokeFixture) so the renderer chrome can be screenshot-captured in the empty state — the fix(ui): give BrowserPanel a narrow bottom-sheet layout (#546) #818 narrow-layout defect surface.browser-panel-chromesource contract (symmetric toartifact-pane-layout.test.ts) locking the 5 declarative chrome invariants that don't need a screenshot: back/forward:disabledtrackcanGoBack/canGoForward; reload/stop icon + aria-label + onClick swap withloading; address bar snaps to live URL on blur when not editing; empty state present when!hasPage.Why
BrowserPanel(apps/desktop/src/renderer/browser-panel.tsx) is the renderer chrome over a native ElectronWebContentsView; the native page can't be captured in a deterministic fixture, but the chrome is renderer DOM. The chrome had no deterministic visual or DOM/computed-style coverage, so layout/a11y regressions (e.g. the narrow-layout defect fixed in #818) could only be caught by manual real-window smoke.Closes #819.
Scope
BrowserStatefor a loaded screenshot would need a production test-seam (a main-side visual-smokebrowser:stateemit hook, or a visual-smoke read insideBrowserPanel) for no layout-regression value — rejected on first-principles/Occam's-razor grounds.BrowserPanelis IPC-driven viawindow.maka.browser. The 5 invariants are declarative (disabled={expr},cond ? <X/> : <Y/>,onBlur={...},!hasPage && <Empty>) — the wiring IS the rendered DOM, so a source regex is a reliable proxy without a render harness + heavy mocking. This matches the cited symmetry (artifact-pane-layout.test.ts).liveBrowserSessionIdsis a natural seam. It's the exact state the renderer already uses to gateBrowserPanelmounting (activeId && liveBrowserSessionIds.includes(activeId)); adding it toVisualSmokeStateextends the existing fixture contract, no production behavior change. Real users never receive a visual smoke state.Verification
typecheck✓ (main + renderer + storybook)test✓ — 2363/2363 (was 2354; +2browser-emptyfixture tests, +5browser-panel-chromecontract tests)browser-panel-chrome✓ 5/5 (back/forward disabled, reload/stop swap, address blur-snap, empty-state)visual-smoke-fixture✓ —browser-emptyscenario resolves,activeSessionId+liveBrowserSessionIdsseeded correctly, turn session on disk (reused, no browser-specific seed)apps/desktop/tests/screenshots/browser-empty/(gitignored generated captures):1280/990 × light/dark × motion/reduced. Dimensions verified viasips(1280→2560×1640, 990→1980×1640 Retina; sizes 249KB light / 729KB-1MB dark, non-blank). motion == reduced-motion byte-for-byte (empty chrome has no animation).browser-panel-layoutCSS contract (fix(ui): give BrowserPanel a narrow bottom-sheet layout (#546) #818/fix(ui): reserve chat/composer height when both ArtifactPane and BrowserPanel are open at narrow widths #824, already merged) + the empty-state fixture; the 5 behavioral invariants are locked by the source contract. The generated PNGs are a stable-subset candidate pending visual review + promotion toscreenshots-baseline/via a separate baseline commit (the established pattern — baselines are committed separately from fixture code, e.g.e1d1344e,12ad19aa;--update-baselineis designed for a full--allre-capture, not single-scenario promotion).Impact
None on users — visual-smoke mode only; real users never receive a visual smoke state, so
liveBrowserSessionIdsnever drives productionbrowser:livewiring. Dev/test impact: a new fixture scenario + a new source contract test; the capture script'sALL_SCENARIOSincludesbrowser-emptyfor baseline runs.Reviewer notes
browser-emptyscenario reuses the always-seeded turn session (TURN_SESSION_ID), so it adds no on-disk seed branch —getVisualSmokeStatereturnsliveBrowserSessionIds: [TURN_SESSION_ID]+activeSessionId: TURN_SESSION_ID;seedVisualSmokeFixturealready writes the turn session unconditionally.Ready for review
Verificationexplains why it is not applicable