Skip to content

release: v1.0.0 — widget system & Lua plugin system#272

Open
eugenioenko wants to merge 191 commits into
mainfrom
release/v1.0.0
Open

release: v1.0.0 — widget system & Lua plugin system#272
eugenioenko wants to merge 191 commits into
mainfrom
release/v1.0.0

Conversation

@eugenioenko

Copy link
Copy Markdown
Owner

Summary

This release branch contains all work targeting v1.0.0:

Widget System

  • Composable widget system with tree, list, input, button, label, divider, tabs, dialog, drawer, scrollview, select, checkbox, hstack/vstack layout, and box container widgets
  • Replaces legacy hardcoded UI components (explorer, changes panel, dialogs) with composed widget trees
  • Focus management system with tab navigation and active border highlighting
  • Surface interface for widget rendering with sub-surface clipping
  • Comprehensive widget unit tests (30 → 186 tests)

Lua Plugin System

  • Sandboxed Lua VMs with permission model and plugin lifecycle management
  • Plugins can register bottom panels, sidebar panels, commands, and keybindings
  • Lua APIs for editor, filesystem, system, network, and events
  • Widget API for plugin UI rendering
  • 8 example plugins: notepad, color-picker, go-test-runner, http-client, markdown-preview, todo-scanner, cheat-sheet, docker-manager
  • OUTPUT panel for plugin logging and error routing
  • Plugin install/uninstall/update/reload commands

Audit Status

A 5-agent audit identified items to address before merge (see audit/checklist.md):

  • 6 critical (sandbox security)
  • 10 high (plugin lifecycle bugs, architecture)
  • 55 medium (API consistency, test coverage, duplication)
  • 19 low/informational

Test plan

  • All existing tests pass (make test)
  • Widget unit tests cover all widget types
  • E2E tests for plugin panel rendering
  • Manual testing of all 8 example plugins
  • Address critical/high audit items
  • Add missing test coverage

🤖 Generated with Claude Code

https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm

eugenioenko and others added 30 commits June 26, 2026 10:10
Implement a reusable tree widget in internal/widgets/ with JSON-serializable
config, multi-action icons, context menus, scrollbar, and expand/collapse.
Register it as a "Widget" sidebar panel with demo Docker data for visual testing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Card widget draws a border and insets its child content. Adds DrawBorder
to the widgets.Surface interface and a CardWidgetAdapter bridge. Demo
wraps the tree widget in a card in the Widget sidebar panel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Box widget provides configurable padding (top/bottom/left/right) without
borders. Demo updated to use box with padding 1 on all sides.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Title widget renders a single-line header with optional dropdown icon
when menu entries are provided. Dropdown widget is a standalone clickable
icon that triggers a menu callback. Demo shows title widget in sidebar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add VStack and HStack layout widgets with grow/fixed sizing based on
each child's Height()/Width() methods. Remainder pixels distributed
across first grow children. Define Widget interface in widgets package.
Add generic WidgetAdapter to replace per-type adapters. Demo shows
Docker view with title + 3 cards in a vstack.

TODO: card border should use theme border style

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Box now handles borders (individual sides), padding, and margin —
replacing Card entirely. Border style defaults to theme StyleBorder.
Corner characters only draw when both adjacent sides are enabled.
Added helper constructors for common cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add BaseWidget with embedded BoxModel (border/padding/margin per side)
  and RenderBox() — all widgets inherit layout via struct embedding
- Remove CardWidget; BoxWidget now delegates to BaseWidget for rendering
- Add JSON widget builder (BuildFromJSON) for hot-reloadable widget panels
- Add "Reload Widgets" command for edit-reload iteration without recompile
- Title and Tree menu icons now use DropdownWidget internally with
  configurable icon and padding via JSON (icon, padded, menuIcon, menuIconPadded)
