feat(plugin-ui): chat-style input submit, scroll follow/observe/jump, markdown node - #3327
Conversation
|
rebased onto main |
|
rebased onto main |
|
rebased over the drag-and-drop merge |
|
dropped the slot-reaping commit, superseded by 3065b05 |
|
rebased over df40cac; dropped the meson hunk, superseded by the test refactor |
1. [P1] Version the new plugin API before exposing it — src/scripting/ui_prelude.h:29-30(https://github.com/gegnep/noctalia/blob/a166bcc20b574e194f038613e68085fd615c5d75/src/scripting/ui_prelude.h#L29-L30) 2. [P1] Defer scrollToBottomRev until the new scroll extent exists — src/ui/ui_tree_reconciler.cpp:1649-1653(https://github.com/gegnep/noctalia/blob/a166bcc20b574e194f038613e68085fd615c5d75/src/ui/ui_tree_reconciler.cpp#L1649-L1653) applyProps()runs before child reconciliation and layout. This reads the previous maxScrollOffset(), and setScrollOffset() immediately clamps to that stale range. Consequently, a fresh scroll starts at offset 0 despite the PR description, and adding content while incrementing the revision jumps only to the previous bottom unless stickToBottom is also enabled. Store a pending jump and consume it in ScrollView::doLayout() after recomputing 3. [P2] Include scale in the markdown parse cache key — src/ui/ui_tree_reconciler.cpp:1606-1612(https://github.com/gegnep/noctalia/blob/a166bcc20b574e194f038613e68085fd615c5d75/src/ui/ui_tree_reconciler.cpp#L1606-L1612) MarkdownView::setMarkdown()bakes m_scale into font sizes, spacing, padding, and radii, but reconciliation calls it only when the text changes. When a surface’s fractional scale changes or a plugin surface moves between differently scaled outputs, ordinary props rescale while the retained MarkdownView |
Plugin panels building chat composers need Enter-to-send with Shift+Enter for newlines. Off by default; existing multiline behavior (Enter inserts, Ctrl+Enter submits) is unchanged. Assisted-by: Fable 5 (main session), claude-code v2.1.220
…mRev props Chat-style plugin panels need follow-scroll while streaming content, a way to observe scroll position (offset and max are delivered to the onScroll callback), and an explicit jump-to-bottom trigger. All three are opt-in; native ScrollView users are unaffected. Assisted-by: Fable 5 (main session), claude-code v2.1.220
Exposes the existing md4c MarkdownView control to plugin UI trees as ui.markdown with a text prop. The markdown source is cached per slot so streaming re-renders only re-parse when the text actually changes. Links render as underlined text (MarkdownView has no click handling); sizing follows parent layout like other leaf controls. Assisted-by: Fable 5 (main session), claude-code v2.1.220
MarkdownView only applied its wrap width to labels in doLayout, so measure reported single-line label sizes and a parent flex allocated too little height, overlapping sibling rows. Apply the wrap width from the measure constraints as well; doLayout still re-applies the final arranged width. Assisted-by: Fable 5 (main session), claude-code v2.1.220
ui.markdown and the new input submitOnEnter and scroll stickToBottom, onScroll and scrollToBottomRev props expand the plugin UI contract. Plugins that rely on them can declare plugin_api = 20, so an older shell rejects them at load instead of failing at runtime. Declaration only, matching the API 18/19 precedent; the manifest range check in plugin_registry enforces it. Assisted-by: Fable 5 (main session), claude-code v2.1.217
|
All three points addressed; rebased onto main (
Tested live against a patched shell: fresh chat opens at the bottom, |
Summary
Three additive, default-off plugin UI capabilities, plus two bugfixes found
while exercising them from a real plugin:
submitOnEnterprop: Enter submits, Shift+Enter inserts a newline,Ctrl+Enter still submits (existing behavior unchanged when off)
stickToBottom/onScroll(offset, maxOffset)/scrollToBottomRevprops for follow-scroll, scroll observation, and an explicit jump-to-bottom
ui.markdownnode type registering the existing md4c MarkdownView, with thesource cached per slot so streaming re-renders only re-parse on change
runStreamslots (cap 4/host) were only reclaimed on host teardown, sofour short-lived streams permanently exhausted the cap; exited processes now
mark their cancel token and
startStreamsweeps themoverlapping sibling rows when used alongside other children in a flex
Motivation
Plugin panels that stream content (chat-style UIs) currently can't offer
Enter-to-send composers, can't follow or observe scroll position, and can only
render plain labels. All three are small exposures of capability the codebase
already has. Developed for and exercised by a real plugin (a Claude chat
panel: https://github.com/gegnep/noctalia-plugins); happy to adjust naming or
semantics to fit the project's direction.
Type of Change
Related Issue
—
Testing
just formatclean on every commit (appliedper-commit via
rebase --exec).ui_tree_reconciler_testlinksthe markdown control (its source list gained markdown_view/builders/md4c —
caught by a real build, not hypothetically).
Enter-key behaviors; follow-scroll during streaming; scroll-up disengages
the stick and onScroll reports offset/max; scrollToBottomRev snaps back;
markdown (headings/lists/inline code) streaming without re-parse churn;
scrollToBottomRevapplies once on the first reconcile that sees it(a fresh scroll starts at the bottom when the prop is present).
Manual Coverage
Screenshots / Videos
Checklist
CONTRIBUTING.md.just formatwith clang-format v22+ installed.on the external docs site; nothing in-repo to update).
assets/translations/en.json, or this PR adds nonew user-facing strings. (No new strings.)
and identifiers.
Additional Notes
Plugin-API feedback from building against it, offered for the
docs or as follow-ups rather than included here:
valueare silently dropped(
plugin_manifest.cpp), andnoctalia plugins lintdoesn't flag thenow-unselectable declared default — a lint warning candidate.
Portions of these patches were implemented with AI assistance (Claude Fable 5 orchestrating GPT -5.5)
under human review and testing; the repo has no stated policy on this, so disclosing rather than assuming.