Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 20 additions & 17 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,23 +48,26 @@ is to measure retained memory across the whole workspace and budget its caches.
**Completion evidence:** cache growth is bounded under the chosen workload, with
before/after memory and latency measurements and coverage for eviction/recovery.

## 3. Tekton authoring — next feature work

View editing and preview reconciliation are established. Extend that workflow
through typed document operations while preserving invalid drafts, undo/redo,
selection, and compatible preview identities.

- [ ] Start non-view editing with constraints and guides: typed insert, remove,
move, and replace operations, including grouped transactions and validation.
- [ ] Add structured inspectors for those resources and the property metadata
they need, using runtime descriptors for enum choices.
- [ ] Connect inspectors to direct layout authoring: guide overlays, anchor
handles, and constraint constant, priority, and activation editing. Surface
conflicts and ambiguity in the same workflow.

**Completion evidence:** a user can create, edit, undo, save, and reload a
constrained layout, including recovery from invalid input, without losing the
last valid preview or selection.
## 3. Tekton authoring — remaining work

View, guide, constraint, and flat resource editing now share undo, validation, and
save/reload workflows. Property edits reuse the preview graph; guide outlines,
constraint endpoint highlights, and unsatisfied-constraint diagnostics are available.
See [the review and measurements](docs/tekton-review.md).

- [ ] Add tree operations and editable inspectors for controllers and menus, and
collection editors for localization strings, key bindings, and theme rules.
- [ ] Add visual reparenting, anchor handles, multi-selection, alignment, and snapping.
- [ ] Add inspection and change tracking for an attached running application's
resource graph, including custom view and controller types.
- [ ] Add ambiguity diagnostics and runtime conflict attribution to generated layout
inputs; current diagnostics report authored constraints that remain unsatisfied.
- [ ] Extend the palette to the remaining container/model-backed controls with
explicit serialization contracts, and add application command/outlet wiring.

**Completion evidence:** users can author a multi-window interface, wire its
controllers and actions, and inspect the running application without losing history
or live object identities.

## Validation

Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ FigDraw is compiled into the application. Merenda's experimental
`useNativeDynlib` mode and `build_dynlib` task have been removed; omit
`-d:useNativeDynlib` from existing build commands.

## Tekton interface builder

Build a resource document with Tekton's widget palette, property inspector, and live
preview. Add layout guides and constraints, pin views to their parent, undo changes,
and save the result for your Merenda app. Use **Interact** to try controls in the preview.

```sh
nim r src/merenda/tekton.nim
# Or open an existing interface:
nim r src/merenda/tekton.nim path/to/interface.cbor
```

