NAC3 v2.3 / Migration paper / Byte-by-byte diff
← methodology  ·  migration home
Paper 03 of 03 · every attribute · every cell

The migration under the microscope.

Side-by-side comparison of every NAC attribute set on every interactive element across the three decoration paths. Joined on the invariant onclick handler from calc.js. Then every manifest element. Then a divergence list with one row per disagreement, annotated by kind.

File sizes

fixtureindex.html (bytes)manifest.json (bytes)
forge-silent65922658
forge-assisted65922658
sumi-manual75733468

Differences are from (a) markup formatting (manual breaks attributes across multiple lines for readability), (b) label_i18n density on manifest non-action elements.

Body / root attributes

fixturedata-nac-plugin
forge-silentcalc
forge-assistedcalc
sumi-manualcalc

Button-by-button decoration

Joined on the invariant onclick handler from calc.js. Each row is one logical button. Cells show data-nac-id + role / verb.

onclickvisiblesilent idsilent r/vassisted idassisted r/vmanual idmanual r/v
clearAll()Ccalc.action.clearaction / clearcalc.action.clearaction / clearcalc.action.clearaction / clear
backspace()calc.action.backspaceaction / backspacecalc.action.backspaceaction / backspacecalc.action.backspaceaction / backspace
percent()%calc.action.percentaction / percentcalc.action.percentaction / percentcalc.action.percentaction / percent
setOp('/')/calc.action.divideaction / dividecalc.action.divideaction / dividecalc.action.op_divideaction / divide
digit('7')7calc.action.digit_7action / digit_7calc.action.digit_7action / digit_7calc.action.digit_7action / digit_7
digit('8')8calc.action.digit_8action / digit_8calc.action.digit_8action / digit_8calc.action.digit_8action / digit_8
digit('9')9calc.action.digit_9action / digit_9calc.action.digit_9action / digit_9calc.action.digit_9action / digit_9
setOp('*')xcalc.action.multiplyaction / multiplycalc.action.multiplyaction / multiplycalc.action.op_multiplyaction / multiply
digit('4')4calc.action.digit_4action / digit_4calc.action.digit_4action / digit_4calc.action.digit_4action / digit_4
digit('5')5calc.action.digit_5action / digit_5calc.action.digit_5action / digit_5calc.action.digit_5action / digit_5
digit('6')6calc.action.digit_6action / digit_6calc.action.digit_6action / digit_6calc.action.digit_6action / digit_6
setOp('-')-calc.action.subtractaction / subtractcalc.action.subtractaction / subtractcalc.action.op_subtractaction / subtract
digit('1')1calc.action.digit_1action / digit_1calc.action.digit_1action / digit_1calc.action.digit_1action / digit_1
digit('2')2calc.action.digit_2action / digit_2calc.action.digit_2action / digit_2calc.action.digit_2action / digit_2
digit('3')3calc.action.digit_3action / digit_3calc.action.digit_3action / digit_3calc.action.digit_3action / digit_3
setOp('+')+calc.action.addaction / addcalc.action.addaction / addcalc.action.op_addaction / add
digit('0')0calc.action.digit_0action / digit_0calc.action.digit_0action / digit_0calc.action.digit_0action / digit_0
dot().calc.action.dotaction / dotcalc.action.dotaction / dotcalc.action.dotaction / dot
equals()=calc.action.equalsaction / equalscalc.action.equalsaction / equalscalc.action.equalsaction / equals
Reading the table. 15 of 19 buttons are byte-identical across all three decorations. The 4 differences are the operator buttons -- Forge uses calc.action.add, Sumi uses calc.action.op_add. The verb name (add) is the same in both, so the dispatch path is functionally equivalent.

Manifest elements

Joined on the logical key (id minus the calc. plugin prefix). (missing) means that fixture's manifest omits the element entirely.

