Articles: key by label across subtypes, scope per property, FO toggle widget

A single article label (e.g. 'f') may emit on multiple `articles.<subtype>`
lines — `defaults` today, `overwrites` planned — with disjoint property
sets that apply at different stages. Model now merges those emissions:

  { name: 'f', properties: [{ name, value, scope: 'defaults'|'overwrites' }] }

Per-label entries in the AR pane show a property-count breakdown
(`f (1 defaults, 2 overwrites)`). The Article FO pane lists each property
with a `(O-) default` / `(-O) overwrite` toggle — click flips the scope
and marks the score dirty. ASCII glyph mimics a physical switch position
so the active scope is visible at a glance.

Fixture tests cover both the existing single-subtype shape and a synthetic
two-subtype merge (177 assertions total).
This commit is contained in:
c0dev0id
2026-06-28 19:11:01 +02:00
parent 913114bb02
commit 133117922a
4 changed files with 66 additions and 15 deletions

View File

@@ -153,6 +153,29 @@ export const PaneFO = {
? h(EnvelopeEditor, { basicProperties: node.basicProperties, onChange })
: null,
);
} else if (node.type === 'article') {
const markDirty = () => props.store.markDirty();
const SCOPES = ['defaults', 'overwrites'];
const scopeLabel = s => s === 'defaults' ? '(O-) default' : '(-O) overwrite';
children.push(
h('h4', H4, `Article: ${node.name}`),
h('ul', { class: 'se-article-props', style: 'list-style:none;padding:0;margin:0' },
node.properties.map((p, idx) =>
h('li', { key: idx, style: 'display:flex;gap:0.5rem;align-items:center;padding:0.25rem 0' }, [
h('span', { style: 'flex:1' }, `${p.name} = ${p.value}`),
h('button', {
class: ['se-scope-toggle', `scope-${p.scope}`],
style: 'font-family:monospace;min-width:8em',
onClick: () => {
const i = SCOPES.indexOf(p.scope);
p.scope = SCOPES[(i + 1) % SCOPES.length];
markDirty();
},
}, scopeLabel(p.scope)),
])
)
),
);
} else if (node.type === 'bar') {
const markBarDirty = () => { node.isDirty = true; props.store.markDirty(); };
children.push(