Comment by d12frosted

1 month ago

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:

    (vui-defcomponent json-schema-form (schema on-submit)
      :state ((values (schema-defaults schema))
              (errors nil))
      :render
      (vui-vstack
       (render-schema-fields schema values
         :on-change (lambda (path val)
                      (vui-set-state :values (set-at-path values path val))))
       (vui-button "Submit"
         :on-click (lambda ()
                     (let ((errs (validate-schema schema values)))
                       (if errs
                           (vui-set-state :errors errs)
                         (funcall on-submit values)))))))

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!

  • Glad you like it! I was curious to see the comparison myself, so I asked Claude Code to reimplement it with vui.el - wanted concrete numbers and code rather than just claims. The 60% reduction was a pleasant surprise.

    Regarding in-buffer forms: I'd love to understand your use case better. When you say "injecting" forms into a buffer while keeping the rest editable - what's the actual workflow you're building? A few scenarios come to mind:

    1. Inline editing in org/markdown - e.g., a structured data block that becomes a form when you enter it? 2. Mixed content - documentation with embedded interactive widgets? 3. Progressive disclosure - expand a section into a form, collapse back when done?

    Right now vui.el mounts to a dedicated buffer, so it doesn't support inline injection. But depending on the use case, it might not be too complex to add - or there might be a different approach that works better. Would be interested to hear more about what you're trying to build.

    (And yes, feeding vui.el to an agent works surprisingly well - that's exactly how the gist was created!)

    • I haven't thought through the entire use case. But it loosely revolves around the interaction designs provided by org-mode, ipython/jupyter notebooks, atuin desktop, aka "interactive documents", where the execution intent is embedded in text.

      Jupyter is objectively more powerful from an interactivity standpoint, but the cell-based editing strategy has always fell short from a navigation and versioning standpoint. OTOH text-only interaction is sometimes too free, so after all these years, I still haven't found anything that feels just right.

      Say you are writing devops documentation. Skipping the many ugly ways you can achieve this in jupyter (e.g. you need ssh logins and creds), or even slightly less ugly in atuin, in org-mode you'd use a begin_src / end_src block and eval that block. The block kind of breaks the text flow. A possibly better interaction mode would be a text "button", borrowing from GNU Hyperbole's terminology.

      Continuing the button use case, say the devops document says "<run this> to check the uptimes of our servers". That button is simple enough. You can evaluate this and get a buffer, or like org-babel, insert the output into a block inline. Now we want to extend the functionality: "<query the ps of a given server>". We want parameterization, but without breaking flow. So here I'm thinking of injecting a disposable form at cursor. In a web-based toolkit, we'd use a popup or expandable form, but that switches focus out of the document and into a modal. Does that make sense?

      I'm not sure how well it will all fit, but every now and then I feel the same itch, and it seems like in 10+ years of using different tools, Emacs is still the best for providing "structured, validated input for in-document interaction".

      2 replies →