Skip to content

Layout editor: four rounds of bug fixes - import, generator, canvas, runtime parity - #6

Merged
oblomov-dev merged 6 commits into
mainfrom
claude/exciting-johnson-2dv1u1
Oct 15, 2026
Merged

oblomov-dev merged 6 commits into
mainfrom
claude/exciting-johnson-2dv1u1

Conversation

@oblomov-dev

Copy link
Copy Markdown
Member

Four rounds of bug hunting in the layout editor. The bugs were found by:

  • reading the code
  • fuzzers over the generator, the XML and class import, and the model operations
  • an independent review of the remaining files
  • a random walk through the UI in Chromium (UI5 current and 1.71)
  • random designs run through the real abap2UI5 runtime

Every fix has a test that fails without it.

Opening, importing, exporting

  • A saved .design.json was read as an ABAP class: its indented lines start with ", which is an ABAP comment.
  • A view or class that does not parse closed the dialog, and the pasted text was lost.
  • "Create attributes" after an import looped forever on a binding path longer than 30 characters.
  • A class renamed in the editor:
    • kept its old CLASS name;
    • combined with a new attribute, its declaration was written into the middle of a line.
  • 7.02 classes:
    • a builder variable other than view was taken for 7.40;
    • the chain head was overwritten with an undeclared view =.
  • A view built on after an IF was half imported, and the export called a variable it no longer declared. Such a class is now refused with the reason.
  • A new WHEN could land in a nested CASE, and WHEN \A` OR `B`` counted only A.
  • VALUE \Walldorf, DE`, VALUE 5 READ-ONLYand=>` in seed rows lost their values.
  • Initial dates and times in seed rows came back as 0000-00-00 / 00:00:00.
  • The command palette's Copy ABAP regenerated an imported class and dropped the author's code.
  • Share link: a large design overflowed the stack.

Generator and canvas

  • Texts of backticks gave lines over 255 characters, and emoji could be torn in two at a cut.
  • Line breaks and tabs showed as spaces on the canvas. escapeXml now escapes as c_escape_xml does.
  • The xmlns order depended on the order the view was built in, so the same view could give different classes.
  • Things the canvas showed differently from what abap2UI5 sends, all confirmed on the transpiled runtime:
    • midnight;
    • decimals like .5 and 1e3;
    • -0;
    • control characters, which are now also never written into the class.
  • After a failed render, the next view failed with "duplicate id". Each view now gets an id that is never reused.
  • The preview accepted render messages from any window. It now takes them only from the editor that frames it.
  • A view with two root elements dropped the second one silently.

Editing

  • Controls could be dropped into the grid table's rows, which UI5 refuses.
  • A new Wizard had fewer than the 3 steps UI5 requires.
  • Static values UI5 cannot parse are now reported in Checks:
    • an error for int, float and enum, because they fail the view;
    • a warning for a CSS size (checked with the 1.71 pattern) or a flag;
    • the property field turns red as it is typed.
  • Binding a table again, or "+ Table", replaced hand-built columns without asking.
  • A table of only table fields bound a text to a table field.
  • An edit that threw half way left the design half changed.
  • Focusing an event field with Tab named the event.
  • A field switched between table and scalar kept stale rows.
  • Attribute names on_event, view_display, model_init and main clashed with the class's methods.
  • Undo merged the first keystrokes after New or Open.
  • parseClassName accepted sap.m.; tools/serve.mjs served sibling folders.

Small improvements

  • Attributes named in ƒx expressions count as used and are renamed with them.
  • Ctrl+S saves the design.
  • Enter submits in several fields, and Escape closes menus.
  • A pasted #d= link opens in an editor that is already open.
  • The outline shows #field bindings.

Checks

  • npm test: 377 tests.
  • npm run check:abap: 23 files, 0 failing.
  • npm run check:abap702: 0 issues.
  • npm run verify:abap: 23/23 classes.
  • npm run test:e2e:local: 55/55 tests on UI5 1.144 and 1.71.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NJdCHJULfqVBdruCtKNUm2


Generated by Claude Code

Bugs
- Open: a saved .design.json was read as an ABAP class (its indented
  lines start with a quote, an ABAP comment); JSON is checked first
- Import view / Open class: a text that does not parse closed the dialog
  and lost what was pasted; the dialog now closes only once it is read
- Create attributes after an import: a binding path of more than 30
  characters looped for ever (no number made the name valid)
- An imported class renamed in the editor kept its old CLASS name, so
  file and class differed; the CLASS statements follow the rename
- An imported 7.02 class with its own builder variable got `view =`
  written over it, undeclared; the chain keeps the author's head
- Command palette: Copy ABAP generated the class anew and dropped the
  author's code of an imported class; Open design took no .abap
- Share link: a large design overflowed the stack (one spread of all
  bytes); chunked as the playground link is
- An edit that throws half way (a building block dropped where it does
  not fit) left the design half changed; commit restores and says why
- Focusing an event field (Tab) named the event and added a WHEN
  branch; the suggestion is taken by a click or Enter
- A field switched between table and scalar kept the old row values,
  which reached the class and the canvas
- An attribute named view_display, on_event, model_init or main
  clashed with the method of that name; now refused
- A design file with an attribute without fields crashed the checks;
  normalizeDesign gives every attribute its shape
- Opening a new design kept the merge key of the last typing, so the
  next keystrokes merged into the replacement's undo step

