Skip to content

fix(panels): proportional absorber pins, stale-pin cleanup, double-click to reset#123

Open
nishasdk wants to merge 1 commit into
johannesjo:mainfrom
nishasdk:panel-layout-improvements
Open

fix(panels): proportional absorber pins, stale-pin cleanup, double-click to reset#123
nishasdk wants to merge 1 commit into
johannesjo:mainfrom
nishasdk:panel-layout-improvements

Conversation

@nishasdk
Copy link
Copy Markdown

What

ResizablePanel reliability fixes

  • Switch wrapperRefs from an index-keyed array to an ID-keyed Map — drag measurement now survives dynamic children changes (e.g. the plan tab appearing mid-drag)
  • Add flexGrow prop to PanelChild so absorbers can be weighted (notes-files gets a smaller weight, leaving the AI terminal ~2/3 of the remaining space)
  • Store absorber pins as flex-grow ratios instead of pixel values so they scale proportionally when the window is resized
  • Detect and clear asymmetric absorber pins (one absorber pinned while siblings are not) — these are stale pixel values from before the child was promoted to absorber status, and would otherwise starve the other absorbers

Double-click on tiling resize handles to unpin

  • Double-clicking a resize handle between two task columns resets both panels to the default equal split without any dragging

Layout cleanup

  • Remove the 40vh max-height from TaskNotesBody — it was a workaround for content-size bubbling up the flex chain; notes-files is now a proper weighted absorber so height is layout-driven, not content-driven

Why

Pinned panels previously stored pixel values, which go stale when the window is resized or when an absorber configuration changes. Storing ratios instead keeps the layout self-healing. The double-click reset gives users a quick escape hatch without needing to drag.

Testing

  1. Pin two panels at different widths, then resize the window → proportions should hold
  2. Double-click a tiling resize handle → both panels should snap back to an equal split
  3. Open the plan tab (which adds a new absorber) → drag should measure correctly with no visual jump
  4. No layout breakage in focus mode or with long plan markdown open

PS. just cosmetic fixes! just wanted to test what submitting a PR on parallel code would look like :)

…ick to reset

ResizablePanel:
- Switch wrapperRefs from an index-keyed array to an ID-keyed Map so drag
  measurement survives dynamic children changes (e.g. plan tab appearing).
- Add flexGrow prop to PanelChild for weighted absorbers (notes-files gets
  a smaller weight so the AI terminal absorbs ~2/3 of remaining space).
- Store absorber pins as flex-grow ratios instead of pixel values so pinned
  panels scale proportionally when the window is resized.
- Detect and clear asymmetric absorber pins (one absorber pinned while
  siblings are not) — these are stale pixel values that would starve siblings.

TilingLayout:
- Double-click on a tiling resize handle calls deletePanelUserSize to unpin
  both neighbours, snapping them back to the default equal split.

TaskNotesBody:
- Remove 40vh max-height cap; notes-files is now a weighted absorber so
  its height is layout-driven, not content-driven.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@johannesjo
Copy link
Copy Markdown
Owner

johannesjo commented May 18, 2026

Thank you very much for this! <3

I reviewed this with separate passes over the resize component, task layout wiring, and tiling reset behavior. I think there’s one blocker before this lands:

The TaskNotesBody max-height: 40vh removal relies on notes-files being a weighted absorber, but that wiring doesn’t appear to be in this PR. In TaskPanel, the stack-mode vertical panel still uses absorberIds={['ai-terminal']}, and notesAndFilesChild does not set flexGrow, so notes-files remains content-sized. With the cap removed, long plan markdown can again bubble through the flex tree and push/starve the AI terminal. Please either make notes-files an absorber with the intended smaller weight or keep the cap until that layout change is actually applied.

