Skip to content

feat(app): rebuild Knobs as a stack of features - #57921

Merged
kitlangton merged 1 commit into
betafrom
knobs-global-top
Oct 25, 2026
Merged

kitlangton merged 1 commit into
betafrom
knobs-global-top

Conversation

@kitlangton

@kitlangton kitlangton commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What it does

Rebuilds the Knobs panel as the Stack layout from D-27. The home screen is a list of features under small area headings. Each row has a one-line hint and a dot when the feature differs from Shipped. Tapping a feature opens its page:

  • presets for that feature only (Shipped, Snappy, Smooth, Calm, plus modes like James);
  • every control on one grid, with the label on the left and the value in a fixed right column;
  • Replay, Focus and Reset to Shipped.

When the panel is wide, the list and the page sit side by side. Drag the left edge to resize it.

Speed and slow motion are no longer in Knobs; they stay in the debug bar's SPEED and SLOW cells. The global preset strip and the cards are gone.

The Message menu experiment stays; #58867 removes it. Until then it shows as its own feature under Composer.

How it works

  • @opencode/ui/motion-tuning-features (replaces motion-tuning-panel) groups registered knobs into features by area and subsection, and holds per-feature presets, changed and reset. It is covered by motion-tuning-features.test.ts; debug-bar.spec.ts checks a page's Enabled switch stays bound to its own feature after switching.
  • The panel is now a custom Solid component in gui-extensions/src/debug/motion.tsx and motion-rows.tsx. It reads and writes the same motionTuning registry and storage, so edits still apply live. It no longer uses the knobs library; the components lab still does.
  • Knobs gain optional description (the hint) and target (a selector Focus outlines and scrolls to).
  • Push and back animate transform and opacity on a critically damped spring, so there is no bounce, and mirror in RTL. Escape goes back from a pushed page and closes the panel otherwise; focus moves into the page and back to its row.
  • Replay: Sidebars › Open & close (Sidebar and Side panel), and Composer › Docks in its story. Other features hide it.
  • Focus: Open & close, Scale by distance, Peek, Hover, Docks, Activity chip, Avatars, Voice calls, Sources.

What knobs would need to do this itself:

  • a stack/split navigation container (list → page, side by side when wide);
  • presets and a Replay/Focus/Reset action bar scoped to a group;
  • a fixed label/control/value/reset grid;
  • headless Solid controls (slider, spring curve) that bind to an external store;
  • a built-in "outline this selector" for a group.

Risk

This is debug-only UI (dev and Beta). The registry API only gains fields.

Proof

Before (beta) After
Home
Feature

Wide:

Push and back: https://problems.kitlangton.com/media/5JlWsFzas6dc65_fU-uSAw.mp4

@opencode-agent opencode-agent 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.

The new layout works as described, with one problem. In happy-dom, Global renders as its own panel above the tabs, Experiments sorts last, the tab strip disappears when there is only one section, and the panel test and the packages/ui typecheck both pass. I couldn't render it in a real browser here, so before/after screenshots or a short clip of the debug-bar panel would help.

Public API: unchanged. The only addition is an internal knobs store id, opencode-motion-global.

Concerns:

  • Duplicate toolbar (inline): the lower panel still shows a header toolbar with no title, and each header's Reset, Copy and Random now covers only half the knobs.
  • Variants drop Speed and Slow motion: Global now lives in a store with persist: "none", so a saved variant no longer records or restores them. Older saved variants still load, but their Global values are ignored. If that's intended, it's fine.

Comment thread packages/ui/src/motion-tuning-panel.ts Outdated
...keys,
})