- Remove per-type adapters; only generic WidgetAdapter remains
- Add widget.json demo file with Docker container/image/volume layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HStack supports left/center/right, VStack supports top/center/bottom.
Alignment applies when all children have fixed sizes. Grow children
fill available space, making alignment irrelevant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ButtonWidget with accelerator key support (underline, not
background highlight), themed styles (StyleButton/StyleButtonFocused
with inverted fg/bg default for focus), and JSON builder wiring.
Replace hardcoded StyleCount with iota sentinel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add FocusManager with flat focusable list, tab/shift+tab cycling,
click-to-focus, and key routing to focused widget only. BoxWidget
shows StyleBorderActive when its child has focus. Button padding
now fills correctly with background color via BorderedInterior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Widget panel was built before ApplyBorderStyle, so it always used
the theme default borders instead of the user's settings override.
Rebuild after border style is applied at startup and at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Label displays plain text (single line, no interactivity).
Divider draws a horizontal rule using the theme border character.
Both wired into the JSON builder.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Supports bordered (default, uses theme borders with active highlight)
and borderless (chevron prefix with border color styling) modes. Includes
cursor positioning, double-click word selection, clipboard, and paste.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add TabsWidget (standalone tab bar with overflow indicator) and
TabbedWidget (tab container that pairs tabs with child content panels,
switchable from JSON). Fix click-focus bug where mouse forwarding in
FocusManager consumed events before the tab bar could handle them.
Also adds inner padding to bordered inputs and wires tabbed callbacks
on widget panel reload.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TabsWidget is now focusable with left/right to move a selected cursor
and Space/Enter to activate the tab. Underline indicator shows on the
selected tab label text only (not padding spaces). SetFocused resets
the selection to the active tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add StyleSelectedTab theme style with fallback to SidebarSelected for
backwards compatibility. Reorder focus collection so content widgets
are focused before the tab bar. Highlight overflow chevron when
keyboard-navigating to hidden tabs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…idget

Close the gaps needed for the tree widget to replace the explorer:
- ActiveID highlights a node independently of selection (open file)
- Muted flag renders node labels in muted style (gitignored/dotfiles)
- OnExpand callback fires on expand for lazy child loading
- Reload preserves expanded state while refreshing via OnExpand
- SelectByID programmatically navigates to a node
- Mouse handler now checks rect bounds before consuming events

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allows each node to specify its own badge style (e.g. StyleWarning
for modified, StyleDanger for deleted) instead of always using
StyleMuted. Falls back to StyleMuted when unset.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ListItem struct provides a clean flat definition (no children/nesting).
Parsed as "list" type in JSON, maps to TreeWidget internally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a Navigation sidebar panel that reimplements the explorer using
the widget system's TreeWidget. Extracts shared code (LoadDirEntries,
FileOp* helpers) so both explorer and navigation use the same filesystem
loading and file operation logic, keeping them in sync on mutations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generic modal dialog that accepts any widget tree as content and
renders a configurable button row. Supports Tab cycling between
content focusables and buttons, Esc to dismiss, cursor forwarding
for input widgets, and mouse click on buttons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Display widget that takes a string and wraps it at the available
width. Reports Height() from the wrapped line count so layout
containers (VStack, Dialog) can auto-size around it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace custom button rendering, hit regions, and focus tracking with
real ButtonWidget instances in a right-aligned HStack. Adapter's
FocusManager handles Tab cycling. Add OnClick to ButtonConfig, default
button padding 1 left/right, HeightForWidth interface for paragraph
sizing, and demo confirm dialog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ersed colors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gap property spaces children in layout stacks. Bold field on Cell
enables per-cell bold for styled cells. Dialog and title widgets
now render text in bold.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… 25 items

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Delete old ui.InputDialogWidget and demo dialog. ShowInputDialog now
composes a DialogWidget with an InputWidget as content. Add
ShowInputDialogEx for custom confirm labels (Open, Review, etc).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Delete old ui.ConfirmDialogWidget. ShowConfirmDialog now composes a
DialogWidget with a ParagraphWidget as content. Add ShowConfirmDialogEx
for titled confirm dialogs. Fix dialog layout gaps between title,
content, and footer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add OnSubmit callback to InputWidget triggered on Enter (not
Shift+Enter). Wire it in ShowInputDialogEx so all input dialogs
submit on Enter. Add "Unsaved Changes" title to quit dialog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
eugenioenko and others added 30 commits June 27, 2026 23:38
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-005: Close All Tabs now shows a confirm dialog with Abort / Close
Saved / Discard All when dirty tabs exist. Adds Close All Saved command
and context menu entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-006: Same pattern as BUG-005. Also extracts CloseOtherTabs and
CloseAllTabs into named methods on App instead of inline closures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-008: Clicking a search result placed the cursor correctly but the
viewport was off by 1 row because scrollViewport only ensures the cursor
is barely visible. Now GoToLine sets TopLine so the target line appears
at ~1/3 from the top, with a fallback for pre-render viewport height.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-018: Go to File picker now left-truncates directory paths with
ellipsis when space is tight, keeping filenames fully visible with a
minimum 3-char gap between columns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-009: not a bug (matches VS Code)
BUG-010: not a bug (DE captures keybinding)
BUG-011: not a bug (DE captures keybinding)
BUG-012: duplicate of BUG-005
BUG-013: duplicate of BUG-006
BUG-014: not a bug
BUG-015: not a bug
BUG-016: not a bug
BUG-017: not a bug
BUG-018: fixed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-020: Dialog stayed open when entering 0 because the condition
rejected non-positive numbers without dismissing. Now clamps to 1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
Adds `hover X Y` to simulate mouse move events, useful for testing
hover-dependent UI like fold gutter indicators.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-019: issue #277
BUG-020: fixed
BUG-021: not a bug (hover-to-reveal by design)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-024: Tracks undo stack position at save time. When undo/redo
returns to the save point, Dirty flag is cleared and the dot indicator
disappears from the tab title.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-022: not a bug
BUG-023: not a bug
BUG-024: fixed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-025: lineRange() now excludes the trailing empty line (file
terminator) when no selection is active, preventing it from sorting
to the top.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-026: Panel select dialog now shows friendly names like "TERMINAL"
instead of raw IDs like "terminal".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
BUG-027: Removed early return in focusTerminal() so SetFocus is called
after opening the terminal panel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
…m clicks