See [Tekton's authoring workflow](docs/resources.md#tekton-resource-editor) for supported
resources and how to load the saved interface in your app.

## A few small apps

### Hello, Merenda
Expand Down
60 changes: 49 additions & 11 deletions docs/resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,34 +218,61 @@ discard document.showWindows(app)
app.run()
```

The 13-kind palette and editable view inspector are driven by the same registry used
for runtime construction. Selecting a non-view hierarchy item shows path-addressed,
read-only details for layout, controller/window ownership, target/action connections,
menus, commands, images, localization, key bindings, and themes.
The view palette follows the runtime registry, including custom registered kinds,
sliders, and steppers. The resource picker below it adds layout guides, constraints,
windows, commands, images, localization catalogs, key binding tables, and themes.
Select a view before adding a guide, constraint, or window to use it as the owner or
content view. Newly added resources with missing references remain editable and show
diagnostics until their required fields are filled in.

The inspector edits guide insets, constraint endpoints and anchors, relation,
multiplier, constant, priority, and activation; window titles and connections; command
targets; image sources; and catalog/theme metadata. Enum fields offer choices and
boolean fields use checkboxes. Controllers, menu trees, key binding entries,
localization strings, and theme rules still have read-only detail rows.

The view inspector chooses controls from each `ResourcePropertyDescriptor`: boolean
properties use checkboxes, enum-backed properties use combo boxes populated from the
registry, colors use a popup color well, and open-ended values retain text editing. If
typed text cannot be parsed, it is committed as a string value so the exact draft text
remains visible and validation can diagnose the mismatch. Valid revisions are
reconciled while preserving compatible view and controller identities; layout
constraints are rebuilt against the resulting view map. Invalid drafts never replace
reconciled while preserving compatible view and controller identities. Property edits
stage only changed widgets; edits to layout records rebuild constraints against the
existing views. Unchanged hierarchies, assets, and constraints are retained. Invalid drafts never replace
the last working preview. Hierarchy clicks and preview clicks both update selection by
`ResourceId`, and preview selection uses `installViewSelection` and
`installSelectionRing` rather than changing serialized records or preview state.
`ResourceId`. Design mode captures input at the canvas, so selecting a checkbox does
not toggle it or invoke application actions. Enable **Interact** to run the controls.
The inspector displays live defaults without adding them to the document; authored
values, including invalid text, take precedence. Invalid boolean/enum drafts switch
to text editing so they can be corrected.

Select a guide to see its inset outline, or a constraint to highlight its endpoints.
**Pin to Parent** creates four edge constraints for a selected child of a freeform
container as one undo action. Active layout constraints disable freeform dragging
and keyboard resizing. After layout, unsatisfied authored constraints appear in
Diagnostics, including conflicts with a control's size limits. Invalid numeric text
in typed resource fields stays in the field editor with an error; validly parsed but
inconsistent records remain drafts and keep the last valid preview.

Palette buttons insert into selected containers and beside selected leaf views. The
Delete and Backspace keys remove the selected view and restore selection to its parent;
the toolbar also exposes the same deletion operation.
Delete and Backspace keys remove the selected view or flat resource. Deleting a view
also removes its guides and touching constraints in the same undo action, and selects
the nearest sibling or parent. Use Duplicate, Up, and Down for view duplication and
ordering; Option-arrow moves freeform views, and Shortcut-Option-arrow resizes them
(Shift changes the step from one to ten points).

The editor installs `DocumentFileProtocol` for canonical CBOR reads and writes and
shares the resource draft's `UndoManager` with the application document. A save or
revert updates the manager's clean state and the normal document edited indicator.
**Save** prompts for a destination for new documents, **Save As** writes another file,
and **Open** opens a file in a separate editor window. Save commits the active field
first and stops if its text is invalid. Resource image paths are resolved relative to
the opened document unless the caller supplies an explicit asset base directory.

Run the complete vertical slice with:

```sh
nim r src/merenda/tekton/tekton.nim
nim r src/merenda/tekton.nim
```

The app module exports `tektonStarterBundle`, `newTektonDocument`, and `runTekton`, so
Expand All @@ -254,6 +281,17 @@ an existing CBOR path to load it at startup. The reusable editor, preview reconc
and resource-value editing APIs are available through `import merenda/tekton`; they are
kept separate from the backend-neutral `merenda/nimkit/resources` API.

For typed operations, `ResourceDocument.insertResource`, `replaceResource`,
`removeResource`, and `moveResource` support flat resource records. Insert and replace
also have `ResourceInsertOperation[T]` and `ResourceReplaceOperation[T]` forms. Identity
and index errors reject an operation; semantic errors stay in the draft. Use the
shared undo manager's `beginUndoGrouping`/`endUndoGrouping` to group operations.

Tekton edits resource documents; it does not attach to a separate running process or
generate application logic. For inspection inside an existing app, use NimKit's
`showViewInspector(root, app)` (see `examples/view_inspector_demo.nim`). The remaining
work toward a complete Interface Builder is recorded in [the Tekton review](tekton-review.md).

## Resource Limits

`ResourceLoadLimits` controls the maximum encoded bytes, node count, tree depth, and
Expand Down
Binary file added docs/tekton-editor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 86 additions & 0 deletions docs/tekton-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Tekton review

Reviewed on 2026-09-25 against `5513ba37`. This change improves resource authoring;
Tekton still needs the work listed below to become a complete Interface Builder.

![Tekton with its resource hierarchy, scrolling palette, themed preview, and inspector](tekton-editor.png)

## Confirmed problems and fixes

| Problem | Evidence and change |
| --- | --- |
| A title edit rebuilt every widget, rewired the hierarchy, and replaced constraints. | A factory-count regression constructed 100 widgets for one edit. It now constructs one; layout-only changes construct none. Property and layout edits preserve the view hierarchy. Structural changes retain transactional reconstruction. |
| Selection repeatedly rebuilt the resource outline; synchronization repeatedly rebuilt property editors. | Selection now reuses the outline by document revision. Unchanged synchronization does not read inspector properties again. Hover updates only the hover label and ring. |
| Loading another document at revision zero left the previous preview visible. | A save/load regression reproduced the stale title. Synchronization now tracks document identity as well as revision, and recreates asset contexts on file changes. |
| Inspector checkboxes displayed false when a property's default was true. | Unauthored `enabled` now reads its live getter. Reading defaults does not change the resource document. Invalid authored values remain visible as text. |
| Selecting preview controls triggered their actions. | A canvas click toggled a checkbox in the original implementation. The canvas now intercepts design input. Interact explicitly enables normal control behavior. |
| Failed setters could mutate the preview before raising. | A setter that changes a property and then raises is now rolled back, including the attempted property. The preview revision remains unchanged. |
| Decimal input could be accepted as an empty value for numeric properties. | Failed integer parsing now clears partial results before trying the float parser. Slider and stepper edits exercise this path. |
| A preview root could shrink when constraints were added to its children. | Canvas constraints retain authored root frames at priority 999, allowing required authored constraints to override them. The pin-to-parent workflow checks rendered geometry. |
| New documents had no working Save flow. | Save commits the active editor and prompts for a CBOR destination; Save As and Open are available in the toolbar. |
| Non-view resources could only be inspected. | Typed flat-record operations and inspectors now cover guides, constraints, windows, commands, image source metadata, catalog metadata, and theme metadata. Grouped undo, invalid draft recovery, and save/reload are covered by the shared Tekton runner. |

Preview mouse picking uses an identity lookup table instead of scanning all resource
views at every ancestor. Property conversion contexts retain only images and
localization data. Structural reconciliation retires the staging layout and replaces
its identity maps so the preview does not retain an unused second view graph.

## Measurements

`nim r tests/benchmark_tekton.nim` creates a document, selects 40 widgets, performs
10 title edits, and synchronizes an unchanged document 40 times. These are model and
editor timings, without a native window or GPU rendering. Measured on macOS arm64,
Nim 2.2.12, release ARC, using the same Atlas dependencies for both revisions.
The table reports medians of three alternating original/updated runs. Other local
builds were active, so absolute timings include contention.

| Workload | Original, 100 widgets | Updated, 100 widgets | Original, 300 widgets | Updated, 300 widgets |
| --- | ---: | ---: | ---: | ---: |
| Open document | 153.28 ms | 179.12 ms | 242.43 ms | 187.84 ms |
| Select widget | 5.490 ms | 3.700 ms | 17.909 ms | 3.443 ms |
| Edit title | 37.754 ms | 3.978 ms | 154.158 ms | 7.743 ms |
| Unchanged synchronization | 5.815 ms | 0.001 ms | 17.339 ms | 0.001 ms |

Timings are observations, not CI thresholds. The regression suite asserts bounded
construction work and preserved identities independently of machine speed.

## Authoring workflow

1. Run `nim r src/merenda/tekton.nim`, or pass an existing CBOR document path.
2. Select a container and insert controls. Inspect default and authored properties.
3. Use the resource picker to add guides, constraints, windows, commands, or assets.
4. Edit layout endpoints, anchors, relation, multiplier, constant, priority, and
activation. Selecting a guide outlines its insets; a constraint highlights its
endpoint views. Unsatisfied constraints appear after layout in Diagnostics.
5. Pin a freeform child to its parent with one undo action. Deleting a view removes
its guides and touching constraints in the same action.
6. Choose a preview theme and enable Interact to exercise controls. These preview
settings do not modify the document. Return to design mode for positioning.
7. Save or Save As, then reopen. Invalid numeric field text must be corrected before
saving; semantically invalid resource drafts retain the last valid preview.

## Remaining gaps

- Editing controller/menu trees and collection contents: localization strings,
key binding entries, and theme rules remain read-only.
- Attaching Tekton to an existing running application, preserving its state, and
synchronizing external mutations. The separate NimKit `showViewInspector` remains
available for inspection inside an application.
- Application logic generation and controller/outlet/action connection authoring.
- Visual reparenting, multi-selection, anchor dragging, alignment, snapping, and
palette coverage for the remaining model-backed controls.
- Ambiguity analysis and attribution of solver conflicts to generated constraints.
- Broader performance measurements with complex layouts, assets, and native rendering.

See [PLAN.md](../PLAN.md) for follow-up work. The focused checks live in
`tests/tekton/regressions.nim` and `tests/tekton/layout_authoring.nim`, imported by
`tests/ttekton.nim`.

## Verification

All four shared runners passed on macOS arm64 with Nim 2.2.12. The full
`atlas-run tests` run identified an outdated expected registry-kind list in NimKit;
after adding slider and stepper, `atlas-run tests tests/tnimkit.nim` passed.
All 41 Tekton tests also passed with address and undefined-behavior sanitizers under
ARC and ORC. The example bundle compiled, the shared-runner import check passed,
and the screenshot above was rendered using Metal.
3 changes: 1 addition & 2 deletions merenda.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ requires "chroniclers >= 0.6"
requires "crunchy >= 0.1.11"
# Native damage must request onRender: https://github.com/levovix0/siwin/pull/55
requires "gh:elcritch/siwin#fdec8e4"
# Render ownership and GPU completion; use >= 0.43.0 once released.
requires "gh:elcritch/figdraw#fix/acyclic-render-ownership [siwin, harfbuzz]"
requires "gh:elcritch/figdraw >= 0.43.0 [siwin, harfbuzz]"
requires "sigils >= 0.31.0 [sigNameAsString, closures, siwin, chronos]"
requires "gh:elcritch/variant#fix/ic-type-ids"
requires "kiwiberry"
Expand Down
22 changes: 18 additions & 4 deletions src/merenda/nimkit/resources/resrcconstruction.nim
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ proc initResourceInstance(): ResourceInstance =
keyBindingsValue: initTable[ResourceId, KeyBindingTable](),
)

proc rebindResourceIdentities*(
instance: var ResourceInstance,
views: Table[ResourceId, View],
controllers: Table[ResourceId, ViewController],
layout: ResourceLayoutInstance,
) =
## Retires a staging layout and installs reconciled identity maps. Callers must
## first reconnect controllers, windows, menus, and view hierarchy to these maps.
instance.layoutValue.deactivate()
instance.viewsValue = views
instance.controllersValue = controllers
instance.layoutValue = layout

func instantiated*(instantiation: ResourceInstantiationResult): bool =
not instantiation.diagnostics.hasErrors

Expand Down Expand Up @@ -215,16 +228,17 @@ proc initResourcePropertyContext*(
## Image getters are mapped back to stable resource identifiers by identity;
## localized getter values are intentionally returned as their resolved string.
let
bundleCopy = bundle
instanceCopy = instance
bundleCopy =
ResourceBundle(images: bundle.images, localizations: bundle.localizations)
images = instance.imagesValue
locale = context.locale
imageIdFor = proc(image: ImageResource): ResourceId =
for asset in bundleCopy.images:
if instanceCopy.findImage(asset.id) == image:
if images.getOrDefault(asset.id) == image:
return asset.id
ResourcePropertyContext(
imageFor: proc(id: ResourceId): ImageResource =
instanceCopy.findImage(id),
images.getOrDefault(id),
imageIdFor: imageIdFor,
textFor: proc(key, fallback: string): string =
bundleCopy.localizedValue(locale, key, fallback),
Expand Down
Loading
Loading