Skip to content

fix(layout): show no-webcam for camera-less projects - #310

Open
arhxam wants to merge 2 commits into
getopenscreen:mainfrom
arhxam:codex/camera-less-layout-state
Open

fix(layout): show no-webcam for camera-less projects#310
arhxam wants to merge 2 commits into
getopenscreen:mainfrom
arhxam:codex/camera-less-layout-state

Conversation

@arhxam

@arhxam arhxam commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • derive the layout pane's displayed preset from actual camera availability
  • show the disabled No Webcam state for camera-less timelines, matching preview/export behavior
  • hide PiP-only shape, size, and reactive-zoom controls when no camera exists
  • preserve the stored camera layout preference so it returns unchanged when a camera-backed clip is present

This completes the remaining UI portion of #248 after the renderer/export correction merged in #261.

Related issue

Fixes #248

Type of change

  • Bug fix
  • Feature
  • Enhancement
  • Documentation
  • Refactor / maintenance
  • Performance
  • Security

Release impact

  • Patch
  • Minor
  • Major / breaking change
  • No release note needed

Desktop impact

  • Windows
  • macOS
  • Linux
  • Installer / packaging
  • Not platform-specific

Screenshots / video

Not included; DOM regression coverage verifies both camera-less and camera-present states.

Testing

  • Started with a failing camera-less selector regression test (picture-in-picture was displayed)
  • npx vitest --run src/components/ai-edition/RightPanes.layout.test.tsx src/components/ai-edition/RightPanes.i18n.test.tsx (7 passed)
  • npm run test (1,679 passed, 1 skipped across 141 files)
  • npx tsc --noEmit
  • npx tsc -p tsconfig.test.json --noEmit
  • npx biome check src/components/ai-edition/RightPanes.tsx src/components/ai-edition/RightPanes.layout.test.tsx
  • npm run docs:check
  • npm run i18n:check
  • npm run build-vite

Authored with Codex assistance and manually verified against the remaining #248 UI behavior.

Summary by CodeRabbit

  • Bug Fixes
    • Improved layout behavior when no webcam footage is available.
    • Webcam-dependent controls are now disabled and the interface displays a “no webcam” layout.
    • Saved webcam preferences are preserved and automatically restored when camera footage is available.
    • Camera-specific controls and reactive zoom now correctly reflect the available footage.

@arhxam
arhxam requested a review from EtienneLescot as a code owner August 8, 2026 18:30
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@arhxam, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b0812d0-c6e5-4a38-8716-cbcb61c3e4d9

📥 Commits

Reviewing files that changed from the base of the PR and between ff2f1c6 and 7fad1f1.

📒 Files selected for processing (1)
  • src/components/ai-edition/RightPanes.layout.test.tsx
📝 Walkthrough

Walkthrough

LayoutPane now uses a no-webcam effective layout when no camera clips exist. Tests cover camera-present and camera-absent projects while confirming that the saved preset remains unchanged.

Changes

Camera-aware layout handling

Layer / File(s) Summary
Effective layout and test coverage
src/components/ai-edition/RightPanes.tsx, src/components/ai-edition/RightPanes.layout.test.tsx
LayoutPane derives no-webcam when no camera track exists, updates related control visibility, preserves the saved preset, and adds tests for both camera states.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: etiennelescot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary change: showing the No Webcam layout for projects without camera tracks.
Description check ✅ Passed The description includes the required summary, issue reference, change type, release impact, platform impact, screenshots note, and detailed testing.
Linked Issues check ✅ Passed The changes satisfy issue #248 by showing No Webcam for camera-less timelines and preventing the incorrect Picture in Picture state.
Out of Scope Changes check ✅ Passed The implementation and regression tests directly support issue #248 and the stated layout-pane objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot 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.

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 `@src/components/ai-edition/RightPanes.layout.test.tsx`:
- Around line 67-88: The “LayoutPane camera availability” tests need to cover
all camera-dependent UI behavior when no camera is available. Extend the
no-camera case to assert reactive zoom and webcam size are hidden, and verify
the remaining camera-dependent controls are disabled; keep the existing
saved-preset assertions unchanged and add coverage in this test file for each
new branch.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f87047f2-ff91-4d9d-9804-be27ca140d0b

📥 Commits

Reviewing files that changed from the base of the PR and between 4e7a85b and ff2f1c6.

📒 Files selected for processing (2)
  • src/components/ai-edition/RightPanes.layout.test.tsx
  • src/components/ai-edition/RightPanes.tsx

Comment thread src/components/ai-edition/RightPanes.layout.test.tsx

@EtienneLescot EtienneLescot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at the PR head (7fad1f1c). Verdict: ship with nits. Nice, small, correctly-placed change.

Things I checked that came back clean:

  • It's live code. App.tsx:19AiEditionShellNewEditorShell.tsx:46,1207 mounts FloatingInspectorv4/FloatingInspector.tsx:47 imports LayoutPane from ../RightPanes and renders it at :1057. (The v4 shell reuses these panes; the dead-code note applies to RightPanelStack/Bottombar/Titlebar, not here.)
  • It reuses the consolidated camera helper rather than adding a fourth way to ask "is there a webcam" — hasAnyClipWithCamera from lib/ai-edition/timeline/camera.ts, no new detection code.
  • It reuses the existing "no-webcam" preset instead of inventing an empty-state component, and derives isPip/supportsReactiveZoom/value from one effectiveLayoutPreset const so the three consumers can't drift.
  • Test verified by reverting: at PR head 2/2 pass; with RightPanes.tsx reverted to main and the new test kept, it fails exactly on the fix's assertion (expect(element).toHaveValue(no-webcam) … Received: picture-in-picture). Genuine regression test, asserting on rendered DOM.
  • // @vitest-environment jsdom correctly on line 1. Both typecheck configs, biome, and the tests all pass locally.

