Skip to content

feat: migrate web UI from kotlinx.html to xemantic-kotlin-js#55

Open
morisil wants to merge 2 commits intomainfrom
claude/issue-54-20260218-1903
Open

feat: migrate web UI from kotlinx.html to xemantic-kotlin-js#55
morisil wants to merge 2 commits intomainfrom
claude/issue-54-20260218-1903

Conversation

@morisil
Copy link
Copy Markdown
Member

@morisil morisil commented Feb 18, 2026

Replace kotlinx.html dependency with xemantic-kotlin-js library in the golem-xiv-web module. All view code now uses the node { } DSL pattern from com.xemantic.kotlin.js.dom.html.* instead of kotlinx.html TagConsumer.

Closes #54

Generated with Claude Code

Replace kotlinx.html dependency with xemantic-kotlin-js library in the
golem-xiv-web module. All view code now uses the `node { }` DSL pattern
from com.xemantic.kotlin.js.dom.html.* instead of kotlinx.html TagConsumer.

Changes:
- Replace libs.kotlinx.html with libs.xemantic.kotlin.js in build.gradle.kts
- Remove js/Dom.kt (GolemTagConsumer/inject pattern replaced by node { +el })
- Remove js/Accessibility.kt (ariaLabel now from com.xemantic.kotlin.js.dom)
- Remove js/Storage.kt (setItems now from com.xemantic.kotlin.js.dom)
- Rewrite HtmlMainView, HtmlHeaderView, HtmlNavigationRailView,
  HtmlNotFoundView, HtmlMemoryView, HtmlCognitionView, UiElements
  using xemantic-kotlin-js node { } DSL

Closes #54

Co-authored-by: Kazik Pogoda <morisil@users.noreply.github.com>
@claude
Copy link
Copy Markdown

claude bot commented Feb 18, 2026

PR Review: Migrate web UI from kotlinx.html to xemantic-kotlin-js

Overall this is a clean and well-motivated migration. The node { } DSL from xemantic-kotlin-js is more idiomatic and removes the need for the custom GolemTagConsumer workaround that was required to inject pre-built DOM elements into kotlinx.html builders. Here is my detailed feedback:


Positive changes

Removal of GolemTagConsumer hack — The old Dom.kt contained a marker-div injection pattern that was fragile: it used querySelector("#golem-marker-N") to locate placeholder <div> elements and replace them post-finalization. The new node { } DSL supports direct child insertion via +element, eliminating this complexity entirely. This is a significant correctness improvement.

Cleaner element construction — The pattern node { div("foo") { +child } } is simpler and more readable than dom.div("foo") { inject(child) }.

HtmlMainView.kt — Removal of the large commented-out buildMainUi block is good housekeeping.

HtmlNavigationRailView.kt — Inlining window.resizes() as window.eventFlow<Event>("resize") and removing the resizes extension avoids having a separate utility file for a one-liner.


Issues and observations

1. Icon() function in UiElements.kt wraps content differently

Before: fun Icon(name: String): HTMLSpanElement = document.create.span { i { +name } }

After: fun Icon(name: String): HTMLSpanElement = node { span { +name } }

The new version renders <span>name</span> instead of <span><i>name</i></span>. This is a behavioral regression — the <i> element is required for Material Icons (icon font rendering). The top-level icon() DSL function from xemantic-kotlin-js is used correctly elsewhere (e.g., icon("menu"), icon("person")), but the Icon() factory function in UiElements.kt (used by Button, IconButton, Link) no longer wraps in <i>. Verify whether Icon() should delegate to icon() or use span { i { +name } }.

2. Missing newline at end of file in UiElements.kt

The diff ends with \ No newline at end of file. This is worth fixing.

3. HtmlNavigationRailView.ktit.setAttribute(...) vs DSL attributes

The old code used attributes["data-target"] = "..." (kotlinx.html style). The new code uses it.setAttribute("data-target", "..."). This is fine but inconsistent with it.ariaLabel = ... which uses a typed property extension. Consider it.dataset["target"] = "..." as the idiomatic Web API equivalent for data-* attributes.

4. HtmlCognitionView.ktaddExpression role class

Before: val role = if (expression.agent is EpistemicAgent.AI) "ai" else "human"

After: val role = expression.agent.cssClass()

Good simplification, but worth verifying: does EpistemicAgent.Computer.cssClass() produce a meaningful CSS class for the expression display?

5. Commented-out code removal in HtmlCognitionView.kt

The large block of commented-out addTextResponse / addToolUseRequest / addToolUseResponse methods has been removed. Fine if this dead code is no longer needed, but if those features are planned, a GitHub issue would be better than keeping them as comments.

6. Storage.kt deletion

js/Storage.kt (the Storage.setItems() extension) is deleted. Confirm nothing else in the codebase still depends on it before merging.

7. Accessibility.kt deletion

