Skip to content

Commit 1dedc7f

Browse files
docs: add v1.9 changelog and close out release plan
Captures the v1.9 release: 12 new widgets, animated-GIF support, JPEG/BMP via GD, in-process callback bridge, system-wide Tcl-injection fix, window event hooks, and 453 passing assertions across 30 test files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e163eee commit 1dedc7f

2 files changed

Lines changed: 233 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Changelog
2+
3+
All notable changes to **php-gui** are documented here.
4+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to semantic versioning.
5+
6+
---
7+
8+
## [v1.9] — 2026-04-26
9+
10+
A foundation release. Closes the four production blockers from the v1.0 plan and ships twelve new essential widgets. Tests grew from 159 → **453 passing assertions** across 30 files.
11+
12+
If you're upgrading from v1.8, the changes are **backwards-compatible at the public API level** — all v1.8 code continues to work, but you also get safer defaults, a faster event loop, and a much larger widget toolbox.
13+
14+
### Highlights
15+
16+
- **Twelve new widgets**`Text`, `Scrollbar`, `Listbox`, `Radiobutton` (with `RadioGroup`), `Scale`, `Spinbox`, `Progressbar`, `Notebook`, `Treeview`, `PanedWindow`, `LabelFrame`, `Separator`. The library now covers the full table-stakes set for desktop apps.
17+
- **Animated GIFs** — the `Image` widget now plays multi-frame GIFs with proper disposal and transparency compositing.
18+
- **JPEG + BMP via GD**`Image` transcodes formats Tk core can't read into temp PNGs.
19+
- **Arbitrary widget nesting** — Frames inside Frames inside Windows now work the way you'd expect.
20+
- **Tcl-injection eliminated** — every widget now safely quotes user-supplied strings.
21+
- **Event loop is in-process** — no more `/tmp/phpgui_callback.txt`, no 100 ms input lag, no cross-process collisions.
22+
23+
---
24+
25+
### Added — New widgets
26+
27+
#### `Text`
28+
Multi-line editor with safe value routing through Tcl variables, `setText` / `getText` / `append` / `insertAt` / `clear`, length and line counts, `setState('disabled')` for read-only log views (still accepts `append()`). Strict index validation on `insertAt()` blocks Tcl injection through index expressions.
29+
30+
#### `Scrollbar`
31+
Wraps `ttk::scrollbar` with manual `bindTo($target)` two-way wiring, plus a one-call `Scrollbar::attachTo($target, $orient)` factory that creates, binds, and packs in one shot. Both orientations.
32+
33+
#### `Listbox`
34+
Selectable list with all four Tk select modes (`browse`, `single`, `multiple`, `extended`). Full add/remove/replace/clear, all selection accessors, `<<ListboxSelect>>` virtual-event binding via `onSelect()`. Items round-trip Tcl-special characters literally.
35+
36+
#### `Radiobutton` + `RadioGroup`
37+
First-class `RadioGroup` object backing a single Tcl variable — solves the silent foot-gun where two radios accidentally share `-variable` names. Per-radio `command` callback receives the value; group-level `onChange` fires for both user clicks and programmatic `setValue()`.
38+
39+
#### `Scale`
40+
Slider input with `from`/`to`/`orient`, `getValue`/`setValue`, and `onChange` that fires uniformly for user drag and programmatic updates.
41+
42+
#### `Spinbox`
43+
Bounded numeric input or fixed enumeration via `values`. `onChange` is wired to `<Return>` and `<FocusOut>` in addition to Tk's `-command`, so direct typing fires the handler.
44+
45+
#### `Progressbar`
46+
Both determinate (`setValue` / `step`) and indeterminate (`start(intervalMs)` / `stop`) modes; `setMode()` switches at runtime; all inputs validated.
47+
48+
#### `Notebook`
49+
Tabbed container wrapping `ttk::notebook`. `addTab` / `selectTab` / `selectPage` / `setTabState` (`normal` / `disabled` / `hidden`); `onTabChange` bound to `<<NotebookTabChanged>>`. Enforces page-is-child invariant at `addTab()` time so the silent "empty pane" Tk failure mode can't happen.
50+
51+
#### `Treeview`
52+
Hierarchical list / multi-column table — the single biggest missing widget for desktop apps. Two operating modes:
53+
- Flat tables with named columns and headings
54+
- Hierarchies (folder trees, outlines)
55+
56+
Positional and keyed value APIs, full selection management (single + multi), `onSelect` and `onDoubleClick` (with click-coordinate-to-row resolution), column / heading configuration, `delete` cascades to descendants. All values go through safe-quoting; row IDs are deterministic and Tcl-safe.
57+
58+
#### `PanedWindow`
59+
Resizable split container; `addPane` with `weight`-based space distribution, `setSashPosition` / `getSashPosition`, both orientations.
60+
61+
#### `LabelFrame`
62+
Titled bordered container — a `Frame` whose top edge is broken by a small caption. Optional title (empty produces a plain bordered frame).
63+
64+
#### `Separator`
65+
Thin horizontal or vertical divider line.
66+
67+
---
68+
69+
### Added — Image widget improvements
70+
71+
- **Animated GIFs** — full multi-frame playback with disposal handling, on-load frame compositing onto a logical-screen-sized canvas, and per-frame snapshot pre-decoding so frame swaps never flash transparent gaps. New `getFrameCount()` / `isAnimated()`.
72+
- **JPEG + BMP support** — transparent transcoding through PHP's GD extension to a temp PNG before Tk loads it. Original path is preserved by `getPath()`; the temp PNG is unlinked automatically on `setPath()` and `destroy()`.
73+
- **Safe Tcl quoting** for all option values; paths route through Tcl variables so spaces, brackets, `$`, and quotes never break command parsing.
74+
75+
---
76+
77+
### Added — Window event hooks
78+
79+
- `Window::onClose(callable)` — runs your handler before the application exits. Returning `false` vetoes the close. Useful for "save unsaved changes?" prompts.
80+
- `Window::onResize(callable)` — fires `(int $width, int $height)` on actual resizes (descendant `<Configure>` events and same-size duplicates are filtered out).
81+
82+
---
83+
84+
### Added — Public API additions
85+
86+
- `AbstractWidget::tclQuote(string)` — central safe-quoting helper.
87+
- `AbstractWidget::buildOptionString(array $skip)` — replaces every per-widget option-formatter; routes every value through `tclQuote()`.
88+
- `AbstractWidget::getParentId()` / `getTclPath()` — used internally by factories like `Scrollbar::attachTo`, also useful for advanced custom widgets.
89+
- `ProcessTCL::drainPendingCallbacks()` — drains the new in-process callback queue (returns count for tests).
90+
- `ProcessTCL::shouldQuit()` — reads the new `::phpgui_quit` flag.
91+
- `ProcessTCL::unregisterCallback($id)` — releases a previously-registered callback.
92+
93+
---
94+
95+
### Changed
96+
97+
- **Event loop replaced**`/tmp/phpgui_callback.txt` polling is gone. Tcl-side handlers now `lappend ::phpgui_pending $id`; the PHP event loop drains that list every tick. Loop cadence dropped from 100 ms to 10 ms (~100 Hz responsiveness). Lost-callback bug fixed (queue holds an arbitrary number of IDs); cross-process collision fixed (no shared filesystem state).
98+
- **Quit signal**`/tmp/phpgui_quit.txt` removed; `::exit_app` now sets `::phpgui_quit 1`.
99+
- **`Application::quit()`** — no longer calls `exit(0)`. Flips the run flag and tears down WebViews, then `Application::run()` returns to the caller. You can finalise (logging, cleanup) before the script ends.
100+
- **Widget destruction**`AbstractWidget::destroy()` now also calls `unregisterCallback($this->id)`. `Menu::destroy()` cascades through its per-command callback IDs and submenus. `Window::destroy()` frees the `onClose` / `onResize` IDs.
101+
- **Widget nesting**`AbstractWidget` now tracks the full Tk path (`getTclPath()`) instead of assuming one level under root. Frames inside Frames inside Windows finally work as advertised.
102+
103+
---
104+
105+
### Fixed
106+
107+
- **Tcl injection across all widgets.** Before v1.9, a label whose text contained `Hello"; destroy .; "` would execute the embedded `destroy` command. Now every widget runs user-supplied strings through `tclQuote()` (or, where possible, sets a Tcl variable directly via FFI). Documented in `docs/security.md`. Regression suite in `tests/widgets_test/TclInjectionTest.php`.
108+
- **Lost callbacks.** Two events fired between event-loop ticks no longer collapse to one.
109+
- **Callback memory leaks.** Closures registered by destroyed widgets are now released.
110+
- **`docs/Frame.md` was aspirational.** The Tcl-path refactor makes its nesting examples actually work.
111+
- **Animated GIFs flickered transparent dots.** Pre-decoded frame snapshots remove the per-tick reload flash.
112+
- **GIF `setPath` after animation produced "couldn't recognize data" errors.** Reset `-format` to `{}` when swapping to non-GIFs.
113+
- **Spinbox `value` shorthand was clobbering `-values`.** Tk partial-matched `-value` to `-values` and overwrote the enumeration list.
114+
115+
---
116+
117+
### Documentation
118+
119+
New pages in `docs/`:
120+
121+
- `security.md` — Tcl-quoting policy and threat model.
122+
- `event-loop.md` — IPC redesign, test patterns, callback lifecycle.
123+
- `Image.md`, `Text.md`, `Scrollbar.md`, `Listbox.md`, `Radiobutton.md`, `Scale.md`, `Spinbox.md`, `Progressbar.md`, `Notebook.md`, `Treeview.md`, `PanedWindow.md`, `LabelFrame.md`, `Separator.md`.
124+
125+
`docs/Window.md` updated with `onClose` / `onResize` examples. `docs/_sidebar.md` updated to list every new page.
126+
127+
---
128+
129+
### Tests
130+
131+
- 159 → **453 passing assertions** across 30 files.
132+
- New suites: `EventLoopTest`, `ImageTest`, `LabelFrameTest`, `ListboxTest`, `NestingTest`, `NotebookTest`, `PanedWindowTest`, `ProgressbarTest`, `RadiobuttonTest`, `ScaleTest`, `ScrollbarTest`, `SeparatorTest`, `SpinboxTest`, `TclInjectionTest`, `TextTest`, `TreeviewTest`, `WindowEventsTest`.
133+
134+
Run any suite with `php tests/widgets_test/<Name>Test.php` — exits non-zero on failure.
135+
136+
---
137+
138+
### Upgrade notes
139+
140+
No breaking changes. If you were relying on the temp-file behaviour (`/tmp/phpgui_callback.txt`, `/tmp/phpgui_quit.txt`) from outside the library, those files no longer exist. If you called `Application::quit()` and counted on `exit(0)`, add an explicit `exit;` at the end of your script.
141+
142+
---
143+
144+
## [v1.8] — earlier
145+
146+
See `git log v1.7..v1.8`.
147+
148+
## Earlier releases
149+
150+
Earlier release tags (`v1.0` through `v1.7`) are in the git history.

0 commit comments

Comments
 (0)