BUG-040: Clicking outside the palette bounds now dismisses it. Item
clicks are also bounds-checked so clicking at the same Y row but
outside the box no longer triggers the item.

Also marks BUG-027 through BUG-041 as reviewed in bugs.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
Hovering over a different menu item while a dropdown is open now
switches to that menu's dropdown without requiring a click. Also fixes
command palette click-outside detection (BUG-040) and updates sort
line tests for BUG-025 trailing empty line fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
QA bug fixes from automated sweep (BUG-001 through BUG-041)
Three findings from --exec agent testing:
- BUG-042: selection replace is two undo steps instead of atomic
- BUG-043: Ctrl+PgUp/PgDn swallowed by editor, tab switch broken
- BUG-044: horizontal scrollbar thumb never updates size/position

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
Fixes #281. bufColToVisualCol and visualColToBufCol used byte index
from range loop instead of rune index, causing cursor position
mismatch for non-ASCII text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
Tests confirm the byte-vs-rune bug fixed in the previous commit.
These tests fail on the old code and pass with the fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
…for tab switching

Editor was consuming alt/ctrl-modified keys (arrows, backspace, delete,
pgup/pgdn) before global keybindings could fire. Now returns EventIgnored
for any key with alt or ctrl modifier. Fixes BUG-010, BUG-011, BUG-043.

Also adds keyboard tester debug command and replaces ctrl+pgup/pgdn tab
switching with alt+,/alt+. which works reliably across terminals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
Added replaceSelection() helper that batches DeleteSelectionCommand with
follow-up commands into a single BatchCommand. Typing over a selection
now undoes in one Ctrl+Z instead of requiring two.

Fixes BUG-042

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
Batch delete+insert into one undo entry via replaceSelection(), with
ContinueGroup() so subsequent typing groups naturally with the replacement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GG9dYw2AG18ro5c2dkPtm
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.

1 participant