Comment by d12frosted
1 month ago
Thanks for sharing! The JSON schema → widget mapping is a solid use case, and your two-tier validation (keystroke + submit-time) is a nice touch.
On the unbalanced parentheses from LLMs - I've found Claude Code with Opus 4.5 is generally quite good at keeping them balanced, fwiw.
Curious about this though:
> after understanding the tradeoffs, I pushed the widget code creation to an LLM
What tradeoffs made you go with raw widget.el over vui.el? Genuine question - that's exactly the kind of feedback that helps me understand if vui.el is solving the right problems or missing something.
Your gist essentially reimplements some things vui.el provides generically (state tracking, inline form lifecycle, cleanup). Would be interesting to see if vui.el could simplify the widget/state parts while you focus on the schema translation - or learn what's missing if it can't.
If having conversation here is not convenient, we can continue under your gist or via email. Let me know if you are interested :) If not - that's also fine :)
The tradeoffs are very shallow, mainly I just wanted the maximum chance of seeing something working ASAP.
Staying vanilla reduces dependencies, which also makes testing easier during iteration. I forgot which agent (CC or codex) I used for the bulk of the code generation, but some times I manually do some one-off dialog and those get unbalanced parens.
In the agent I ask it to do `emacs -q -nw -l ...` to iterate, so it starts a bare emacs instance. This seems to have worked well when adding [Submit and Close] and [Cancel] buttons, as well as "move the cursor into the first text input widget when inserting a new array item" (the default action is to keep the cursor on the [INS] widget after inserting the list item).
The next consideration is just that I am less confident in the agent's ability to just ingest a .el library and use it correctly vs something more popular like python. Maybe it can, I just wanted results fast and not have to dig too deep myself. I had to go in and do some (setq debug-on-error t) toggles a few times while it was figuring out the json schema load into alist/hashmap differences and I didn't want to spend more time doing plumbing.
But as you probably can imagine, dynamic inline forms immediate gives us state issues, so I asked the agent to create a buffer-local alist (or hashmap?) to track the states of the form input, so it can be cleaned up on close. It's a bit unreliable now. If vui.el already has a solution I'll switch over next.
Thanks for the detailed response! This is really helpful feedback.
Looking at your gist, I think the code actually illustrates why I built vui.el in the first place. The schema→widget mapping logic is genuinely interesting work, but a significant chunk of the code (~400 lines) is dedicated to the inline form lifecycle: jsf--inline-forms registry, marker tracking, resync passes, advice on widget-before-change, overlay cleanup, etc. That's exactly the plumbing vui.el handles automatically.
With vui.el, your json-schema-form could potentially be just the schema translation + a component wrapper:
State tracking, cleanup on close, multiple forms per buffer - all handled by the framework. Your validation logic and schema mapping would be the same, just without the infrastructure code.
On the emacs -q -nw workflow: it works, but you might find eldev + buttercup tests even better for AI-assisted iteration. The agent can run eldev test after changes and self-correct on failures. Byte-compilation catches issues too. Claude Code handles eldev well out of the box.
Anyway, not trying to hard-sell vui.el - your approach clearly works and the schema-form idea is cool. But if you do hit more state/cleanup headaches, might be worth a look. Happy to help if you want to try porting the schema logic over.
I played with vui.el's hello world code earlier today, and it's a great showcase!
Is there a simple way to achieve in-buffer forms? The jsf-- stuff was instructed to make the widget form read-only except for the interactive widget components (took a few iterations to work), while keeping the rest of the buffer editable. The demos seem to all mount to a new buffer. Though I could also feed vui.el into an agent and ask :-)
Thanks for the tips. I guess I can add the deps to --eval "(load 'eldev)" in manual testing.
edit: wow, I just saw your implementation at https://gist.github.com/d12frosted/51edb747167c0fa8fcce40fed...
this is awesome!
4 replies →