feat(convey): make the notification icon a Lucide name
The notification icon contract is now a Lucide icon name, not an emoji. Emitters in JS, Python, and sol notify help send names; the renderer resolves names to vouched SVG and never renders the caller's value as markup. A legacy glyph-to-name table keeps already-persisted localStorage['solstone:notification_history'] entries rendering correctly; that is a migration path for external data, not a backwards-compatibility shim.
The honest price is that this route deliberately manufactures window.ConveyIcons.svg('name') literals inside the notifications object whose only consumer is the guard test's allow_list <= used direction. It trades a one-line regex change for a hand-maintained duplicate list that must stay in sync with CONVEY_ICON_NAMES, and it narrows the notification namespace from 36 available names to 13. It is the right call here because the safety property - a caller can never inject markup through icon - is worth it. It is not parity with L3's genuinely-free zero-mechanism route, and a later session must not read it as precedent for always manufacturing literals.
D5 and D6 were fixed, not deferred. ERROR_ICON in SurfaceState was a hand-rolled inline triangle-alert SVG; it now uses window.ConveyIcons.svg('triangle-alert'). _updateCard never refreshed the icon even though show() updates existing.icon for keyed notifications - invisible while both were the same emoji, but a stale icon under resolved SVG. It now refreshes from the resolver.
The browser Notification icon member was dropped because it is specified as a URL; the old emoji was parsed as a relative URL and fetched, which always failed. Title, body, and tag still deliver.
feat(convey): make the notification icon a Lucide name
The notification icon contract is now a Lucide icon name, not an emoji. Emitters in JS, Python, and sol notify help send names; the renderer resolves names to vouched SVG and never renders the caller's value as markup. A legacy glyph-to-name table keeps already-persisted localStorage['solstone:notification_history'] entries rendering correctly; that is a migration path for external data, not a backwards-compatibility shim.
The honest price is that this route deliberately manufactures window.ConveyIcons.svg('name') literals inside the notifications object whose only consumer is the guard test's allow_list <= used direction. It trades a one-line regex change for a hand-maintained duplicate list that must stay in sync with CONVEY_ICON_NAMES, and it narrows the notification namespace from 36 available names to 13. It is the right call here because the safety property - a caller can never inject markup through icon - is worth it. It is not parity with L3's genuinely-free zero-mechanism route, and a later session must not read it as precedent for always manufacturing literals.
D5 and D6 were fixed, not deferred. ERROR_ICON in SurfaceState was a hand-rolled inline triangle-alert SVG; it now uses window.ConveyIcons.svg('triangle-alert'). _updateCard never refreshed the icon even though show() updates existing.icon for keyed notifications - invisible while both were the same emoji, but a stale icon under resolved SVG. It now refreshes from the resolver.
The browser Notification icon member was dropped because it is specified as a URL; the old emoji was parsed as a relative URL and fetched, which always failed. Title, body, and tag still deliver.
feat(convey): make the notification icon a Lucide name
The notification icon contract is now a Lucide icon name, not an emoji. Emitters in JS, Python, and sol notify help send names; the renderer resolves names to vouched SVG and never renders the caller's value as markup. A legacy glyph-to-name table keeps already-persisted localStorage['solstone:notification_history'] entries rendering correctly; that is a migration path for external data, not a backwards-compatibility shim.
The honest price is that this route deliberately manufactures window.ConveyIcons.svg('name') literals inside the notifications object whose only consumer is the guard test's allow_list <= used direction. It trades a one-line regex change for a hand-maintained duplicate list that must stay in sync with CONVEY_ICON_NAMES, and it narrows the notification namespace from 36 available names to 13. It is the right call here because the safety property - a caller can never inject markup through icon - is worth it. It is not parity with L3's genuinely-free zero-mechanism route, and a later session must not read it as precedent for always manufacturing literals.
D5 and D6 were fixed, not deferred. ERROR_ICON in SurfaceState was a hand-rolled inline triangle-alert SVG; it now uses window.ConveyIcons.svg('triangle-alert'). _updateCard never refreshed the icon even though show() updates existing.icon for keyed notifications - invisible while both were the same emoji, but a stale icon under resolved SVG. It now refreshes from the resolver.
The browser Notification icon member was dropped because it is specified as a URL; the old emoji was parsed as a relative URL and fetched, which always failed. Title, body, and tag still deliver.