Improvements
- An attribute named in a UI5 expression counts as used and is renamed
  with it; switching a binding to ƒx keeps its path
- Event names show upper case while typing; Enter adds a property and
  inserts a control by class name; Enter on the class URL opens it
- Escape closes the File / Export menus; menus update aria-expanded
- A link pasted into an open editor (#d=) opens its design
- The outline shows #field bindings and the bound dot for them
- A list added from Children gets the offer of its rows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJdCHJULfqVBdruCtKNUm2
Class import and export (src/clas.js)
- A rename together with a new attribute wrote the declaration into the
  middle of a line (the rename shifted the offsets the insertion uses);
  the rename now comes last
- A view built on after an IF (page->tag( ) past another statement) was
  half imported, and the export left a call to an undeclared variable;
  such a class is now refused with the reason
- A new WHEN branch could land in a CASE nested in a branch; CASE depth
  is tracked
- WHEN `A` OR `B` counted only A as handled, B got a second branch
- A 7.02 class with a builder variable other than `view` was taken for
  7.40 and patched with string templates
- DATA ... VALUE `Walldorf, DE` lost its value (split at the comma in
  the literal); VALUE 5 READ-ONLY lost it too
- A seed row with cl_abap_char_utilities=>newline cut the operand at =>

Generator and view (src/abap.js, src/xml.js)
- A text of backticks was cut by its raw length; doubled, a piece passed
  255 characters per line (abapGit refuses that). Cut by the written
  width, and by code point, so an emoji is never torn in two
- A line break or tab in a value was written as it is; XML turns it into
  a space, so the canvas showed one line where the app shows two. Now
  &#xA; / &#xD; / &#x9;, as z2ui5_cl_ui5_util_context=>c_escape_xml( )
- The xmlns order followed the order aggregations were filled, so the
  same view could give different classes; now the order of the view
- A pasted view with two root elements dropped the second silently

Canvas (src/preview/init.js)
- After a render that failed, the next new view could get the id of the
  view still on screen and fail too; ids are now busy until destroyed,
  and a view that fails to place is destroyed
- The preview took render messages from any window; only the editor
  that frames it now (a view's XML can carry script)

Also
- parseClassName took sap.m. and sap..Button
- tools/serve.mjs served sibling folders whose name starts like the root
- Ctrl+S saves the design; the class name shows upper case as typed;
  a click beside the command palette closes it; the offer of rows for a
  control deleted meanwhile creates nothing; a class URL says it loads

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJdCHJULfqVBdruCtKNUm2
- The rows of a grid table (sap.ui.table.Table) took any control - a
  drop on the table could land there, and UI5 refuses: the table manages
  its rows from the binding. The aggregation now takes nothing
- After a view that failed to render (a SimpleForm keeps its labels
  registered then), a later view could get the same id and fail with
  "duplicate id"; every view gets an id of its own now
- A new Wizard had two steps, UI5 wants three to eight
- A static value UI5 cannot take for its property type went unchecked:
  text in an int (columnsM = abc) failed the whole view with a GridSpan
  error, 1.5 as a width was dropped by UI5. Checks says it - an error
  for a number or an enum, which fail the view, a warning for a size or
  a flag, which UI5 logs - and the property field turns red as it is
  typed. The CSS size is UI5 1.71's own pattern

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJdCHJULfqVBdruCtKNUm2
Random designs - every attribute type, tables with table fields, odd
values, 7.40 and 7.02 - generated, transpiled against abap2UI5 and
started: the view and the model the app sends compared with the canvas,
and the class imported again. What did not match:

- Midnight (00:00:00) is ABAP's initial time: the class cannot tell it
  from no time, abap2UI5 sends ''. The canvas showed 00:00:00; it shows
  what the app gets now, and Checks says why
- A decimal the number field takes - .5, 1e3, " 2.5 " - was dropped by
  the class (0 in the app) and shown on the canvas; written now
- -0 showed as -0 on the canvas, abap2UI5 sends 0
- An initial date or time in a seed row ('00000000', written to keep
  the columns) came back from an import as 0000-00-00 / 00:00:00
- A table whose fields are all tables bound a list's text to a table
  field - an error right after one click; the template gets static
  text instead

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJdCHJULfqVBdruCtKNUm2
…nor onto the canvas

A text with a control character (pasted, imported) was written raw into
the ABAP source - invisible there, and the importer's mask read it as a
literal of its own - and shown on the canvas, while abap2UI5 drops it on
the way to the browser. Both drop it now. Found by running 31 odd texts
(quotes, braces, escapes, line breaks, entities, emoji, 300 characters)
through the real abap2UI5 runtime in both releases: every other one
builds exactly the view and the model the canvas shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJdCHJULfqVBdruCtKNUm2
Binding a table (sap.m.Table or the grid table) to a table attribute, or
creating one with + Table, replaces its columns with a template built
from the fields - and the check that asks first looked at the row
template only: hand-made columns went without a word, and a grid table,
whose template lives in its columns alone, never asked. The check now
covers every aggregation the binding replaces, and knows what the
current binding generated by generating it again; + Table asks too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJdCHJULfqVBdruCtKNUm2
@oblomov-dev
oblomov-dev merged commit d765806 into main Oct 15, 2026
2 checks passed
@oblomov-dev
oblomov-dev deleted the claude/exciting-johnson-2dv1u1 branch October 15, 2026 21:30
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.

2 participants