logical keyforge-silentforge-assistedsumi-manual
action.addcalc.action.add action verb=[add]calc.action.add action verb=[add](absent: uses action.op_add)
action.backspacecalc.action.backspace action verb=[backspace]calc.action.backspace action verb=[backspace]calc.action.backspace action verb=[backspace]
action.clearcalc.action.clear action verb=[clear]calc.action.clear action verb=[clear]calc.action.clear action verb=[clear]
action.digit_0... verb=[digit_0]... verb=[digit_0]... verb=[digit_0]
action.digit_1... verb=[digit_1]... verb=[digit_1]... verb=[digit_1]
action.digit_2... verb=[digit_2]... verb=[digit_2]... verb=[digit_2]
action.digit_3... verb=[digit_3]... verb=[digit_3]... verb=[digit_3]
action.digit_4... verb=[digit_4]... verb=[digit_4]... verb=[digit_4]
action.digit_5... verb=[digit_5]... verb=[digit_5]... verb=[digit_5]
action.digit_6... verb=[digit_6]... verb=[digit_6]... verb=[digit_6]
action.digit_7... verb=[digit_7]... verb=[digit_7]... verb=[digit_7]
action.digit_8... verb=[digit_8]... verb=[digit_8]... verb=[digit_8]
action.digit_9... verb=[digit_9]... verb=[digit_9]... verb=[digit_9]
action.dividecalc.action.divide verb=[divide]calc.action.divide verb=[divide](absent: uses action.op_divide)
action.multiplycalc.action.multiply verb=[multiply]calc.action.multiply verb=[multiply](absent: uses action.op_multiply)
action.subtractcalc.action.subtract verb=[subtract]calc.action.subtract verb=[subtract](absent: uses action.op_subtract)
action.op_add(absent)(absent)calc.action.op_add verb=[add]
action.op_divide(absent)(absent)calc.action.op_divide verb=[divide]
action.op_multiply(absent)(absent)calc.action.op_multiply verb=[multiply]
action.op_subtract(absent)(absent)calc.action.op_subtract verb=[subtract]
action.percent... verb=[percent]... verb=[percent]... verb=[percent]
action.dot... verb=[dot]... verb=[dot]... verb=[dot]
action.equals... verb=[equals]... verb=[equals]... verb=[equals]
field.displaycalc.field.display fieldcalc.field.display fieldcalc.field.display field (+ label_i18n)
field.historycalc.field.history fieldcalc.field.history fieldcalc.field.history field (+ label_i18n)
region.maincalc.region.main regioncalc.region.main regioncalc.region.main region (+ label_i18n)
region.padcalc.region.pad regioncalc.region.pad region(absent: uses region.keypad)
region.keypad(absent: uses region.pad)(absent: uses region.pad)calc.region.keypad region (+ label_i18n)

Divergences

Every place where one fixture diverges from another (id structure, manifest role, or verb name).

kindelementforge-silentforge-assistedsumi-manual
idsetOp('/')calc.action.dividecalc.action.dividecalc.action.op_divide
idsetOp('*')calc.action.multiplycalc.action.multiplycalc.action.op_multiply
idsetOp('-')calc.action.subtractcalc.action.subtractcalc.action.op_subtract
idsetOp('+')calc.action.addcalc.action.addcalc.action.op_add
shaperegion.pad vs region.keypaduses paduses paduses keypad
i18nlabel_i18n on field+regionnonenonefull es/en on every element
Reading the divergences. Zero verb-name divergences. The 4 operator id divergences are stylistic (action.add vs action.op_add) and don't change dispatch behaviour because both refer to the same verb. The only structural difference is the keypad region name (pad vs keypad) and Sumi's i18n labels on non-action elements. The verb-set the LLM has to choose from is identical in size for all three.

E2E driver cost (latest run)

Source: results/calc_e2e_2026-05-20T19-55-11-899Z.jsonl, 1 iteration per (fixture, task), driver Claude Sonnet 4.6.

fixturemean tokens inmean tokens outmean LLM latency (ms)5-task cost (Sonnet 4.6)
forge-silent13071083288$0.02774
forge-assisted13071082651$0.02774
sumi-manual17551072772$0.03435

E2E success rate (latest run)

fixturetasks passed
forge-silent5 / 5
forge-assisted5 / 5
sumi-manual5 / 5
The headline. 19 of 19 button decorations agree on verb. Three of 19 disagree on id naming (operator buttons). One region label disagrees (pad vs keypad). Sumi-manual adds label_i18n across the board which costs +17% input tokens per dispatch with no functional benefit for the LLM driver. All three pass every task in the suite.