custom widget handoff
Objectiveβ
Make Protota a source-first renderer for GTK4/Libadwaita applications:
official app source β editable Protota document β rendered mockup β .blp export
The renderer must be generic across GNOME Core and Circle applications. Do not add per-app presets, hand-authored UI JSON, screenshot-specific CSS, or visual substitutions that claim to render a widget whose source Protota did not understand.
The immediate blocker is custom widgets. Many GNOME applications declare their window structure in Blueprint/GtkBuilder, but create major visual regions from Vala, C, Rust, or GJS. If those regions disappear, source import is technically successful but visually useless.
Current implementationβ
- Blueprint and GtkBuilder import lives in
src/utils/blueprint.ts. - Multi-file Blueprint template expansion is
blueprintBundleToDocument(). - Unsupported
$Template instance { ... }references becomeProtota.CustomWidgetnodes, rendered as visibly striped, allocated boundaries insrc/components/AdwaitaRenderer.tsx. - Comparison code masks those boundaries and reports:
- raw similarity;
- source-resolved similarity;
- unresolved-widget coverage;
- raw-similarity ceiling.
- CI runs native applications through Broadway and captures the exact native
window dimensions.
tests/broadway-reference.spec.tsholds the contract. - Calculator is the only current source-bundle CI case. CI clones the official
gnome-calculatorcheckout and importssrc/ui/math-window.blpplus everysrc/ui/*.blpfile. It is explicitly labelledinputKind: source-bundle.
Recent generic importer additions include GtkStack, GtkStackPage, and
GtkScrolledWindow, plus Blueprint array handling (styles [ ... ]).
Evidence from the first source-derived comparisonβ
Run: 30444382804, Calculator artifact. Native and Protota surfaces were both
360 Γ 460; geometry is not the mismatch.
| Metric | Result |
|---|---|
| Raw similarity | 78.04% |
| Source-resolved similarity | 75.92% |
| Foreground IoU | 14.70% |
| Unresolved-widget coverage | 10.43% |
| Attainable raw ceiling | 89.57% |
Visual inspection found a native Calculator window with a display/history and
full basic keypad. The imported render had header controls and a visible
GtkSourceView boundary, but no usable keypad. It is an honest result, not a
renderer pass.
GtkSourceView is referenced from the Calculator BLP but implemented in code,
so it is correctly represented as an explicit custom-widget boundary. The
larger MathButtons region is code-defined and is not currently discoverable
from the BLP tree, so it is absent rather than falsely rendered.
Source facts to preserveβ
Official Calculator source on Himachal is at:
/var/home/james/work/gnome-source-fixtures/gnome-calculator
Relevant files:
src/ui/math-window.blp: template$MathWindow, structural window layout.src/ui/math-display.blp: includes$GtkSourceViewreference.src/ui/buttons-*.blp: declarative button panel templates.- Calculator's
MathButtonsis implemented in Vala, then instantiated by the app/runtime rather than defined as a BLP template.
The BLP contains a $MathButtons _buttons { ... } reference near the end of
the window content. Determine why the importer does not retain that reference
as an allocated code-boundary node after template expansion. Fix the parser or
bundle resolver generically if it is at fault; do not special-case Calculator.
What a correct solution should doβ
1. Retain every source-declared construction boundaryβ
For each declarative child, import one of:
- a supported generic GTK/Adwaita widget tree;
- an inlined template from the supplied source bundle; or
- an explicit
custom-widgetboundary.
No source child may silently vanish. Introduce an importer report that records the source path, source class/template, instance id, parent id/slot, and the reason for a boundary.
2. Recover geometry for code-only widgetsβ
A boundary needs an allocated rectangle that follows GTK layout rules. Derive its width/height from the best available evidence, in priority order:
- explicit Blueprint/GtkBuilder size requests, margins, expand flags, and grid attachment;
- properties/types visible in the code constructor or composite template;
- sibling allocation and parent layout constraints;
- native Broadway widget geometry, when a stable source-id-to-DOM mapping can be established.
Render a labelled boundary only when visual implementation is unavailable. Its actual allocated rectangle must be included in the unresolved mask.
3. Extract code construction structurally, not by application nameβ
Implement a generic static extractor for common patterns in Vala/C/Rust/GJS:
- GObject subclass template declarations;
Gtk.Widget/Adw.*construction calls;set_child,append,attach,set_content,add_overlay, and stack/page insertion calls;- builder/template child declarations;
- constant properties affecting geometry and visibility.
It is acceptable to begin with a conservative extractor that emits boundaries for unrecognised expressions. It must never fabricate controls or text from a screenshot.
4. Promote reusable widget implementationsβ
Some classes should become generic renderer features when encountered across
apps, for example GtkSourceView, list/grid models, tab bars, navigation
pages, scrollers, stacks, overlays, and custom drawing surfaces. Add generic
support only when the semantics are documented and testable.
Required tests and success criteriaβ
- A unit test using the real Calculator checkout (
OFFICIAL_SOURCE_ROOT) must prove that$MathButtons _buttonssurvives as either an expanded tree or acustom-widgetwith its source id and parent placement. - Add source-bundle fixture tests for each newly supported generic pattern.
- CI comparison JSON must retain
inputKind, geometry, unresolved coverage, and source-resolved similarity. - A visual score is a pass only when both source-resolved similarity and foreground IoU meet calibrated thresholds. High background similarity alone is never a pass.
- Track maximum attainable similarity as
1 - unresolvedWidgetCoverage; do not demand a raw score above that ceiling.
Non-goals / guardrailsβ
- Do not restore or tune
public/presets/*.mockup.jsonto make comparisons look better. They are legacy inputs only. - Do not encode official app UIs in JSON.
- Do not add per-app branches in the renderer.
- Do not use screenshots as runtime input or infer semantic widgets from their pixels.
- Do not hide unresolved regions from metrics.
- Keep
playwright-report/index.htmluntouched; it is a user-owned dirty file.
Recommended first investigationβ
Run the official-source unit test on Himachal:
cd /var/home/james/work/protota-source-loop
OFFICIAL_SOURCE_ROOT=/var/home/james/work/gnome-source-fixtures/gnome-calculator/src/ui \
podman run --rm --userns=keep-id \
-e OFFICIAL_SOURCE_ROOT=/fixtures \
-v "$PWD":/work:Z \
-v /var/home/james/work/gnome-source-fixtures/gnome-calculator/src/ui:/fixtures:ro,Z \
-w /work mcr.microsoft.com/playwright:v1.62.0-noble npm run test:unit
Then inspect the expanded math-window.blp token stream and parser cursor
around $MathButtons _buttons. The first deliverable is not a visual keypad;
it is a correct retained, allocated boundary with an auditable source report.