A few smaller follow-ups I noticed:

  • ResizablePanel now interprets persisted absorber values as flex-grow weights, but dragging between two absorbers only persists those two panels. If this component is ever used with 3+ absorbers, the untouched absorber keeps weight 1 while the dragged pair get pixel-derived weights, so it can collapse unexpectedly.
  • The drag preview state is still index-keyed, even though refs are now ID-keyed. If children change during an active drag before the dragged pair, the live override can apply to the wrong child.
  • In TilingLayout, the double-click reset deletes saved widths but doesn’t schedule updateViewportState(). If the reset changes task boundaries while the total strip width stays the same, offscreen/attention affordance state can remain stale until another scroll or layout update.

@nishasdk
Copy link
Copy Markdown
Author

Thanks for the thorough review!

This was mostly me experimenting to learn how things fit together, so appreciate the patience!
Re: the TaskNotesBody - I think it may have ended up in a worktree somewhere while I was playing around with arena mode so will track it down.

On the follow-ups: I had similar concerns about the flex-grow weighting and index drift while writing it, but went with the simpler fix for now since I wasn't sure how far this panel pattern is intended to expand. Happy to revisit if you have a sense of where it's headed, especially with behaviour in combination with the Terminal/dev/e2e section, which I haven't really touched.

A few questions before I tinker further:

  1. What are you/ people actually using Notes for day-to-day? What's the intended purpose?
    (My own Notes usage has basically been zero except when Claude generates a Plan. I occasionally copy bits of plan/review comments in there as a reference, but I end up pasting into Obsidian because the editing experience isn't quite there yet. One thing I'd find genuinely useful: if anything you highlight in the plan or review automatically synced into Notes.)

  2. What's the practical difference between sending a prompt from Notes vs. the AI chat interface vs. "Send a Prompt"? I think I have the gist but want to confirm

  3. Is Plan currently Claude-only? I haven't been able to get another agent to trigger the Plan tab but not sure if that's by design or a prompting issue.

Would love to hear how you're thinking about these sections long-term, mostly so I can work around your direction rather than against it. For now I'm just adding things I personally want haha, but if I make a bigger structural change, is it better to gate it behind a settings toggle (like the Send a Prompt panel)?

@johannesjo
Copy link
Copy Markdown
Owner

  1. What are you/ people actually using Notes for day-to-day? What's the intended purpose?
    (My own Notes usage has basically been zero except when Claude generates a Plan. I occasionally copy bits of plan/review comments in there as a reference, but I end up pasting into Obsidian because the editing experience isn't quite there yet. One thing I'd find genuinely useful: if anything you highlight in the plan or review automatically synced into Notes.)

You can use it for everything, but personally I mostly use it for keeping track I've want to evaluate with the agent later, when current work is done.

2. What's the practical difference between sending a prompt from Notes vs. the AI chat interface vs. "Send a Prompt"? I think I have the gist but want to confirm

In terms of what gets send to the agent, there is no difference.

  1. Plan finds new md docs in plan folder. Not sure to be honest, but I think we might need to extend this to work with other agents as plan folder is read from .claude/settings.json.

@johannesjo
Copy link
Copy Markdown
Owner

For this PR I would keep the scope conservative: either restore the notes max-height or wire notes-files as a real weighted absorber. If you want to go further with the absorber work, I would also make dragOverride ID-keyed while you are there, since dynamic children are exactly the case this PR is trying to handle.

I do not think we need to make ResizablePanel a fully general layout solver yet. The current concrete need is 2 absorbers. If we later rely on 3+ absorbers, then dragging should persist the full absorber ratio vector, not just the two panels next to the handle.

Plans are not meant to be Claude-only long term, but the current implementation is Claude-biased: Claude is configured via .claude/settings.local.json, while the watcher also looks at docs/plans. Other agents can work if they write markdown there, but we probably need a clearer agent-agnostic convention.

On prompt surfaces: Notes, the prompt input, and review submission all send through the same running-agent prompt path. Notes are mainly a persistent scratchpad/backlog; the prompt box is the transient next-message composer. Inline "ask about code" is different: that is a separate one-shot Q&A flow, not the task agent session.

For bigger structural changes, I would prefer good defaults over adding settings toggles by default. Add toggles when it is a lasting workflow preference or the UI would otherwise be noisy/controversial.

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.

2 participants