1. The same defect is still live on the Full Camera control

Not introduced here, and I'd file it as a follow-up rather than expand this PR — but it's the sibling of exactly what you just fixed, so worth naming while the context is fresh.

V4Timeline.tsx:1369 and NewEditorShell.tsx:980 — the "Add Full Camera" toolbar button and the C shortcut create a camera-fullscreen region on a camera-less project with no gate at all, while the AI-agent path refuses the identical action verbatim (agent-tools.ts:208-226: "this recording has no webcam").

Open a project whose only asset has cameraTrack: null, press C. A Full Camera pill appears in the lane and is persisted into legacyEditor.cameraFullscreenRegions, but PreviewCanvas.effectiveLayout short-circuits (if (!layout?.webcamRect || cameraFullscreenProgress <= 0) return layout) and the export does the same — the region renders nothing, forever, with no feedback.

The right depth is inside useTimeline.addCameraFullscreen (useTimeline.ts:348), so both entry points and any future one route through it:

if (!hasAnyClipWithCamera(document.assets, document.timeline.clips)) return;

…plus disabled on the toolbar button. I'll open an issue for this.

2. Two predicates now disagree about what the user sees

camera.ts holds both answers to "does this have a camera":

  • hasAnyClipWithCamera (:23) → cameraTrack != null
  • assetCameraSource (:45-52) → visible && sourcePath, declared "THE answer" by the SSOT refactor

This PR promotes the looser one from "should the controls be enabled" to "what value does the control display", so for an asset with cameraTrack: { sourcePath: "/cam.webm", visible: false } the select would show and enable "Picture-in-Picture" while sceneDescription.ts:593, PreviewCanvas.tsx:230 and ExportDialog.tsx:66 all resolve no-webcam — the exact defect this PR fixes, unfixed for hidden cameras.

Latent, not shipping: nothing writes visible: false today (schema defaults true; migrate.ts:112 and projectStore.ts:164 both write true; no UI toggle exists), and camera.test.ts:83 pins the current behaviour on purpose. So no code change needed — but one line in the new comment saying the panel deliberately keeps the saved preset for a hidden-but-present camera would stop the next reader "fixing" it in the wrong direction.

3. Third literal copy of the same expression

RightPanes.tsx:1510hasCamera ? settings.webcamLayoutPreset : "no-webcam" now exists in three places (the others are PreviewCanvas.tsx:230 and sceneDescription.ts:593). lib/compositeLayout.ts already hosts exactly this shape next door — resolveWebcamReactiveZoom(preset, enabled) at :140, "the stored setting, gated by the active preset", right beside supportsWebcamReactiveZoom.

Adding a second preset to the "renders nothing without a camera" set currently means editing three files:

export function resolveWebcamLayoutPreset(preset: WebcamLayoutPreset, hasCamera: boolean) {
  return hasCamera ? preset : "no-webcam";
}

4. The select has no accessible name

RightPanes.tsx:1528-1531<label>{ts("layout.title")}</label> is a sibling with no htmlFor, and the select has no id/aria-label. A screen reader announces "combobox, No Webcam, dimmed" with nothing saying what it controls — and this PR makes that announced value the entire point of the change.

The tell is in the new test: getByRole("combobox") with no name filter only works because the control is nameless. Adding htmlFor/id and tightening the test to getByRole("combobox", { name: "Layout" }) fixes both.

Nits

  • RightPanes.tsx:1505-1506 — the comment ("make the disabled control describe what the preview/export actually render right now") is only true for uniformly camera-less projects. Preview and scene resolve per-clip, so in a mixed project hasAnyCamera is true and the panel shows PiP while the playhead sits over a camera-less clip. Suggest "…describe whether this project has any camera at all".
  • A user whose saved preference is Picture-in-Picture now reads "No Webcam" in a dead control with nothing telling them the setting survived. The test asserts the preservation; the UI never communicates it. A title on the disabled select, or a camera-less variant of ts("layout.help"), would close that.
  • RightPanes.tsx:1507-1509hasAnyClipWithCamera is an unmemoized clips.some(… assets.find(…)), O(clips × assets), on every LayoutPane render; the pane subscribes to the whole document, and setLive during a slider drag replaces the document every frame. Pre-existing (identical on main), but the PR rewrites these exact lines, so useMemo(…, [document]) is a one-line win while you're here.
  • RightPanes.layout.test.tsx:41,56,70 — locals named document shadow the jsdom global inside a jsdom test file. Harmless now; a document.querySelector(...) added later would silently query the AxcutDocument. doc would be safer.
  • No locale is pinned, unlike the sibling RightPanes.i18n.test.tsx:17,22,26. The hardcoded English strings ride on jsdom's implicit en-US; if the i18n fallback ever changed, the three queryByText(...).not.toBeInTheDocument() assertions would pass vacuously.
  • The second test ("keeps the saved preset active…") passes unchanged against main, so its only value is guarding over-reach — fine, just noting three of its five assertions restate coverage RightPanes.i18n.test.tsx already carries.

Worth knowing for the rebase: this branch is 22 commits behind origin/main and does not contain the camera-SSOT refactor. It merges clean only because it never touches camera.ts — but §2 above is written against what it will actually land on.

Nothing blocking. Happy to approve as-is; §4 is the one I'd fold in while you're in the file.

@EtienneLescot

Copy link
Copy Markdown
Collaborator

Filed the Full Camera gap from my review as #353 — out of scope for this PR, tracked separately.

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.

[Bug]: If no webcam is set, picture-in picture mode is locked by default

2 participants