const stopPanel = controls.mount(lower, {

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.

title: false only clears the title text. In knobs 0.3.0 the header (Undo/Redo/Reset/Copy/Random) still renders unless header: false is passed. In happy-dom, all five buttons stay visible here, so there's a second, untitled toolbar row between Global and the tabs. The two toolbars also split the panel-wide commands: the top Reset, Random and Copy now cover only Speed and Slow motion, and this one covers only the sections. Either hide this header and keep one set of panel-wide commands, or make sure both headers' Reset and Copy cover all knobs.

@kitlangton kitlangton changed the title fix(gui-extensions): put global Knobs above the sections fix(gui-extensions): make the Knobs panel clearer Oct 21, 2026
@kitlangton
kitlangton force-pushed the knobs-global-top branch 2 times, most recently from d61497f to 5cbd2e4 Compare October 22, 2026 00:38

@opencode-agent opencode-agent 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.

The new commit fixes my earlier point (thread 1). There is now one store and one panel, with Global as a pinned group, so the second untitled header and the split Reset/Copy are gone. I checked it in happy-dom: motion-tuning-panel.test.ts passes (4 tests), and the packages/ui and packages/gui-extensions typechecks are clean. I couldn't check it in a real browser.

Public API: unchanged. motionTuning gains optional section, description and target, which are internal. knobs moves to the prerelease 0.4.0-next.12, as the description notes.

New concern: the hover outline flickers as the pointer moves within a single row (inline).

  • packages/ui/src/motion-tuning-panel.ts:266: pointerover bubbles from every child element of a row, so moving the pointer across one row calls show repeatedly. Each call runs clear(), which fades out and removes the current frame, then adds a new one at opacity 0. In happy-dom, hovering a row and then one of its child elements left two frames: the first at opacity 0, the second a new element. The outline blinks while the pointer moves across a row. Keeping the frames when the [data-field]/[data-group] spot hasn't changed since the last call (and only clearing when it does) would avoid this.

@opencode-agent opencode-agent 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.

The newest push looks sound. My earlier point about the second, untitled header is fixed, since the panel is now a single store with Global pinned. The new per-tab presets (scopedPresets) and the mode switching that now follows its tab read correctly. I checked in happy-dom: motion-tuning-panel.test.ts passes (4 tests) and the packages/ui and packages/gui-extensions typechecks are clean. I couldn't check it in a real browser.

Public API: unchanged. The knobs dependency is now the prerelease 0.4.0-next.13.

@opencode-agent opencode-agent 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.

The latest changes look sound, with no new concerns. They add a card per experiment, ordered sidebar to composer, rename rows, pass card descriptions as groupDescriptions, and gate Rounded stop size with when. The earlier point about the second, untitled header stays addressed, since Global is pinned in the single panel. Knob ids and the opencode.motion.v2 storage key are unchanged, so values already saved on a device still load.

I checked it with tests and type checks: in packages/app, motion-tuning-panel.test.ts and panel-motion.test.ts pass (19 tests); in packages/ui, motion-tuning.test.ts passes (4 tests). Typechecks are clean in packages/ui, packages/app and packages/gui-extensions. I didn't render the panel in a browser; the description's before/after image covers it.

Public API: unchanged. description and target are new optional fields on the internal motionTuning options.

@opencode-agent opencode-agent 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.

The latest push looks sound, with no new concerns. The hover-outline flicker I pointed out earlier is fixed: the outline now comes from knobs' onActive and only redraws when the target changes. The doubled-header point stays addressed. Card descriptions now render under each card's title, and Experiments cards fold. In happy-dom, a folded card's summary updated live when its choice changed from outside the panel.

I checked it with tests and type checks: in packages/app, motion-tuning-panel.test.ts and panel-motion.test.ts pass (19 tests); in packages/ui, motion-tuning.test.ts passes (4 tests). Typechecks are clean in packages/ui, packages/app and packages/gui-extensions. I didn't render it in a browser; the description's before/after and hover images cover it.

Public API: unchanged. The knobs prerelease moves to 0.4.0-next.16 in both packages/ui and packages/app.

@opencode-agent opencode-agent 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.

The new commits look fine, with no new concerns: they add a description to the Sidebar hover card and move knobs to 0.4.0-next.17 in packages/ui and packages/app. The earlier doubled-header point stays addressed.

I checked it with tests and type checks: in packages/app, motion-tuning-panel.test.ts and panel-motion.test.ts pass (19 tests); in packages/ui, motion-tuning.test.ts passes (4 tests). typecheck:tsc is clean in packages/ui, packages/app and packages/gui-extensions. I didn't render it in a browser; the description's images cover it.

Public API: unchanged.

@kitlangton kitlangton changed the title fix(gui-extensions): make the Knobs panel clearer feat(app): organise Knobs into area tabs with a segmented speed picker Oct 24, 2026

@opencode-agent opencode-agent 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.

This push reworks the panel, and I found nothing that would block it. Speed and Slow motion become one global.speed choice, with motionGlobals.time() = 1/speed. The debug bar's SPEED and SLOW cells drive that choice, the panel gets area tabs with folded per-feature cards, and knobs moves to 0.4.0-next.20. The earlier doubled-header point stays addressed: there's one store and one header, with Global pinned. Nothing else in the repo still reads motionGlobals.slow. Saved speed/slow values are dropped, as your Risk section notes.

I checked it with tests and type checks: in packages/app, the panel and motion browser tests pass (30) and the components-lab tests pass (4); in packages/ui, motion-tuning.test.ts passes (4). typecheck:tsc is clean in packages/ui, packages/app and packages/gui-extensions. I didn't render it in a browser; the description's screenshot covers it.

Public API: unchanged. motionGlobals and the knob ids are internal developer tooling.

@kitlangton kitlangton changed the title feat(app): organise Knobs into area tabs with a segmented speed picker feat(app): rebuild Knobs as a stack of features Oct 25, 2026

@opencode-agent opencode-agent 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.

The rebuild type-checks cleanly across the four changed packages, and the new motion-tuning-features tests pass, but one existing test now fails.

I ran the tests closest to the change: in packages/ui, motion-tuning-features.test.ts and motion-tuning.test.ts pass (6); in packages/app, the components-lab tests pass (4). typecheck:tsc is clean in packages/ui, packages/app, packages/gui-extensions and packages/session-ui. Nothing still imports motion-tuning-panel or the message menu. I didn't render the panel in a browser; the description's screenshots and video cover it.

The earlier doubled-header point no longer applies, since the knobs panel is gone.

Public API: unchanged. @opencode/ui's internal ./motion-tuning-panel export is replaced by ./motion-tuning-features, and nothing else in the repo used the old one.

Blocking: test-browser/panel-motion.test.ts now fails one test, "swaps the spring knobs for James's while it's on" (inline). It passes on pr/base.


const group = "Sidebars"

const subsection = "Open & close"

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.

Moving the Sidebars knobs into subsection: "Open & close" breaks test-browser/panel-motion.test.ts › "swaps the spring knobs for James's while it's on". The test still selects knob.group === "Sidebars" && !knob.subsection, so it now finds nothing and fails at line 457 ([] instead of ["Spring", "Motion", "Contents fade", "Edge fade"]). The file passes 16/16 on pr/base and 15/16 here. Filtering on knob.subsection === "Open & close" in the test should fix it.

@opencode-agent opencode-agent 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.

The failing test from my last review is fixed: panel-motion.test.ts now selects the "Open & close" subsection and passes 16/16, the same as on pr/base. Nothing else changed since that review, where the packages/ui and components-lab tests passed and the typechecks were clean. The earlier knobs-header point no longer applies, since that panel was removed.

Public API: unchanged.

The Knobs panel is now a list of features under their areas. Opening one pushes its page: its presets, its controls on one grid, Replay where a trigger exists, Focus on the part of the app it changes, and Reset to Shipped. Wide enough, the list and the page sit side by side. Speed and slow motion stay in the debug bar.

Also removes the Message menu experiment.

@opencode-agent opencode-agent 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.

The latest push looks sound, with no new concerns. The Message menu removal is reverted cleanly. In the panel, Escape now goes back or closes, focus moves into and out of a page, the push mirrors in RTL, and the Enabled switch row is keyed so it controls the open feature's switch. A feature now reads as Custom when a non-spring control was tuned by hand.

I checked it with tests and type checks: in packages/ui, motion-tuning-features.test.ts and motion-tuning.test.ts pass (6); in packages/app, panel-motion.test.ts passes 16/16. typecheck:tsc is clean in packages/ui, packages/app, packages/gui-extensions and packages/session-ui. I couldn't run the new Playwright test in debug-bar.spec.ts, because no browser is installed here.

Public API: unchanged.

@kitlangton
kitlangton merged commit ce44769 into beta Oct 25, 2026
13 of 17 checks passed
@kitlangton
kitlangton deleted the knobs-global-top branch October 25, 2026 04:52
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.

1 participant