The ariaLabel extension on HTMLTag is replaced by ariaLabel from com.xemantic.kotlin.js.dom. Verify the new property extension has the same semantics (writes to element.setAttribute("aria-label", ...) for browser compatibility).


Minor nits

  • The println("adding expression: $expression") debug log in HtmlCognitionView.addExpression predates this PR but is worth removing in a future cleanup.
  • class HtmlMemoryView() and class HtmlNotFoundView() have empty primary constructor parens — minor style issue, consistent with the existing codebase.

Summary

The migration is sound and the resulting code is cleaner. The main item to verify before merging is issue #1 (the Icon() regression removing the <i> wrapper) and issue #6 (ensure Storage.setItems() is truly unused).

Generated with Claude Code

Copy link
Copy Markdown
Member Author

@morisil morisil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude can you address these comments?

import com.xemantic.ai.golem.web.ui.Icon
import com.xemantic.ai.golem.web.ui.IconButton
import com.xemantic.ai.golem.web.view.HasRootHtmlElement
import com.xemantic.kotlin.js.dom.html.article
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use wildcard here


private val promptInput = dom.textArea {
placeholder = "Another question from the carbon side..."
private val promptInput: HTMLTextAreaElement = node {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess explicit type is not needed here?

import com.xemantic.ai.golem.web.navigation.HtmlNavigationRailView
import com.xemantic.ai.golem.web.cognition.HtmlCognitionView
import com.xemantic.ai.golem.web.view.HasRootHtmlElement
import com.xemantic.kotlin.js.dom.html.div
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use wildcard import here

import kotlinx.browser.document
import kotlinx.html.dom.create
import kotlinx.html.js.iframe
import com.xemantic.kotlin.js.dom.html.iframe
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wildcard import here

import com.xemantic.ai.golem.web.js.dom
import com.xemantic.ai.golem.web.js.inject
import com.xemantic.ai.golem.web.view.HasRootHtmlElement
import com.xemantic.kotlin.js.dom.ariaLabel
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wildcard

div { +"Initiate" }
private val initiateCognitionItem = node {
a {
it.setAttribute("data-target", "initiate-cognition")
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it["data-target"] = "initiate-cognition"]

should be possible here

div { +"Memory" }
private val memoryItem = node {
a {
it.setAttribute("data-target", "memory")
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see previous comment, apply everywhere if possible

i {
+name
}
fun Icon(name: String): HTMLSpanElement = node {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove this code since icon("foo") was added to the library, and is now the preferred way of using icons with beer css.

): HTMLButtonElement = node {
button("round") {
if (icon != null) {
i { +icon }
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can also use icon(icon) from xemantic-kotlin-js

): HTMLAnchorElement = node {
a("wave round") {
if (icon != null) {
i { +icon }
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use icon(icon)

@claude
Copy link
Copy Markdown

claude bot commented Feb 18, 2026

Claude finished @morisil's task in 1m 39s —— View job


Addressing Review Comments

  • Read affected files
  • Fix wildcard imports in HtmlCognitionView.kt
  • Remove explicit type in HtmlCognitionView.kt:62
  • Fix wildcard import in HtmlMainView.kt
  • Fix wildcard import in HtmlMemoryView.kt
  • Fix wildcard import in HtmlHeaderView.kt
  • Fix wildcard import in HtmlNavigationRailView.kt
  • Use it.dataset["target"] syntax in HtmlNavigationRailView.kt (all 3 occurrences)
  • Update UiElements.kt: remove Icon(), use icon() DSL in Button, IconButton, Link
  • Commit and push changes

Summary

All review comments have been addressed in commit e62242c:

  • Wildcard imports: Replaced individual com.xemantic.kotlin.js.dom.html.* element imports with a single wildcard import in all 5 view files.
  • Explicit type removed: promptInput in HtmlCognitionView no longer needs : HTMLTextAreaElement — type is inferred from node { textarea(...) }.
  • dataset syntax: Replaced all it.setAttribute("data-target", ...) calls in HtmlNavigationRailView with it.dataset["target"] = ....
  • Icon() removed: The factory function is gone. Button, IconButton, and Link in UiElements.kt now call icon(icon) from the xemantic-kotlin-js library directly, which is both correct for Beer CSS and idiomatic with the new DSL.
    |

- Use wildcard imports (com.xemantic.kotlin.js.dom.html.*) in all view files
- Remove explicit type annotation on promptInput in HtmlCognitionView
- Replace setAttribute("data-target", ...) with dataset["target"] = ... in HtmlNavigationRailView
- Remove Icon() factory function (superseded by icon() DSL from xemantic-kotlin-js)
- Use icon() DSL instead of i {} in Button, IconButton, and Link in UiElements

Co-authored-by: Kazik Pogoda <morisil@users.noreply.github.com>
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.

[Task]: Update UI rendering to xemantic-kotlin-js

2 participants